插件开发指南
入门
CLI 插件是一个 npm 包,它可以使用 Vue CLI 为项目添加额外的功能。这些功能可以包括
- 更改项目 webpack 配置 - 例如,您可以为特定文件扩展名添加新的 webpack 解析规则,如果您的插件应该与这种类型的文件一起使用。例如,
@vue/cli-plugin-typescript
添加了这样的规则来解析.ts
和.tsx
扩展名; - 添加新的 vue-cli-service 命令 - 例如,
@vue/cli-plugin-unit-jest
添加了一个新的命令test:unit
,允许开发人员运行单元测试; - 扩展
package.json
- 当您的插件向项目添加一些依赖项时,这是一个有用的选项,您需要将它们添加到包依赖项部分; - 在项目中创建新文件和/或修改旧文件。有时,创建一个示例组件或修改主文件以添加一些导入是一个好主意;
- 提示用户选择某些选项 - 例如,您可以询问用户是否要创建上面提到的示例组件。
提示
不要过度使用 vue-cli 插件!如果您只想包含某个依赖项,例如 Lodash - 使用 npm 手动执行它比只创建一个特定插件更容易。
CLI 插件应该始终包含一个 服务插件 作为其主要导出,并且可以选择包含一个 生成器、一个 提示文件 和一个 Vue UI 集成。
作为 npm 包,CLI 插件必须具有 package.json
文件。还建议在 README.md
中提供插件描述,以帮助其他人从 npm 上找到您的插件。
因此,典型的 CLI 插件文件夹结构如下所示
.
├── README.md
├── generator.js # generator (optional)
├── index.js # service plugin
├── package.json
├── prompts.js # prompts file (optional)
└── ui.js # Vue UI integration (optional)
命名和可发现性
为了使 CLI 插件在 Vue CLI 项目中可用,它必须遵循命名约定 vue-cli-plugin-<name>
或 @scope/vue-cli-plugin-<name>
。它允许您的插件
- 被
@vue/cli-service
发现; - 通过搜索被其他开发人员发现;
- 通过
vue add <name>
或vue invoke <name>
安装。
警告
确保正确命名插件,否则无法通过 vue add
命令安装它或使用 Vue UI 插件搜索找到它!
为了在用户搜索您的插件时获得更好的可发现性,请在插件 package.json
文件的 description
字段中添加描述您的插件的关键字。
示例
{
"name": "vue-cli-plugin-apollo",
"version": "0.7.7",
"description": "vue-cli plugin to add Apollo and GraphQL"
}
您应该在 homepage
或 repository
字段中添加指向插件网站或存储库的 URL,以便在您的插件描述中显示“更多信息”按钮
{
"repository": {
"type": "git",
"url": "git+https://github.com/Akryum/vue-cli-plugin-apollo.git"
},
"homepage": "https://github.com/Akryum/vue-cli-plugin-apollo#readme"
}
生成器
CLI 插件的生成器部分通常在您想用新依赖项扩展您的包、在您的项目中创建新文件或编辑现有文件时需要。
在 CLI 插件中,生成器应该放在 generator.js
或 generator/index.js
文件中。它将在两种可能的情况下被调用
在项目初始创建期间,如果 CLI 插件作为项目创建预设的一部分安装。
当插件在项目创建后安装并通过
vue add
或vue invoke
单独调用时。
生成器应该导出一个接收三个参数的函数
一个 GeneratorAPI 实例;
此插件的生成器选项。这些选项在项目创建的 提示 阶段解析,或从
~/.vuerc
中的已保存预设加载。例如,如果已保存的~/.vuerc
如下所示
{
"presets" : {
"foo": {
"plugins": {
"@vue/cli-plugin-foo": { "option": "bar" }
}
}
}
}
如果用户使用 foo
预设创建项目,那么 @vue/cli-plugin-foo
的生成器将接收 { option: 'bar' }
作为其第二个参数。
对于第三方插件,当用户执行 vue invoke
时,选项将从提示或命令行参数中解析(参见 提示)。
- 整个预设(
presets.foo
)将作为第三个参数传递。
创建新的模板
当您调用 api.render('./template')
时,生成器将使用 EJS 渲染 ./template
中的文件(相对于生成器文件解析)。
假设我们正在创建 vue-cli-auto-routing 插件,并且我们希望在插件调用时对项目进行以下更改
- 创建一个包含默认布局文件的
layouts
文件夹; - 创建一个包含
about
和home
页面的pages
文件夹; - 将
router.js
添加到src
文件夹的根目录
要渲染此结构,您需要先在 generator/template
文件夹中创建它
创建模板后,您应该将 api.render
调用添加到 generator/index.js
文件中
module.exports = api => {
api.render('./template')
}
编辑现有模板
此外,您可以使用 YAML 前置 matter 继承和替换现有模板文件(即使来自另一个包)的部分
---
extend: '@vue/cli-service/generator/template/src/App.vue'
replace: !!js/regexp /<script>[^]*?<\/script>/
---
<script>
export default {
// Replace default script
}
</script>
也可以进行多次替换,但需要将替换字符串包含在<%# REPLACE %>
和<%# END_REPLACE %>
块中
---
extend: '@vue/cli-service/generator/template/src/App.vue'
replace:
- !!js/regexp /Welcome to Your Vue\.js App/
- !!js/regexp /<script>[^]*?<\/script>/
---
<%# REPLACE %>
Replace Welcome Message
<%# END_REPLACE %>
<%# REPLACE %>
<script>
export default {
// Replace default script
}
</script>
<%# END_REPLACE %>
文件名边缘情况
如果要渲染一个以点开头的模板文件(例如.env
),则必须遵循特定的命名约定,因为在将插件发布到 npm 时会忽略点文件
# dotfile templates have to use an underscore instead of the dot:
/generator/template/_env
# When calling api.render('./template'), this will be rendered in the project folder as:
/generator/template/.env
因此,这意味着如果要渲染文件名实际上以下划线开头的文件,也必须遵循特殊的命名约定
# such templates have to use two underscores instead of one:
/generator/template/__variables.scss
# When calling api.render('./template'), this will be rendered in the project folder as:
/generator/template/_variables.scss
扩展包
如果需要向项目添加额外的依赖项,创建新的 npm 脚本或以任何其他方式修改package.json
,可以使用 API extendPackage
方法。
// generator/index.js
module.exports = api => {
api.extendPackage({
dependencies: {
'vue-router-layout': '^0.1.2'
}
})
}
在上面的示例中,我们添加了一个依赖项:vue-router-layout
。在插件调用期间,将安装此 npm 模块,并将此依赖项添加到用户package.json
文件中。
使用相同的 API 方法,可以向项目添加新的 npm 任务。为此,需要指定任务名称以及应该在用户package.json
的scripts
部分中运行的命令
// generator/index.js
module.exports = api => {
api.extendPackage({
scripts: {
greet: 'vue-cli-service greet'
}
})
}
在上面的示例中,我们添加了一个新的greet
任务来运行在服务部分中创建的自定义 vue-cli 服务命令。
更改主文件
使用生成器方法可以对项目文件进行更改。最常见的情况是对main.js
或main.ts
文件进行一些修改:新的导入,新的Vue.use()
调用等。
让我们考虑一下通过模板创建了router.js
文件,现在要将此路由器导入到主文件的情况。我们将使用两个 Generator API 方法:entryFile
将返回项目的 main 文件(main.js
或main.ts
),injectImports
用于向此文件添加新的导入
// generator/index.js
api.injectImports(api.entryFile, `import router from './router'`)
现在,当我们导入路由器后,可以在主文件中将此路由器注入到 Vue 实例中。我们将使用afterInvoke
钩子,该钩子将在文件写入磁盘后调用。
首先,需要使用 Node fs
模块(它提供与文件系统交互的 API)读取主文件内容,并将此内容按行拆分
// generator/index.js
module.exports.hooks = (api) => {
api.afterInvoke(() => {
const fs = require('fs')
const contentMain = fs.readFileSync(api.resolve(api.entryFile), { encoding: 'utf-8' })
const lines = contentMain.split(/\r?\n/g)
})
}
然后,应该找到包含render
单词的字符串(它通常是 Vue 实例的一部分),并将我们的router
添加为下一个字符串
// generator/index.js
module.exports.hooks = (api) => {
api.afterInvoke(() => {
const fs = require('fs')
const contentMain = fs.readFileSync(api.resolve(api.entryFile), { encoding: 'utf-8' })
const lines = contentMain.split(/\r?\n/g)
const renderIndex = lines.findIndex(line => line.match(/render/))
lines[renderIndex] += `\n router,`
})
}
最后,需要将内容写回主文件
// generator/index.js
module.exports.hooks = (api) => {
api.afterInvoke(() => {
const { EOL } = require('os')
const fs = require('fs')
const contentMain = fs.readFileSync(api.resolve(api.entryFile), { encoding: 'utf-8' })
const lines = contentMain.split(/\r?\n/g)
const renderIndex = lines.findIndex(line => line.match(/render/))
lines[renderIndex] += `${EOL} router,`
fs.writeFileSync(api.resolve(api.entryFile), lines.join(EOL), { encoding: 'utf-8' })
})
}
服务插件
服务插件用于修改 webpack 配置,创建新的 vue-cli 服务命令或更改现有命令(例如serve
和build
)。
服务插件在创建服务实例时自动加载 - 即每次在项目中调用vue-cli-service
命令时。它位于 CLI 插件根文件夹的index.js
文件中。
服务插件应该导出一个函数,该函数接收两个参数
一个PluginAPI实例
一个包含在
vue.config.js
中或在package.json
的"vue"
字段中指定的项目本地选项的对象。
服务插件文件中所需的最小代码如下
module.exports = () => {}
修改 webpack 配置
API 允许服务插件扩展/修改不同环境的内部 webpack 配置。例如,这里我们使用 webpack-chain 修改 webpack 配置,以包含具有给定参数的vue-auto-routing
webpack 插件
const VueAutoRoutingPlugin = require('vue-auto-routing/lib/webpack-plugin')
module.exports = (api, options) => {
api.chainWebpack(webpackConfig => {
webpackConfig
.plugin('vue-auto-routing')
.use(VueAutoRoutingPlugin, [
{
pages: 'src/pages',
nested: true
}
])
})
}
也可以使用configureWebpack
方法修改 webpack 配置或返回要与 webpack-merge 合并的对象。
添加新的 cli-service 命令
使用服务插件,除了标准命令(例如serve
和build
)之外,还可以注册新的 cli-service 命令。可以使用registerCommand
API 方法来实现。
以下是如何创建一个简单的新的命令,该命令将向开发人员控制台打印问候语的示例
api.registerCommand(
'greet',
{
description: 'Writes a greeting to the console',
usage: 'vue-cli-service greet'
},
() => {
console.log(`👋 Hello`)
}
)
在此示例中,我们提供了命令名称('greet'
)、一个包含description
和usage
的命令选项对象,以及一个在vue-cli-service greet
命令上运行的函数。
提示
可以在package.json
文件中通过 Generator将新命令添加到项目 npm 脚本列表中。
如果尝试在安装了插件的项目中运行新命令,将看到以下输出
$ vue-cli-service greet
👋 Hello!
还可以为新命令指定可用选项列表。让我们添加选项--name
并将函数更改为在提供此名称时打印此名称。
api.registerCommand(
'greet',
{
description: 'Writes a greeting to the console',
usage: 'vue-cli-service greet [options]',
options: { '--name': 'specifies a name for greeting' }
},
args => {
if (args.name) {
console.log(`👋 Hello, ${args.name}!`);
} else {
console.log(`👋 Hello!`);
}
}
);
现在,如果使用指定的--name
选项运行greet
命令,此名称将添加到控制台消息中
$ vue-cli-service greet --name 'John Doe'
👋 Hello, John Doe!
修改现有的 cli-service 命令
如果要修改现有的 cli-service 命令,可以使用api.service.commands
检索它并添加一些更改。我们将向控制台打印一条消息,其中包含应用程序运行的端口
const { serve } = api.service.commands
const serveFn = serve.fn
serve.fn = (...args) => {
return serveFn(...args).then(res => {
if (res && res.url) {
console.log(`Project is running now at ${res.url}`)
}
})
}
在上面的示例中,我们从现有命令列表中检索serve
命令;然后修改其fn
部分(fn
是创建新命令时传递的第三个参数;它指定在运行命令时运行的函数)。完成修改后,将成功运行serve
命令后打印控制台消息。
为命令指定模式
如果插件注册的命令需要在特定默认模式下运行,则插件需要通过module.exports.defaultModes
以{ [commandName]: mode }
的形式公开它
module.exports = api => {
api.registerCommand('build', () => {
// ...
})
}
module.exports.defaultModes = {
build: 'production'
}
这是因为在加载环境变量之前需要知道命令的预期模式,而加载环境变量又需要在加载用户选项/应用插件之前进行。
提示
提示是处理在创建新项目或将新插件添加到现有项目时用户选择所必需的。所有提示逻辑都存储在prompts.js
文件中。提示使用inquirer在后台呈现。
当用户通过调用vue invoke
初始化插件时,如果插件在其根目录中包含prompts.js
,它将在调用期间使用。该文件应该导出一个问题数组,这些问题将由 Inquirer.js 处理。
应该直接导出问题数组,或者导出返回这些问题的函数。
例如,直接问题数组
// prompts.js
module.exports = [
{
type: 'input',
name: 'locale',
message: 'The locale of project localization.',
validate: input => !!input,
default: 'en'
},
// ...
]
例如,返回问题数组的函数
// prompts.js
// pass `package.json` of project to function argument
module.exports = pkg => {
const prompts = [
{
type: 'input',
name: 'locale',
message: 'The locale of project localization.',
validate: input => !!input,
default: 'en'
}
]
// add dynamically prompt
if ('@vue/cli-plugin-eslint' in (pkg.devDependencies || {})) {
prompts.push({
type: 'confirm',
name: 'useESLintPluginVueI18n',
message: 'Use ESLint plugin for Vue I18n ?'
})
}
return prompts
}
解析后的答案对象将作为选项传递给插件的生成器。
或者,用户可以跳过提示,并通过命令行直接传递选项来初始化插件,例如
vue invoke my-plugin --mode awesome
提示可以具有不同的类型,但在 CLI 中最广泛使用的是checkbox
和confirm
。让我们添加一个confirm
提示,然后在插件生成器中使用它来为模板渲染创建条件。
// prompts.js
module.exports = [
{
name: `addExampleRoutes`,
type: 'confirm',
message: 'Add example routes?',
default: false
}
]
在插件调用时,将提示用户有关示例路由的问题,默认答案为No
。
如果要在生成器中使用用户选择的结果,可以使用提示名称访问它。我们可以对generator/index.js
进行修改
if (options.addExampleRoutes) {
api.render('./template', {
...options
})
}
现在,只有在用户同意创建示例路由时才会渲染模板。
在本地安装插件
在开发插件时,需要测试它并检查它在使用 Vue CLI 的项目中如何本地运行。可以使用现有项目或专门为测试目的创建一个新项目
vue create test-app
要添加插件,请在项目的根文件夹中运行以下命令
npm install --save-dev file:/full/path/to/your/plugin
vue invoke <your-plugin-name>
每次更改插件时,都需要重复这些步骤。
添加插件的另一种方法是利用 Vue UI 的强大功能。可以使用以下命令运行它
vue ui
将在浏览器窗口的localhost:8000
上打开一个 UI。转到Vue Project Manager
选项卡
并在那里查找测试项目名称
单击应用程序名称,转到插件选项卡(它有一个拼图图标),然后单击页面右上角的Add new plugin
按钮。在新视图中,将看到一个通过 npm 访问的 Vue CLI 插件列表。页面底部还有一个Browse local plugin
按钮
单击它后,可以轻松搜索插件并将其添加到项目中。之后,你将能够在插件列表中看到它,并通过简单地单击Refresh
图标来应用对插件所做的所有更改
UI 集成
Vue CLI 具有一个很棒的 UI 工具,允许用户使用漂亮的图形界面来搭建和管理项目。Vue CLI 插件可以集成到此界面中。UI 为 CLI 插件提供了额外的功能
- 可以从 UI 中直接运行 npm 任务,包括特定于插件的任务;
- 可以显示插件的自定义配置。例如,vue-cli-plugin-apollo为 Apollo 服务器提供了以下配置屏幕
- 在创建项目时,可以以视觉方式显示提示
- 如果要支持多种语言,可以为插件添加本地化
- 可以使插件在 Vue UI 搜索中可发现
所有与 Vue UI 相关的逻辑都应该放在根文件夹中的ui.js
文件中,或者放在ui/index.js
中。该文件应该导出一个函数,该函数将 api 对象作为参数获取
module.exports = api => {
// Use the API here...
}
在 UI 中增强任务
Vue CLI 插件不仅允许您向项目添加新的 npm 任务 通过 Generator,还允许您在 Vue UI 中为它们创建视图。当您希望直接从 UI 运行任务并在那里查看其输出时,这非常有用。
让我们将使用 Generator 创建的 greet
任务添加到 UI 中。任务是从项目 package.json
文件中的 scripts
字段生成的。您可以使用 api.describeTask
方法为任务添加额外的信息和钩子。让我们为我们的任务提供一些额外的信息
module.exports = api => {
api.describeTask({
match: /greet/,
description: 'Prints a greeting in the console',
link: 'https://cli.vuejs.ac.cn/dev-guide/plugin-dev.html#core-concepts'
})
}
现在,如果您在 Vue UI 中浏览您的项目,您会发现您的任务已添加到 Tasks
部分。您可以看到任务的名称、提供的描述、一个指向提供的 URL 的链接图标,以及一个用于显示任务输出的输出屏幕
显示配置屏幕
有时您的项目可能具有针对不同功能或库的自定义配置文件。使用 Vue CLI 插件,您可以在 Vue UI 中显示此配置,更改它并保存(保存将更改项目中相应的配置文件)。默认情况下,Vue CLI 项目有一个代表 vue.config.js
设置的主配置屏幕。如果您将 ESLint 包含到您的项目中,您还会看到一个 ESLint 配置屏幕
让我们为我们的插件构建一个自定义配置。首先,在您将插件添加到现有项目后,应该有一个包含此自定义配置的文件。这意味着您需要将此文件添加到 模板步骤 上的 template
文件夹中。
默认情况下,配置 UI 可能会读取和写入以下文件类型:json
、yaml
、js
、package
。让我们将我们的新文件命名为 myConfig.js
并将其放置在 template
文件夹的根目录中
.
└── generator
├── index.js
└── template
├── myConfig.js
└── src
├── layouts
├── pages
└── router.js
现在您需要向此文件添加一些实际的配置
// myConfig.js
module.exports = {
color: 'black'
}
在您的插件被调用后,myConfig.js
文件将在项目根目录中被渲染。现在让我们使用 ui.js
文件中的 api.describeConfig
方法添加一个新的配置屏幕
首先,您需要传递一些信息
// ui.js
api.describeConfig({
// Unique ID for the config
id: 'org.ktsn.vue-auto-routing.config',
// Displayed name
name: 'Greeting configuration',
// Shown below the name
description: 'This config defines the color of the greeting printed',
// "More info" link
link: 'https://github.com/ktsn/vue-cli-plugin-auto-routing#readme'
})
警告
确保正确地命名空间 ID,因为它必须在所有插件中都是唯一的。建议使用 反向域名表示法
配置徽标
您还可以为您的配置选择一个图标。它可以是 Material 图标 代码或自定义图像(请参阅 公共静态文件)。
// ui.js
api.describeConfig({
/* ... */
// Config icon
icon: 'color_lens'
})
如果您没有指定图标,则将显示插件徽标(如果有)(请参阅 徽标)。
配置文件
现在您需要将您的配置文件提供给 UI:这样您就可以读取其内容并保存对它的更改。您需要为您的配置文件选择一个名称,选择其格式并提供一个指向该文件的路径
api.describeConfig({
// other config properties
files: {
myConfig: {
js: ['myConfig.js']
}
}
})
可以提供多个文件。例如,如果我们有 myConfig.json
,我们可以使用 json: ['myConfig.json']
属性提供它。顺序很重要:列表中的第一个文件名将用于在文件不存在时创建配置文件。
显示配置提示
我们希望在配置屏幕上显示一个用于颜色属性的输入字段。为此,我们需要一个 onRead
钩子,它将返回要显示的提示列表
api.describeConfig({
// other config properties
onRead: ({ data }) => ({
prompts: [
{
name: `color`,
type: 'input',
message: 'Define the color for greeting message',
value: 'white'
}
]
})
})
在上面的示例中,我们使用 white
的值指定了输入提示。这就是我们的配置屏幕在提供所有上述设置后的样子
现在让我们用配置文件中的属性替换硬编码的 white
值。在 onRead
钩子中,data
对象包含每个配置文件内容的 JSON 结果。在我们的例子中,myConfig.js
的内容是
// myConfig.js
module.exports = {
color: 'black'
}
因此,data
对象将是
{
// File
myConfig: {
// File data
color: 'black'
}
}
很容易看出我们需要 data.myConfig.color
属性。让我们更改 onRead
钩子
// ui.js
onRead: ({ data }) => ({
prompts: [
{
name: `color`,
type: 'input',
message: 'Define the color for greeting message',
value: data.myConfig && data.myConfig.color
}
]
}),
提示
请注意,如果在加载屏幕时配置文件不存在,则 myConfig
可能未定义。
您可以看到,在配置屏幕上,white
被替换为 black
。
如果配置文件不存在,我们还可以提供一个默认值
// ui.js
onRead: ({ data }) => ({
prompts: [
{
name: `color`,
type: 'input',
message: 'Define the color for greeting message',
value: data.myConfig && data.myConfig.color,
default: 'black',
}
]
}),
保存配置更改
我们只是读取了 myConfig.js
的内容并在配置屏幕上使用了它。现在让我们尝试将颜色输入字段中所做的任何更改保存到文件中。我们可以使用 onWrite
钩子来完成此操作
// ui.js
api.describeConfig({
/* ... */
onWrite: ({ prompts, api }) => {
// ...
}
})
onWrite
钩子可以接受很多 参数,但我们只需要其中的两个:prompts
和 api
。第一个是当前提示运行时对象 - 我们将从中获取提示 ID 并使用此 ID 检索答案。为了检索答案,我们将使用 api
中的 async getAnswer()
方法
// ui.js
async onWrite({ api, prompts }) {
const result = {}
for (const prompt of prompts) {
result[`${prompt.id}`] = await api.getAnswer(prompt.id)
}
api.setData('myConfig', result)
}
现在,如果您尝试在配置屏幕上将颜色输入字段中的值从 black
更改为 red
并按下 保存更改
,您会发现项目中的 myConfig.js
文件也已更改
// myConfig.js
module.exports = {
color: 'red'
}
显示提示
如果您愿意,您也可以在 Vue UI 中显示 提示。当您通过 UI 安装插件时,提示将在插件调用步骤中显示。
您可以使用其他属性扩展 inquirer 对象。它们是可选的,仅供 UI 使用
// prompts.js
module.exports = [
{
// basic prompt properties
name: `addExampleRoutes`,
type: 'confirm',
message: 'Add example routes?',
default: false,
// UI-related prompt properties
group: 'Strongly recommended',
description: 'Adds example pages, layouts and correct router config',
link:
'https://github.com/ktsn/vue-cli-plugin-auto-routing/#vue-cli-plugin-auto-routing'
}
]
结果,您将在插件调用时看到此屏幕
徽标
您可以在将发布到 npm 的文件夹的根目录中放置一个 logo.png
文件。它将在几个地方显示
- 搜索要安装的插件时
- 在已安装的插件列表中
- 在配置列表中(默认情况下)
- 在增强任务的任务列表中(默认情况下)
徽标应该是正方形的非透明图像(理想情况下为 84x84)。
将插件发布到 npm
要发布您的插件,您需要注册一个 npmjs.com 帐户,并且您应该全局安装了 npm
。如果这是您的第一个 npm 模块,请运行
npm login
输入您的用户名和密码。这将存储凭据,因此您不必为每次发布都输入它。
提示
在发布插件之前,请确保您为其选择了正确的名称!命名约定为 vue-cli-plugin-<name>
。有关更多信息,请查看 可发现性 部分
要发布插件,请转到插件根文件夹并在终端中运行以下命令
npm publish
发布成功后,您应该能够使用 vue add <plugin-name>
命令将您的插件添加到使用 Vue CLI 创建的项目中。