Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Vue CLI plug-in development guide


May 07, 2021 Vue CLI


Table of contents


Core concepts

There are two main parts of the system:

  • @vue/cli: globally installed, exposed vue create slt;app>
  • @vue/cli-service: partial installation, exposing the vue-cli-service command.

Both apply plug-in-based architectures.

Creator

Creator is a class that is created when vue create is called. Responsible for preference conversations, calling generators, and installing dependencies.

Service

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.

CLI plug-in

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-in

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:

  • A Plugin API instance
  • An object that contains the item-.js options specified in the vue.config, or the vue field within package.json.

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`
  })
}

Specify a mode for the command

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.

Resolve the webpack configuration in the plug-in

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()
})

Custom options for third-party plug-ins

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.

Generator

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:

  • During the initial creation of a project, if the CLI plug-in is installed as part of the project creation preset.
  • The plug-in is installed when it is called independently by vue invoke after the project has been created.

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:

  1. A GeneratorAPI instance:
  2. The generator option for this plug-in. T hese options are parsed during the project creation conversation or loaded from a preset saved in ./.vuerc. F or example, if you save it as follows: "presets": "foo": "plugins": "@vue/cli-plugin-foo": "option": "bar @vue" . For a third-party plug-in, this option resolves from the command-line parameters of the conversation or when the user performs vue invoke (see Conversations for third-party plug-ins).
  3. The entire preset (presets.foo) will be passed in as a third argument.

Example:

module.exports = (api, options, rootOptions) => {
  // 修改 `package.json` 里的字段
  api.extendPackage({
    scripts: {
      test: 'vue-cli-service test'
    }
  })

  // 复制并用 ejs 渲染 `./template` 内所有的文件
  api.render('./template')

  if (options.foo) {
    // 有条件地生成文件
  }
}

Generator's template processing

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 %>

The extreme case of the file name

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

Prompts

Conversations for built-in plug-ins

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
      }
    }
  })
}

Conversations with third-party plug-ins

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

Publish the plug-in

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:

  • Discovered @vue/cli-service;
  • Searched by other developers;
  • Installed by vue add;name> or vue invoke-lt;name>

Considerations for developing core plug-ins

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:

  1. This package is always present in the root and node_modules of the repository, so we don't have to reinstall them every time we test them.
  2. yarn.lock maintains consistency, so CI programs can make better use of the cache.