Skip to content

Commit e597d12

Browse files
committed
docs: improve documentation for plugin development
1 parent 920d8fa commit e597d12

File tree

5 files changed

+332
-51
lines changed

5 files changed

+332
-51
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
> This is the branch for `@vue/cli` 3.0.
44
5-
**Status: alpha**
5+
## Status: alpha
6+
7+
Certain combinations of plugins may not work properly, and things may break until we reach beta phase. Do not use in production yet unless you are adventurous.
68

79
## Install
810

docs/Plugin.md

Lines changed: 111 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
# Plugin Development Guide
22

3-
#### Important Development Note
4-
5-
A plugin with a generator that injects additional dependencies other than packages in this repo (e.g. `chai` is injected by `@vue/cli-plugin-unit-mocha/generator/index.js`) should have those dependencies listed in its own `devDependencies` field. This ensures that:
6-
7-
1. the package always exist in this repo's root `node_modules` so that we don't have to reinstall them on every test.
8-
9-
2. `yarn.lock` stays consistent so that CI can better use it for inferring caching behavior.
10-
113
## Core Concepts
124

5+
- [Creator](#creator)
6+
- [Service](#service)
7+
- [CLI Plugin](#cli-plugin)
8+
- [Service Plugin](#service-plugin)
9+
- [Generator](#generator)
10+
- [Prompts](#prompts)
11+
1312
There are two major parts of the system:
1413

1514
- `@vue/cli`: globally installed, exposes the `vue create <app>` command;
@@ -25,17 +24,36 @@ Both utilize a plugin-based architecture.
2524

2625
[Service][service-class] is the class created when invoking `vue-cli-service <command> [...args]`. Responsible for managing the internal webpack configuration, and exposes commands for serving and building the project.
2726

28-
### Plugin
27+
### CLI Plugin
28+
29+
A CLI plugin is an npm package that can add additional features to a `@vue/cli` project. It should always contain a [Service Plugin](#service-plugin) as its main export, and can optionally contain a [Generator](#generator) and a [Prompt File](#prompts-for-3rd-party-plugins).
30+
31+
A typical CLI plugin's folder structure looks like the following:
32+
33+
```
34+
.
35+
├── README.md
36+
├── generator.js # generator (optional)
37+
├── prompts.js # prompts file (optional)
38+
├── index.js # service plugin
39+
└── package.json
40+
```
41+
42+
### Service Plugin
43+
44+
Service plugins are loaded automatically when a Service instance is created - i.e. every time the `vue-cli-service` command is invoked inside a project.
2945

30-
Plugins are locally installed into the project as dependencies. `@vue/cli-service`'s [built-in commands][commands] and [config modules][config] are also all implemented as plugins. This repo also contains a number of plugins that are published as individual packages.
46+
Note the concept of a "service plugin" we are discussing here is narrower than that of a "CLI plugin", which is published as an npm package. The former only refers to a module that will be loaded by `@vue/cli-service` when it's initialized, and is usually a part of the latter.
3147

32-
A plugin should export a function which receives two arguments:
48+
In addition, `@vue/cli-service`'s [built-in commands][commands] and [config modules][config] are also all implemented as service plugins.
49+
50+
A service plugin should export a function which receives two arguments:
3351

3452
- A [PluginAPI][plugin-api] instance
3553

36-
- Project local options specified in `vue.config.js`, or in the `"vue-cli"` field in `package.json`.
54+
- An object containing project local options specified in `vue.config.js`, or in the `"vue-cli"` field in `package.json`.
3755

38-
The API allows plugins to extend/modify the internal webpack config for different environments and inject additional commands to `vue-cli-service`. Example:
56+
The API allows service plugins to extend/modify the internal webpack config for different environments and inject additional commands to `vue-cli-service`. Example:
3957

4058
``` js
4159
module.exports = (api, projectOptions) => {
@@ -54,29 +72,79 @@ module.exports = (api, projectOptions) => {
5472
}
5573
```
5674

75+
#### Environment Variables in Service Plugins
76+
77+
An important thing to note about env variables is knowing when they are resolved. Typically, a command like `vue-cli-service serve` or `vue-cli-service build` will always call `api.setMode()` as the first thing it does. However, this also means those env variables may not yet be available when a service plugin is invoked:
78+
79+
``` js
80+
module.exports = api => {
81+
process.env.NODE_ENV // may not be resolved yet
82+
83+
api.regsiterCommand('build', () => {
84+
api.setMode('production')
85+
})
86+
}
87+
```
88+
89+
Instead, it's safer to rely on env variables in `configureWebpack` or `chainWebpack` functions, which are called lazily only when `api.resolveWebpackConfig()` is finally called:
90+
91+
``` js
92+
module.exports = api => {
93+
api.configureWebpack(config => {
94+
if (process.env.NODE_ENV === 'production') {
95+
// ...
96+
}
97+
})
98+
}
99+
```
100+
101+
#### Custom Options for 3rd Party Plugins
102+
103+
The exports from `vue.config.js` will be [validated against a schema](https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-service/lib/options.js#L3) to avoid typos and wrong config values. However, a 3rd party plugin can still allow the user to configure its behavior via the `pluginOptions` field. For example, with the following `vue.config.js`:
104+
105+
``` js
106+
module.exports = {
107+
pluginOptions: {
108+
foo: { /* ... */ }
109+
}
110+
}
111+
```
112+
113+
The 3rd party plugin can read `projectOptions.pluginOptions.foo` to determine conditional configurations.
114+
57115
### Generator
58116

59-
A plugin published as a package can also contain a `generator.js` or `generator/index.js` file. The generator inside a plugin will be invoked after the plugin is installed.
117+
A CLI plugin published as a package can contain a `generator.js` or `generator/index.js` file. The generator inside a plugin will be invoked in two possible scenarios:
118+
119+
- During a project's initial creation, if the CLI plugin is installed as part of the project creation preset.
120+
121+
- When the plugin is installed after project's creation and invoked individually via `vue invoke`.
60122

61123
The [GeneratorAPI][generator-api] allows a generator to inject additional dependencies or fields into `package.json` and add files to the project.
62124

63125
A generator should export a function which receives three arguments:
64126

65127
1. A `GeneratorAPI` instance;
66128

67-
2. The generator options for this plugin. These options are resolved during the prompt phase of project creation, or loaded from a saved `~/.vuerc`. For example, if the saved `~/.vuerc` looks like this:
129+
2. The generator options for this plugin. These options are resolved during the prompt phase of project creation, or loaded from a saved preset in `~/.vuerc`. For example, if the saved `~/.vuerc` looks like this:
68130

69131
``` json
70132
{
71-
"plugins": {
72-
"@vue/cli-plugin-foo": { "option": "bar" }
133+
"presets" : {
134+
"foo": {
135+
"plugins": {
136+
"@vue/cli-plugin-foo": { "option": "bar" }
137+
}
138+
}
73139
}
74140
}
75141
```
76142

77-
Then the plugin `@vue/cli-plugin-foo` will receive `{ option: 'bar' }` as its second argument.
143+
And if the user creates a project using the `foo` preset, then the generator of `@vue/cli-plugin-foo` will receive `{ option: 'bar' }` as its second argument.
144+
145+
For a 3rd party plugin, the options will be resolved from the prompts or command line arguments when the user executes `vue invoke` (see [Prompts for 3rd Party Plugins](#prompts-for-3rd-party-plugins)).
78146

79-
3. The entire `.vuerc` object will be passed as the third argument.
147+
3. The entire preset (`presets.foo`) will be passed as the third argument.
80148

81149
**Example:**
82150

@@ -98,9 +166,11 @@ module.exports = (api, options, rootOptions) => {
98166
}
99167
```
100168

101-
### Prompt Modules
169+
### Prompts
102170

103-
Currently, only built-in plugins have the ability to customize the prompts when creating a new project, and the prompt modules are located [inside the `@vue/cli` package][prompt-modules].
171+
#### Prompts for Built-in Plugins
172+
173+
Only built-in plugins have the ability to customize the initial prompts when creating a new project, and the prompt modules are located [inside the `@vue/cli` package][prompt-modules].
104174

105175
A prompt module should export a function that receives a [PromptModuleAPI][prompt-api] instance. The prompts are presented using [inquirer](https://github.com/SBoudrias/Inquirer.js) under the hood:
106176

@@ -133,6 +203,26 @@ module.exports = api => {
133203
}
134204
```
135205

206+
#### Prompts for 3rd Party Plugins
207+
208+
3rd party plugins are typically installed manually after a project is already created, and the user will initialize the plugin by calling `vue invoke`. If the plugin contains a `prompt.js` in its root directory, it will be used during invocation. The file should export an array of [Questions](https://github.com/SBoudrias/Inquirer.js#question) that will be handled by Inquirer.js. The resolved answers object will be passed to the plugin's generator as options.
209+
210+
Alternatively, the user can skip the prompts and directly initialize the plugin by passing options via the command line, e.g.:
211+
212+
``` sh
213+
vue invoke my-plugin --mode awesome
214+
```
215+
216+
## Note on Development of Core Plugins
217+
218+
> This section only applies if you are working on a built-in plugin inside this very repository.
219+
220+
A plugin with a generator that injects additional dependencies other than packages in this repo (e.g. `chai` is injected by `@vue/cli-plugin-unit-mocha/generator/index.js`) should have those dependencies listed in its own `devDependencies` field. This ensures that:
221+
222+
1. the package always exist in this repo's root `node_modules` so that we don't have to reinstall them on every test.
223+
224+
2. `yarn.lock` stays consistent so that CI can better use it for inferring caching behavior.
225+
136226
[creator-class]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli/lib/Creator.js
137227
[service-class]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-service/lib/Service.js
138228
[generator-api]: https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli/lib/GeneratorAPI.js

docs/README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,52 @@
11
# WIP
2+
3+
## Introduction
4+
5+
## The CLI
6+
7+
## Configuration
8+
9+
### Vue CLI options
10+
11+
### Modes and Environment Variables
12+
13+
### Webpack
14+
15+
- #### Basic Configuration
16+
17+
- #### Chaining
18+
19+
- #### Using Resolved Config as a File
20+
21+
### Babel
22+
23+
- link to: babel preset
24+
- link to: babel plugin
25+
26+
### CSS
27+
28+
- #### PostCSS
29+
30+
- #### CSS Modules
31+
32+
- #### Other Pre-Processors
33+
34+
### ESLint
35+
36+
- link to: eslint plugin
37+
38+
### TypeScript
39+
40+
- link to: typescript plugin
41+
42+
### Unit Testing
43+
44+
- #### Jest
45+
46+
- #### Mocha (via `mocha-webpack`)
47+
48+
### E2E Testing
49+
50+
- #### Cypress
51+
52+
- #### Nightwatch
Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,45 @@
11
const path = require('path')
22

3-
module.exports = class PluginAPI {
3+
class PluginAPI {
4+
/**
5+
* @param {string} id - Id of the plugin.
6+
* @param {Service} service - A vue-cli-service instance.
7+
*/
48
constructor (id, service) {
59
this.id = id
610
this.service = service
711
}
812

13+
/**
14+
* Resolve path for a project.
15+
*
16+
* @param {string} _path - Relative path from project root
17+
* @return {string} The resolved absolute path.
18+
*/
919
resolve (_path) {
1020
return path.resolve(this.service.context, _path)
1121
}
1222

23+
/**
24+
* Check if the project has a given plugin.
25+
*
26+
* @param {string} id - Plugin id, can omit the (@vue/|vue-)-cli-plugin- prefix
27+
* @return {boolean}
28+
*/
1329
hasPlugin (id) {
1430
const prefixRE = /^(@vue\/|vue-)cli-plugin-/
1531
return this.service.plugins.some(p => {
1632
return p.id === id || p.id.replace(prefixRE, '') === id
1733
})
1834
}
1935

20-
// set project mode.
21-
// this should be called by any registered command as early as possible.
36+
/**
37+
* Set project mode and resolve env variables for that mode.
38+
* this should be called by any registered command as early as possible, and
39+
* should be called only once per command.
40+
*
41+
* @param {string} mode
42+
*/
2243
setMode (mode) {
2344
process.env.VUE_CLI_MODE = mode
2445
// by default, NODE_ENV and BABEL_ENV are set to "development" unless mode
@@ -31,6 +52,19 @@ module.exports = class PluginAPI {
3152
this.service.loadEnv(mode)
3253
}
3354

55+
/**
56+
* Register a command that will become available as `vue-cli-service [name]`.
57+
*
58+
* @param {string} name
59+
* @param {object} [opts]
60+
* {
61+
* description: string,
62+
* usage: string,
63+
* options: { [string]: string }
64+
* }
65+
* @param {function} fn
66+
* (args: { [string]: string }, rawArgs: string[]) => ?Promise
67+
*/
3468
registerCommand (name, opts, fn) {
3569
if (typeof opts === 'function') {
3670
fn = opts
@@ -39,23 +73,63 @@ module.exports = class PluginAPI {
3973
this.service.commands[name] = { fn, opts }
4074
}
4175

76+
/**
77+
* Regsiter a function that will receive a chainable webpack config
78+
* the function is lazy and won't be called until `resolveWebpackConfig` is
79+
* called
80+
*
81+
* @param {function} fn
82+
*/
4283
chainWebpack (fn) {
4384
this.service.webpackChainFns.push(fn)
4485
}
4586

87+
/**
88+
* Regsiter
89+
* - a webpack configuration object that will be merged into the config
90+
* OR
91+
* - a function that will receive the raw webpack config.
92+
* the function can either mutate the config directly or return an object
93+
* that will be merged into the config.
94+
*
95+
* @param {object | function} fn
96+
*/
4697
configureWebpack (fn) {
4798
this.service.webpackRawConfigFns.push(fn)
4899
}
49100

101+
/**
102+
* Register a dev serve config function. It will receive the express `app`
103+
* instnace of the dev server.
104+
*
105+
* @param {function} fn
106+
*/
107+
configureDevServer (fn) {
108+
this.service.devServerConfigFns.push(fn)
109+
}
110+
111+
/**
112+
* Resolve the final raw webpack config, that will be passed to webpack.
113+
* Typically, you should call `setMode` before calling this.
114+
*
115+
* @return {object} Raw webpack config.
116+
*/
50117
resolveWebpackConfig () {
51118
return this.service.resolveWebpackConfig()
52119
}
53120

121+
/**
122+
* Resolve an intermediate chainable webpack config instance, which can be
123+
* further tweaked before generating the final raw webpack config.
124+
* You can call this multiple times to generate different branches of the
125+
* base webpack config.
126+
* See https://github.com/mozilla-neutrino/webpack-chain
127+
*
128+
* @return {ChainableWebpackConfig}
129+
*/
54130
resolveChainableWebpackConfig () {
55131
return this.service.resolveChainableWebpackConfig()
56132
}
57-
58-
configureDevServer (fn) {
59-
this.service.devServerConfigFns.push(fn)
60-
}
61133
}
134+
135+
module.exports = PluginAPI

0 commit comments

Comments
 (0)