May 07, 2021 Vue CLI
There are two main parts of the system:
Both apply plug-in-based architectures.
Creator is a class that is created when vue create is called. Responsible for preference conversations, calling generators, and installing dependencies.
Service is called vue-cli-service . C lasses created when args. Responsible for managing internal webpack configurations, exposure services, commands to build projects, and more.
The CLI plug-in is an @vue that adds additional features to the new /cli project. It should always contain a service plug-in as its primary export, and optionally include a Generator and a Prompt file.
The directory structure of a typical CLI plug-in looks like this:
.
├── README.md
├── generator.js # generator (可选)
├── prompts.js # prompt 文件 (可选)
├── index.js # service 插件
└── package.json
Service plug-ins load automatically when a Service instance is created -- for example, every time a vue-cli-service command is called in a project.
Note that the concept of "service plug-in" discussed here is narrower than the "CLI plug-in" published as an npm package. The former involves a module @vue/cli-service at initialization and is often part of the latter.
In addition, @vue built-in commands and configuration modules for the service/cli-service are all implemented with the service plug-in.
A service plug-in should export a function that accepts two arguments:
This API allows the service plug-in to extend/modify the internal webpack configuration for different environments and inject additional commands into the vue-cli-service. For example:
module.exports = (api, projectOptions) => {
api.chainWebpack(webpackConfig => {
// 通过 webpack-chain 修改 webpack 配置
})
api.configureWebpack(webpackConfig => {
// 修改 webpack 配置
// 或返回通过 webpack-merge 合并的配置对象
})
api.registerCommand('test', args => {
// 注册 `vue-cli-service test`
})
}
Note: The way the plug-in sets the mode has changed since beta.10.
If a registered plug-in command needs to be run in a specific default mode, the plug-in needs to be exposed by module.exports.defaultModes in the form of .
module.exports = api => {
api.registerCommand('build', () => {
// ...
})
}
module.exports.defaultModes = {
build: 'production'
}
This is because we need to know the expected pattern of the command before loading the environment variables, so we need to load the user options/app plug-in ahead of time.
A plug-in can get back the parsed webpack configuration by calling api.resolveWebpackConfig(). Each call generates a new webpack configuration for further modification when needed.
module.exports = api => {
api.registerCommand('my-build', args => {
const configA = api.resolveWebpackConfig()
const configB = api.resolveWebpackConfig()
// 针对不同的目的修改 `configA` 和 `configB`...
})
}
// 请确保为正确的环境变量指定默认模式
module.exports.defaultModes = {
'my-build': 'production'
}
Alternatively, a plug-in can obtain a newly generated chain configuration by calling api.resolveChainableWebpackConfig():
api.registerCommand('my-build', args => {
const configA = api.resolveChainableWebpackConfig()
const configB = api.resolveChainableWebpackConfig()
// 针对不同的目的链式修改 `configA` 和 `configB`...
const finalConfigA = configA.toConfig()
const finalConfigB = configB.toConfig()
})
The export of the vue.config .js will be validated by a schema to avoid pen errors and incorrect configuration values. H owever, a third-party plug-in still allows users to configure their behavior through the pluginOptions field. For example, for the following vue.config .js:
module.exports = {
pluginOptions: {
foo: { /* ... */ }
}
}
The third-party plug-in can read projectOptions.pluginOptions.foo to make conditional decision configurations.
A CLI plug-in published as an npm package can contain a generator .js or generator/index .js file. The generator within the plug-in will be called in two scenarios:
The Generator API here allows a generator to inject additional dependencies or fields into package.json and add files to the project.
A generator should export a function that receives three arguments:
Example:
module.exports = (api, options, rootOptions) => {
// 修改 `package.json` 里的字段
api.extendPackage({
scripts: {
test: 'vue-cli-service test'
}
})
// 复制并用 ejs 渲染 `./template` 内所有的文件
api.render('./template')
if (options.foo) {
// 有条件地生成文件
}
}
When you call api.render ('./template'), the generator renders the files in the ./template using EJS (as opposed to the file path in the generator)
In addition, you can use YAML pre-meta-information to inherit and replace parts of an existing template file:
---
extend: '@vue/cli-service/generator/template/src/App.vue'
replace: !!js/regexp /<script>[^]*?<\/script>/
---
<script>
export default {
// 替换默认脚本
}
</script>
You can also complete multiple replacements, and of course you need to wrap the strings you want to replace in blocks of .lt;%-REPLACE%?gt; and .lt;%?END_REPLACE?END_REPLACE?gt;
---
extend: '@vue/cli-service/generator/template/src/App.vue'
replace:
- !!js/regexp /欢迎来到你的 Vue\.js 应用/
- !!js/regexp /<script>[^]*?<\/script>/
---
<%# REPLACE %>
替换欢迎信息
<%# END_REPLACE %>
<%# REPLACE %>
<script>
export default {
// 替换默认脚本
}
</script>
<%# END_REPLACE %>
If you want to render a template file that starts with a point (for example, .env), you need to follow a special naming convention because a file that starts with a point is ignored when the plug-in is published to npm:
# 以点开头的模板需要使用下划线取代那个点:
/generator/template/_env
# 调用 api.render('./template') 会在项目目录中渲染成为:
.env
It also means that when you want to render a file that starts with the following dashes, you also need to follow a special naming convention:
# 这种模板需要使用两个下划线来取代单个下划线:
/generator/template/__variables.scss
# 调用 api.render('./template') 会在项目目录中渲染成为:
_variables.scss
Only built-in plug-ins can customize the initialized conversations when creating new projects, and these conversation modules are placed inside the @vue/cli package.
A conversation module should export a function that receives a PromptModule API instance. The underlying layer of these conversations is presented using inquirer:
module.exports = api => {
// 一个特性对象应该是一个有效的 inquirer 选择对象
api.injectFeature({
name: 'Some great feature',
value: 'my-feature'
})
// injectPrompt 期望接收一个有效的 inquirer 对话对象
api.injectPrompt({
name: 'someFlag',
// 确认对话只在用户已经选取了特性的时候展示
when: answers => answers.features.include('my-feature'),
message: 'Do you want to turn on flag foo?',
type: 'confirm'
})
// 当所有的对话都完成之后,将你的插件注入到
// 即将传递给 Generator 的 options 中
api.onPromptComplete((answers, options) => {
if (answers.features.includes('my-feature')) {
options.plugins['vue-cli-plugin-my-feature'] = {
someFlag: answers.someFlag
}
}
})
}
Third-party plug-ins are typically installed manually after a project is created, and users initialize the plug-in by calling vue invoke. I f the plug-in contains a .js in its root, it will be used when the plug-in is initialized. T his file should export an array of .js for the inquirer and the problem. These parsed answer objects are passed as options to the plug-in's generator.
Alternatively, the user can directly initialize the plug-in by skipping the conversation by passing options on the command line, such as:
vue invoke my-plugin --mode awesome
In order for a CLI plug-in to be available to other developers, you must follow the naming conventions of vue-cli-plugin-lt;name?gt; After the plug-in follows the naming convention, it can:
Attention
This section is for built-in plug-in work inside the vuejs/vue-cli repository only.
A plug-in with a generator that injects additional dependencies into the repository (e.g. chai is injected through @vue/cli-plugin-unit-mocha/generator/index.js should include these dependencies in its own devDependencies field. This ensures that: