Skip to content

Commit cd78376

Browse files
authored
feat!: turn on modern mode by default, and provide a --no-module option (#6416)
1 parent dd217b2 commit cd78376

File tree

13 files changed

+200
-74
lines changed

13 files changed

+200
-74
lines changed

packages/@vue/cli-plugin-babel/__tests__/transpileDependencies.spec.js

+33-7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ async function readVendorFile () {
1313
return project.read(`dist/js/${filename}`)
1414
}
1515

16+
async function readLegacyVendorFile () {
17+
const files = await fs.readdir(path.join(project.dir, 'dist/js'))
18+
const filename = files.find(f => /chunk-vendors-legacy\.[^.]+\.js$/.test(f))
19+
return project.read(`dist/js/${filename}`)
20+
}
21+
1622
beforeAll(async () => {
1723
project = await create('babel-transpile-deps', defaultPreset)
1824

@@ -39,6 +45,8 @@ beforeAll(async () => {
3945
let $packageJson = await project.read('package.json')
4046

4147
$packageJson = JSON.parse($packageJson)
48+
$packageJson.browserslist.push('ie 11') // to ensure arrow function transformation is enabled
49+
$packageJson.browserslist.push('safari 11') // to ensure optional chaining transformation is enabled
4250
$packageJson.dependencies['external-dep'] = '1.0.0'
4351
$packageJson.dependencies['@scope/external-dep'] = '1.0.0'
4452
$packageJson = JSON.stringify($packageJson)
@@ -70,7 +78,7 @@ afterAll(async () => {
7078

7179
test('dep from node_modules should not been transpiled by default', async () => {
7280
await project.run('vue-cli-service build')
73-
expect(await readVendorFile()).toMatch('() => "__TEST__"')
81+
expect(await readLegacyVendorFile()).toMatch('() => "__TEST__"')
7482
})
7583

7684
test('dep from node_modules should been transpiled when matched by transpileDependencies', async () => {
@@ -79,9 +87,9 @@ test('dep from node_modules should been transpiled when matched by transpileDepe
7987
`module.exports = { transpileDependencies: ['external-dep', '@scope/external-dep'] }`
8088
)
8189
await project.run('vue-cli-service build')
82-
expect(await readVendorFile()).toMatch('return "__TEST__"')
90+
expect(await readLegacyVendorFile()).toMatch('return "__TEST__"')
8391

84-
expect(await readVendorFile()).toMatch('return "__SCOPE_TEST__"')
92+
expect(await readLegacyVendorFile()).toMatch('return "__SCOPE_TEST__"')
8593
})
8694

8795
test('dep from node_modules should been transpiled when transpileDependencies is true', async () => {
@@ -90,9 +98,9 @@ test('dep from node_modules should been transpiled when transpileDependencies is
9098
`module.exports = { transpileDependencies: true }`
9199
)
92100
await project.run('vue-cli-service build')
93-
expect(await readVendorFile()).toMatch('return "__TEST__"')
101+
expect(await readLegacyVendorFile()).toMatch('return "__TEST__"')
94102

95-
expect(await readVendorFile()).toMatch('return "__SCOPE_TEST__"')
103+
expect(await readLegacyVendorFile()).toMatch('return "__SCOPE_TEST__"')
96104
})
97105

98106
// https://github.com/vuejs/vue-cli/issues/3057
@@ -104,6 +112,24 @@ test('only transpile package with same name specified in transpileDependencies',
104112
try {
105113
await project.run('vue-cli-service build')
106114
} catch (e) {}
107-
expect(await readVendorFile()).toMatch('() => "__TEST__"')
108-
expect(await readVendorFile()).toMatch('() => "__SCOPE_TEST__"')
115+
expect(await readLegacyVendorFile()).toMatch('() => "__TEST__"')
116+
expect(await readLegacyVendorFile()).toMatch('() => "__SCOPE_TEST__"')
117+
})
118+
119+
test('when transpileDependencies is on, the module build should also include transpiled code (with a different target)', async () => {
120+
await project.write(
121+
'vue.config.js',
122+
`module.exports = { transpileDependencies: true }`
123+
)
124+
await project.write(
125+
'node_modules/external-dep/index.js',
126+
`const test = (x) => x?.y?.z;\nexport default test`
127+
)
128+
129+
await project.run('vue-cli-service build')
130+
const file = await readVendorFile()
131+
// module build won't need arrow function transformation
132+
expect(file).toMatch('() => "__SCOPE_TEST__"')
133+
// but still needs optional chaining transformation
134+
expect(file).not.toMatch('x?.y?.z')
109135
})

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ test('build', async () => {
3838
// expect(index).toMatch(/<link [^>]+app[^>]+\.css" rel="preload" as="style">/)
3939

4040
// should inject scripts
41-
expect(index).toMatch(/<script defer="defer" src="\/js\/chunk-vendors\.\w{8}\.js">/)
42-
expect(index).toMatch(/<script defer="defer" src="\/js\/app\.\w{8}\.js">/)
41+
expect(index).toMatch(/<script defer="defer" src="\/js\/chunk-vendors-legacy\.\w{8}\.js" nomodule>/)
42+
expect(index).toMatch(/<script defer="defer" src="\/js\/app-legacy\.\w{8}\.js" nomodule>/)
4343
// should inject css
4444
expect(index).toMatch(/<link href="\/css\/app\.\w{8}\.css" rel="stylesheet">/)
4545

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ test('build', async () => {
3030
// expect(index).toMatch(/<link [^>]+app[^>]+\.css rel=preload as=style crossorigin>/)
3131

3232
// should apply crossorigin and add integrity to scripts and css
33-
expect(index).toMatch(/<script defer="defer" src="\/js\/chunk-vendors\.\w{8}\.js" crossorigin integrity="sha384-.{64}\s?">/)
34-
expect(index).toMatch(/<script defer="defer" src="\/js\/app\.\w{8}\.js" crossorigin integrity="sha384-.{64}\s?">/)
33+
expect(index).toMatch(/<script defer="defer" src="\/js\/chunk-vendors\.\w{8}\.js" crossorigin integrity="sha384-.{64}\s?" type="module">/)
34+
expect(index).toMatch(/<script defer="defer" src="\/js\/app\.\w{8}\.js" crossorigin integrity="sha384-.{64}\s?" type="module">/)
3535
expect(index).toMatch(/<link href="\/css\/app\.\w{8}\.css" rel="stylesheet" crossorigin integrity="sha384-.{64}\s?">/)
3636

3737
// verify integrity is correct by actually running it

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
jest.setTimeout(30000)
1+
jest.setTimeout(300000)
22

33
const create = require('@vue/cli-test-utils/createTestProject')
44
const { defaultPreset } = require('@vue/cli/lib/options')

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

+44-11
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ let server, browser
1212
test('modern mode', async () => {
1313
const project = await create('modern-mode', defaultPreset)
1414

15-
const { stdout } = await project.run('vue-cli-service build --modern')
15+
const { stdout } = await project.run('vue-cli-service build')
1616
expect(stdout).toMatch('Build complete.')
1717

1818
// assert correct bundle files
@@ -43,13 +43,9 @@ test('modern mode', async () => {
4343
expect(index).toMatch(/<script defer="defer" src="\/js\/chunk-vendors-legacy\.\w{8}\.js" nomodule>/)
4444
expect(index).toMatch(/<script defer="defer" src="\/js\/app-legacy\.\w{8}\.js" nomodule>/)
4545

46-
// should inject Safari 10 nomodule fix
47-
const { safariFix } = require('../lib/webpack/ModernModePlugin')
48-
expect(index).toMatch(`<script>${safariFix}</script>`)
49-
5046
// Test crossorigin="use-credentials"
5147
await project.write('vue.config.js', `module.exports = { crossorigin: 'use-credentials' }`)
52-
const { stdout: stdout2 } = await project.run('vue-cli-service build --modern')
48+
const { stdout: stdout2 } = await project.run('vue-cli-service build')
5349
expect(stdout2).toMatch('Build complete.')
5450
const index2 = await project.read('dist/index.html')
5551
// should use <script type="module" crossorigin="use-credentials"> for modern bundle
@@ -82,21 +78,58 @@ test('modern mode', async () => {
8278
expect(await getH1Text()).toMatch('Welcome to Your Vue.js App')
8379
})
8480

85-
test('no-unsafe-inline', async () => {
86-
const project = await create('no-unsafe-inline', defaultPreset)
81+
test('should not inject the nomodule-fix script if Safari 10 is not targeted', async () => {
82+
// the default targets already excludes safari 10
83+
const project = await create('skip-safari-fix', defaultPreset)
8784

88-
const { stdout } = await project.run('vue-cli-service build --modern --no-unsafe-inline')
85+
const { stdout } = await project.run('vue-cli-service build')
8986
expect(stdout).toMatch('Build complete.')
9087

88+
// should contain no inline scripts in the output html
89+
const index = await project.read('dist/index.html')
90+
expect(index).not.toMatch(/[^>]\s*<\/script>/)
91+
// should not contain the safari-nomodule-fix bundle, either
92+
const files = await fs.readdir(path.join(project.dir, 'dist/js'))
93+
expect(files.some(f => /^safari-nomodule-fix\.js$/.test(f))).toBe(false)
94+
})
95+
96+
test('should inject nomodule-fix script when Safari 10 support is required', async () => {
97+
const project = await create('safari-nomodule-fix', defaultPreset)
98+
99+
const pkg = JSON.parse(await project.read('package.json'))
100+
pkg.browserslist.push('safari > 10')
101+
await project.write('package.json', JSON.stringify(pkg, null, 2))
102+
103+
let { stdout } = await project.run('vue-cli-service build')
104+
let index = await project.read('dist/index.html')
105+
// should inject Safari 10 nomodule fix as an inline script
106+
const { safariFix } = require('../lib/webpack/ModernModePlugin')
107+
expect(index).toMatch(`<script>${safariFix}</script>`)
108+
109+
// `--no-unsafe-inline` option
110+
stdout = (await project.run('vue-cli-service build --no-unsafe-inline')).stdout
111+
expect(stdout).toMatch('Build complete.')
91112
// should output a separate safari-nomodule-fix bundle
92113
const files = await fs.readdir(path.join(project.dir, 'dist/js'))
93114
expect(files.some(f => /^safari-nomodule-fix\.js$/.test(f))).toBe(true)
94-
95115
// should contain no inline scripts in the output html
96-
const index = await project.read('dist/index.html')
116+
index = await project.read('dist/index.html')
97117
expect(index).not.toMatch(/[^>]\s*<\/script>/)
98118
})
99119

120+
test('--no-module', async () => {
121+
const project = await create('no-module', defaultPreset)
122+
123+
const { stdout } = await project.run('vue-cli-service build --no-module')
124+
expect(stdout).toMatch('Build complete.')
125+
126+
const index = await project.read('dist/index.html')
127+
expect(index).not.toMatch('type="module"')
128+
129+
const files = await fs.readdir(path.join(project.dir, 'dist/js'))
130+
expect(files.some(f => /-legacy.js/.test(f))).toBe(false)
131+
})
132+
100133
afterAll(async () => {
101134
if (browser) {
102135
await browser.close()

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

+11-11
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,14 @@ test('build w/ multi page', async () => {
110110
const assertSharedAssets = file => {
111111
// should split and preload vendor chunk
112112
// expect(file).toMatch(/<link [^>]*js\/chunk-vendors[^>]*\.js" rel="preload" as="script">/)
113-
expect(file).toMatch(/<script [^>]*src="\/js\/chunk-vendors\.\w+\.js">/)
113+
expect(file).toMatch(/<script [^>]*src="\/js\/chunk-vendors\.\w+\.js" type="module">/)
114114
}
115115

116116
const index = await project.read('dist/index.html')
117117
assertSharedAssets(index)
118118
// should split and preload common js and css
119119
// expect(index).toMatch(/<link [^>]*js\/chunk-common[^>]*\.js" rel="preload" as="script">/)
120-
expect(index).toMatch(/<script [^>]*src="\/js\/chunk-common\.\w+\.js">/)
120+
expect(index).toMatch(/<script [^>]*src="\/js\/chunk-common\.\w+\.js" type="module">/)
121121
expect(index).toMatch(/<link href="\/css\/chunk-common\.\w+\.css" rel="stylesheet">/)
122122
// expect(index).toMatch(/<link [^>]*chunk-common[^>]*\.css" rel="preload" as="style">/)
123123
// should preload correct page file
@@ -128,9 +128,9 @@ test('build w/ multi page', async () => {
128128
// expect(index).toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css" rel="prefetch">/)
129129
// expect(index).toMatch(/<link [^>]*js\/chunk-\w+\.\w+\.js" rel="prefetch">/)
130130
// should load correct page js
131-
expect(index).toMatch(/<script [^>]*src="\/js\/index\.\w+\.js">/)
132-
expect(index).not.toMatch(/<script [^>]*src="\/js\/foo\.\w+\.js">/)
133-
expect(index).not.toMatch(/<script [^>]*src="\/js\/bar\.\w+\.js">/)
131+
expect(index).toMatch(/<script [^>]*src="\/js\/index\.\w+\.js" type="module">/)
132+
expect(index).not.toMatch(/<script [^>]*src="\/js\/foo\.\w+\.js" type="module">/)
133+
expect(index).not.toMatch(/<script [^>]*src="\/js\/bar\.\w+\.js" type="module">/)
134134

135135
const foo = await project.read('dist/foo.html')
136136
assertSharedAssets(foo)
@@ -143,9 +143,9 @@ test('build w/ multi page', async () => {
143143
// expect(foo).not.toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css" rel="prefetch">/)
144144
// expect(foo).not.toMatch(/<link [^>]*js\/chunk-\w+\.\w+\.js" rel="prefetch">/)
145145
// should load correct page js
146-
expect(foo).not.toMatch(/<script [^>]*src="\/js\/index\.\w+\.js">/)
147-
expect(foo).toMatch(/<script [^>]*src="\/js\/foo\.\w+\.js">/)
148-
expect(foo).not.toMatch(/<script [^>]*src="\/js\/bar\.\w+\.js">/)
146+
expect(foo).not.toMatch(/<script [^>]*src="\/js\/index\.\w+\.js" type="module">/)
147+
expect(foo).toMatch(/<script [^>]*src="\/js\/foo\.\w+\.js" type="module">/)
148+
expect(foo).not.toMatch(/<script [^>]*src="\/js\/bar\.\w+\.js" type="module">/)
149149

150150
const bar = await project.read('dist/bar.html')
151151
assertSharedAssets(bar)
@@ -162,9 +162,9 @@ test('build w/ multi page', async () => {
162162
// expect(bar).toMatch(/<link [^>]*css\/chunk-\w+\.\w+\.css" rel="prefetch">/)
163163
// expect(bar).toMatch(/<link [^>]*js\/chunk-\w+\.\w+\.js" rel="prefetch">/)
164164
// should load correct page js
165-
expect(bar).not.toMatch(/<script [^>]*src="\/js\/index\.\w+\.js">/)
166-
expect(bar).not.toMatch(/<script [^>]*src="\/js\/foo\.\w+\.js">/)
167-
expect(bar).toMatch(/<script [^>]*src="\/js\/bar\.\w+\.js">/)
165+
expect(bar).not.toMatch(/<script [^>]*src="\/js\/index\.\w+\.js" type="module">/)
166+
expect(bar).not.toMatch(/<script [^>]*src="\/js\/foo\.\w+\.js" type="module">/)
167+
expect(bar).toMatch(/<script [^>]*src="\/js\/bar\.\w+\.js" type="module">/)
168168

169169
// assert pages work
170170
const port = await portfinder.getPortPromise()

packages/@vue/cli-service/lib/commands/build/index.js

+5-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const defaults = {
22
clean: true,
33
target: 'app',
4+
module: true,
45
formats: 'commonjs,umd,umd-min',
56
'unsafe-inline': true
67
}
@@ -26,7 +27,7 @@ module.exports = (api, options) => {
2627
options: {
2728
'--mode': `specify env mode (default: production)`,
2829
'--dest': `specify output directory (default: ${options.outputDir})`,
29-
'--modern': `build app targeting modern browsers with auto fallback`,
30+
'--no-module': `build app without generating <script type="module"> chunks for modern browsers`,
3031
'--no-unsafe-inline': `build app without introducing inline scripts`,
3132
'--target': `app | lib | wc | wc-async (default: ${defaults.target})`,
3233
'--inline-vue': 'include the Vue module in the final bundle of library or web component target',
@@ -52,7 +53,7 @@ module.exports = (api, options) => {
5253
}
5354

5455
process.env.VUE_CLI_BUILD_TARGET = args.target
55-
if (args.modern && args.target === 'app') {
56+
if (args.module && args.target === 'app') {
5657
process.env.VUE_CLI_MODERN_MODE = true
5758
if (!process.env.VUE_CLI_MODERN_BUILD) {
5859
// main-process for legacy build
@@ -78,14 +79,6 @@ module.exports = (api, options) => {
7879
}
7980
delete process.env.VUE_CLI_MODERN_MODE
8081
} else {
81-
if (args.modern) {
82-
const { warn } = require('@vue/cli-shared-utils')
83-
warn(
84-
`Modern mode only works with default target (app). ` +
85-
`For libraries or web components, use the browserslist ` +
86-
`config to specify target browsers.`
87-
)
88-
}
8982
await build(args, api, options)
9083
}
9184
delete process.env.VUE_CLI_BUILD_TARGET
@@ -110,7 +103,7 @@ async function build (args, api, options) {
110103
log()
111104
const mode = api.service.mode
112105
if (args.target === 'app') {
113-
const bundleTag = args.modern
106+
const bundleTag = args.module
114107
? args.modernBuild
115108
? `modern bundle `
116109
: `legacy bundle `
@@ -132,7 +125,7 @@ async function build (args, api, options) {
132125
}
133126

134127
const targetDir = api.resolve(options.outputDir)
135-
const isLegacyBuild = args.target === 'app' && args.modern && !args.modernBuild
128+
const isLegacyBuild = args.target === 'app' && args.module && !args.modernBuild
136129

137130
// resolve raw webpack config
138131
let webpackConfig

packages/@vue/cli-service/lib/commands/build/resolveAppConfig.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ module.exports = (api, args, options) => {
1717
})
1818
}
1919

20-
if (args.modern) {
20+
if (args.module) {
2121
const ModernModePlugin = require('../../webpack/ModernModePlugin')
2222
if (!args.modernBuild) {
2323
// Inject plugin to extract build stats and write to disk
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// copied from @vue/babel-preset-app
2+
3+
const { semver } = require('@vue/cli-shared-utils')
4+
const { default: getTargets } = require('@babel/helper-compilation-targets')
5+
6+
const allModernTargets = getTargets(
7+
{ esmodules: true },
8+
{ ignoreBrowserslistConfig: true }
9+
)
10+
11+
function getIntersectionTargets (targets, constraintTargets) {
12+
const intersection = Object.keys(constraintTargets).reduce(
13+
(results, browser) => {
14+
// exclude the browsers that the user does not need
15+
if (!targets[browser]) {
16+
return results
17+
}
18+
19+
// if the user-specified version is higher the minimum version that supports esmodule, than use it
20+
results[browser] = semver.gt(
21+
semver.coerce(constraintTargets[browser]),
22+
semver.coerce(targets[browser])
23+
)
24+
? constraintTargets[browser]
25+
: targets[browser]
26+
27+
return results
28+
},
29+
{}
30+
)
31+
32+
return intersection
33+
}
34+
35+
function getModernTargets (targets) {
36+
// use the intersection of modern mode browsers and user defined targets config
37+
return getIntersectionTargets(targets, allModernTargets)
38+
}
39+
40+
const projectTargets = getTargets()
41+
const projectModernTargets = getModernTargets(projectTargets)
42+
43+
module.exports = {
44+
getTargets,
45+
getModernTargets,
46+
getIntersectionTargets,
47+
48+
projectTargets,
49+
projectModernTargets
50+
}

0 commit comments

Comments
 (0)