Skip to content

Commit d595ada

Browse files
committed
refactor: adjust mode loading order
BREAKING CHANGE: PluginAPI.setMode() has been removed. Instead, for a plugin to sepcify the default mode for a registered command, the plugins should expose `module.exports.defaultModes` in the form of `{ [commandName]: mode }`. close #959
1 parent 51c8090 commit d595ada

File tree

14 files changed

+174
-113
lines changed

14 files changed

+174
-113
lines changed

docs/plugin-dev.md

+22-24
Original file line numberDiff line numberDiff line change
@@ -72,54 +72,52 @@ module.exports = (api, projectOptions) => {
7272
}
7373
```
7474

75-
#### Environment Variables in Service Plugins
75+
#### Specifying Mode for Commands
7676

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:
77+
> Note: the way plugins set modes has been changed in beta.10.
78+
79+
If a plugin-registered command needs to run in a specific default mode,
80+
the plugin needs to expose it via `module.exports.defaultModes` in the form
81+
of `{ [commandName]: mode }`:
7882

7983
``` js
8084
module.exports = api => {
81-
process.env.NODE_ENV // may not be resolved yet
82-
8385
api.registerCommand('build', () => {
84-
api.setMode('production')
86+
// ...
8587
})
8688
}
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:
9089

91-
``` js
92-
module.exports = api => {
93-
api.configureWebpack(config => {
94-
if (process.env.NODE_ENV === 'production') {
95-
// ...
96-
}
97-
})
90+
module.exports.defaultModes = {
91+
build: 'production'
9892
}
9993
```
10094

95+
This is because the command's expected mode needs to be known before loading environment variables, which in turn needs to happen before loading user options / applying the plugins.
96+
10197
#### Resolving Webpack Config in Plugins
10298

10399
A plugin can retrieve the resolved webpack config by calling `api.resolveWebpackConfig()`. Every call generates a fresh webpack config which can be further mutated as needed:
104100

105101
``` js
106-
api.registerCommand('my-build', args => {
107-
// make sure to set mode and load env variables
108-
api.setMode('production')
102+
module.exports = api => {
103+
api.registerCommand('my-build', args => {
104+
const configA = api.resolveWebpackConfig()
105+
const configB = api.resolveWebpackConfig()
109106

110-
const configA = api.resolveWebpackConfig()
111-
const configB = api.resolveWebpackConfig()
107+
// mutate configA and configB for different purposes...
108+
})
109+
}
112110

113-
// mutate configA and configB for different purposes...
114-
})
111+
// make sure to specify the default mode for correct env variables
112+
module.exports.defaultModes = {
113+
'my-build': 'production'
114+
}
115115
```
116116

117117
Alternatively, a plugin can also obtain a fresh [chainable config](https://github.com/mozilla-neutrino/webpack-chain) by calling `api.resolveChainableWebpackConfig()`:
118118

119119
``` js
120120
api.registerCommand('my-build', args => {
121-
api.setMode('production')
122-
123121
const configA = api.resolveChainableWebpackConfig()
124122
const configB = api.resolveChainableWebpackConfig()
125123

packages/@vue/cli-plugin-e2e-cypress/index.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ module.exports = (api, options) => {
1616

1717
const serverPromise = args.url
1818
? Promise.resolve({ url: args.url })
19-
: api.service.run('serve', { mode: args.mode || 'production' })
19+
: api.service.run('serve')
2020

2121
return serverPromise.then(({ url, server }) => {
2222
const { info } = require('@vue/cli-shared-utils')
@@ -71,3 +71,8 @@ module.exports = (api, options) => {
7171
chalk.yellow(`https://docs.cypress.io/guides/guides/command-line.html#cypress-open`)
7272
}, (args, rawArgs) => run('open', args, rawArgs))
7373
}
74+
75+
module.exports.defaultModes = {
76+
e2e: 'production',
77+
'e2e:open': 'production'
78+
}

packages/@vue/cli-plugin-e2e-nightwatch/index.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ module.exports = (api, options) => {
2727

2828
const serverPromise = args.url
2929
? Promise.resolve({ url: args.url })
30-
: api.service.run('serve', { mode: args.mode || 'production' })
30+
: api.service.run('serve')
3131

3232
return serverPromise.then(({ server, url }) => {
3333
// expose dev server url to tests
@@ -68,3 +68,7 @@ module.exports = (api, options) => {
6868
})
6969
})
7070
}
71+
72+
module.exports.defaultModes = {
73+
e2e: 'production'
74+
}

packages/@vue/cli-plugin-typescript/__tests__/tsPluginBabel.spec.js

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ test('using correct loader', () => {
1212
]
1313
})
1414

15+
service.init()
1516
const config = service.resolveWebpackConfig()
1617
const rule = config.module.rules.find(rule => rule.test.test('foo.ts'))
1718
expect(rule.use[0].loader).toMatch('cache-loader')

packages/@vue/cli-plugin-unit-jest/index.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ module.exports = api => {
99
`All jest command line options are supported.\n` +
1010
`See https://facebook.github.io/jest/docs/en/cli.html for more details.`
1111
}, (args, rawArgv) => {
12-
api.setMode('test')
1312
// for @vue/babel-preset-app
1413
process.env.VUE_CLI_BABEL_TARGET_NODE = true
1514
process.env.VUE_CLI_BABEL_TRANSPILE_MODULES = true
@@ -33,3 +32,7 @@ module.exports = api => {
3332
})
3433
})
3534
}
35+
36+
module.exports.defaultModes = {
37+
test: 'test'
38+
}

packages/@vue/cli-plugin-unit-mocha/index.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ module.exports = api => {
4040
`http://zinserjan.github.io/mocha-webpack/docs/installation/cli-usage.html`
4141
)
4242
}, (args, rawArgv) => {
43-
api.setMode('test')
4443
// for @vue/babel-preset-app
4544
process.env.VUE_CLI_BABEL_TARGET_NODE = true
4645
// start runner
@@ -74,3 +73,7 @@ module.exports = api => {
7473
})
7574
})
7675
}
76+
77+
module.exports.defaultModes = {
78+
test: 'test'
79+
}

packages/@vue/cli-service/__tests__/Service.spec.js

+48-34
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,16 @@ const mockPkg = json => {
1010
fs.writeFileSync('/package.json', JSON.stringify(json, null, 2))
1111
}
1212

13-
const createMockService = (plugins = []) => new Service('/', {
14-
plugins,
15-
useBuiltIn: false
16-
})
13+
const createMockService = (plugins = [], init = true) => {
14+
const service = new Service('/', {
15+
plugins,
16+
useBuiltIn: false
17+
})
18+
if (init) {
19+
service.init()
20+
}
21+
return service
22+
}
1723

1824
beforeEach(() => {
1925
mockPkg({})
@@ -79,36 +85,6 @@ test('load project options from vue.config.js', () => {
7985
expect(service.projectOptions.lintOnSave).toBe(false)
8086
})
8187

82-
test('api: setMode', () => {
83-
fs.writeFileSync('/.env.foo', `FOO=5\nBAR=6`)
84-
fs.writeFileSync('/.env.foo.local', `FOO=7\nBAZ=8`)
85-
86-
createMockService([{
87-
id: 'test-setMode',
88-
apply: api => {
89-
api.setMode('foo')
90-
}
91-
}])
92-
expect(process.env.FOO).toBe('7')
93-
expect(process.env.BAR).toBe('6')
94-
expect(process.env.BAZ).toBe('8')
95-
expect(process.env.VUE_CLI_MODE).toBe('foo')
96-
// for NODE_ENV & BABEL_ENV
97-
// any mode that is not test or production defaults to development
98-
expect(process.env.NODE_ENV).toBe('development')
99-
expect(process.env.BABEL_ENV).toBe('development')
100-
101-
createMockService([{
102-
id: 'test-setMode',
103-
apply: api => {
104-
api.setMode('test')
105-
}
106-
}])
107-
expect(process.env.VUE_CLI_MODE).toBe('test')
108-
expect(process.env.NODE_ENV).toBe('test')
109-
expect(process.env.BABEL_ENV).toBe('test')
110-
})
111-
11288
test('api: registerCommand', () => {
11389
let args
11490
const service = createMockService([{
@@ -124,6 +100,44 @@ test('api: registerCommand', () => {
124100
expect(args).toEqual({ _: [], n: 1 })
125101
})
126102

103+
test('api: defaultModes', () => {
104+
fs.writeFileSync('/.env.foo', `FOO=5\nBAR=6`)
105+
fs.writeFileSync('/.env.foo.local', `FOO=7\nBAZ=8`)
106+
107+
const plugin1 = {
108+
id: 'test-defaultModes',
109+
apply: api => {
110+
expect(process.env.FOO).toBe('7')
111+
expect(process.env.BAR).toBe('6')
112+
expect(process.env.BAZ).toBe('8')
113+
// for NODE_ENV & BABEL_ENV
114+
// any mode that is not test or production defaults to development
115+
expect(process.env.NODE_ENV).toBe('development')
116+
expect(process.env.BABEL_ENV).toBe('development')
117+
api.registerCommand('foo', () => {})
118+
}
119+
}
120+
plugin1.apply.defaultModes = {
121+
foo: 'foo'
122+
}
123+
124+
createMockService([plugin1], false /* init */).run('foo')
125+
126+
const plugin2 = {
127+
id: 'test-defaultModes',
128+
apply: api => {
129+
expect(process.env.NODE_ENV).toBe('test')
130+
expect(process.env.BABEL_ENV).toBe('test')
131+
api.registerCommand('test', () => {})
132+
}
133+
}
134+
plugin2.apply.defaultModes = {
135+
test: 'test'
136+
}
137+
138+
createMockService([plugin2], false /* init */).run('test')
139+
})
140+
127141
test('api: chainWebpack', () => {
128142
const service = createMockService([{
129143
id: 'test',

packages/@vue/cli-service/__tests__/css.spec.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ const LOADERS = {
1414
const genConfig = (pkg = {}, env) => {
1515
const prevEnv = process.env.NODE_ENV
1616
if (env) process.env.NODE_ENV = env
17-
const config = new Service('/', { pkg }).resolveWebpackConfig()
17+
const service = new Service('/', { pkg })
18+
service.init()
19+
const config = service.resolveWebpackConfig()
1820
process.env.NODE_ENV = prevEnv
1921
return config
2022
}

packages/@vue/cli-service/lib/PluginAPI.js

+6-21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
const path = require('path')
22
const { matchesPluginId } = require('@vue/cli-shared-utils')
33

4+
// Note: if a plugin-registered command needs to run in a specific default mode,
5+
// the plugin needs to expose it via `module.exports.defaultModes` in the form
6+
// of { [commandName]: mode }. This is because the command mode needs to be
7+
// known and applied before loading user options / applying plugins.
8+
49
class PluginAPI {
510
/**
611
* @param {string} id - Id of the plugin.
@@ -31,25 +36,6 @@ class PluginAPI {
3136
return this.service.plugins.some(p => matchesPluginId(id, p.id))
3237
}
3338

34-
/**
35-
* Set project mode and resolve env variables for that mode.
36-
* this should be called by any registered command as early as possible, and
37-
* should be called only once per command.
38-
*
39-
* @param {string} mode
40-
*/
41-
setMode (mode) {
42-
process.env.VUE_CLI_MODE = mode
43-
// by default, NODE_ENV and BABEL_ENV are set to "development" unless mode
44-
// is production or test. However this can be overwritten in .env files.
45-
process.env.NODE_ENV = process.env.BABEL_ENV =
46-
(mode === 'production' || mode === 'test')
47-
? mode
48-
: 'development'
49-
// load .env files based on mode
50-
this.service.loadEnv(mode)
51-
}
52-
5339
/**
5440
* Register a command that will become available as `vue-cli-service [name]`.
5541
*
@@ -68,7 +54,7 @@ class PluginAPI {
6854
fn = opts
6955
opts = null
7056
}
71-
this.service.commands[name] = { fn, opts }
57+
this.service.commands[name] = { fn, opts: opts || {}}
7258
}
7359

7460
/**
@@ -108,7 +94,6 @@ class PluginAPI {
10894

10995
/**
11096
* Resolve the final raw webpack config, that will be passed to webpack.
111-
* Typically, you should call `setMode` before calling this.
11297
*
11398
* @param {ChainableWebpackConfig} [chainableConfig]
11499
* @return {object} Raw webpack config.

0 commit comments

Comments
 (0)