Skip to content

Commit aad72cf

Browse files
authored
feat: only needs one bundle if all targets support es module (#6419)
1 parent 3b851a8 commit aad72cf

File tree

9 files changed

+201
-108
lines changed

9 files changed

+201
-108
lines changed

packages/@vue/babel-preset-app/index.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,14 @@ function getIntersectionTargets (targets, constraintTargets) {
4747
return intersection
4848
}
4949

50-
function getModernTargets (targets) {
51-
const allModernTargets = getTargets(
50+
function getModuleTargets (targets) {
51+
const allModuleTargets = getTargets(
5252
{ esmodules: true },
5353
{ ignoreBrowserslistConfig: true }
5454
)
5555

5656
// use the intersection of modern mode browsers and user defined targets config
57-
return getIntersectionTargets(targets, allModernTargets)
57+
return getIntersectionTargets(targets, allModuleTargets)
5858
}
5959

6060
function getWCTargets (targets) {
@@ -181,7 +181,7 @@ module.exports = (context, options = {}) => {
181181
targets = getWCTargets(targets)
182182
} else if (process.env.VUE_CLI_MODERN_BUILD) {
183183
// targeting browsers that at least support <script type="module">
184-
targets = getModernTargets(targets)
184+
targets = getModuleTargets(targets)
185185
}
186186

187187
// included-by-default polyfills. These are common polyfills that 3rd party

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

+31-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ test('should inject nomodule-fix script when Safari 10 support is required', asy
103103
let { stdout } = await project.run('vue-cli-service build')
104104
let index = await project.read('dist/index.html')
105105
// should inject Safari 10 nomodule fix as an inline script
106-
const { safariFix } = require('../lib/webpack/ModernModePlugin')
106+
const { safariFix } = require('../lib/webpack/SafariNomoduleFixPlugin')
107107
expect(index).toMatch(`<script>${safariFix}</script>`)
108108

109109
// `--no-unsafe-inline` option
@@ -130,6 +130,36 @@ test('--no-module', async () => {
130130
expect(files.some(f => /-legacy.js/.test(f))).toBe(false)
131131
})
132132

133+
test('should use correct hash for fallback bundles', async () => {
134+
const project = await create('legacy-hash', defaultPreset)
135+
136+
const { stdout } = await project.run('vue-cli-service build')
137+
expect(stdout).toMatch('Build complete.')
138+
139+
const index = await project.read('dist/index.html')
140+
const jsFiles = (await fs.readdir(path.join(project.dir, 'dist/js'))).filter(f => f.endsWith('.js'))
141+
for (const f of jsFiles) {
142+
expect(index).toMatch(`<script defer="defer" src="/js/${f}"`)
143+
}
144+
})
145+
146+
test('should only build one bundle if all targets support ES module', async () => {
147+
const project = await create('no-differential-loading', defaultPreset)
148+
149+
const pkg = JSON.parse(await project.read('package.json'))
150+
pkg.browserslist.push('not ie <= 11')
151+
await project.write('package.json', JSON.stringify(pkg, null, 2))
152+
153+
const { stdout } = await project.run('vue-cli-service build')
154+
expect(stdout).toMatch('Build complete.')
155+
156+
const index = await project.read('dist/index.html')
157+
expect(index).not.toMatch('type="module"')
158+
159+
const files = await fs.readdir(path.join(project.dir, 'dist/js'))
160+
expect(files.some(f => /-legacy.js/.test(f))).toBe(false)
161+
})
162+
133163
afterAll(async () => {
134164
if (browser) {
135165
await browser.close()

packages/@vue/cli-service/bin/vue-cli-service.js

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const rawArgv = process.argv.slice(2)
1818
const args = require('minimist')(rawArgv, {
1919
boolean: [
2020
// build
21+
// FIXME: --no-module, --no-unsafe-inline, no-clean, etc.
2122
'modern',
2223
'report',
2324
'report-json',

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

+39-31
Original file line numberDiff line numberDiff line change
@@ -53,35 +53,43 @@ module.exports = (api, options) => {
5353
}
5454

5555
process.env.VUE_CLI_BUILD_TARGET = args.target
56-
if (args.module && args.target === 'app') {
57-
process.env.VUE_CLI_MODERN_MODE = true
58-
if (!process.env.VUE_CLI_MODERN_BUILD) {
59-
// main-process for legacy build
60-
await build(Object.assign({}, args, {
61-
modernBuild: false,
62-
keepAlive: true
63-
}), api, options)
64-
// spawn sub-process of self for modern build
65-
const { execa } = require('@vue/cli-shared-utils')
66-
const cliBin = require('path').resolve(__dirname, '../../../bin/vue-cli-service.js')
67-
await execa('node', [cliBin, 'build', ...rawArgs], {
68-
stdio: 'inherit',
69-
env: {
70-
VUE_CLI_MODERN_BUILD: true
71-
}
72-
})
73-
} else {
74-
// sub-process for modern build
75-
await build(Object.assign({}, args, {
76-
modernBuild: true,
77-
clean: false
78-
}), api, options)
79-
}
80-
delete process.env.VUE_CLI_MODERN_MODE
81-
} else {
56+
57+
const { log, execa } = require('@vue/cli-shared-utils')
58+
const { allProjectTargetsSupportModule } = require('../../util/targets')
59+
60+
let needsDifferentialLoading = args.target === 'app' && args.module
61+
if (allProjectTargetsSupportModule) {
62+
log(
63+
`All browser targets in the browserslist configuration have supported ES module.\n` +
64+
`Therefore we don't build two separate bundles for differential loading.\n`
65+
)
66+
needsDifferentialLoading = false
67+
}
68+
69+
if (!needsDifferentialLoading) {
8270
await build(args, api, options)
71+
return
72+
}
73+
74+
process.env.VUE_CLI_MODERN_MODE = true
75+
if (!process.env.VUE_CLI_MODERN_BUILD) {
76+
// main-process for legacy build
77+
const legacyBuildArgs = { ...args, moduleBuild: false, keepAlive: true }
78+
await build(legacyBuildArgs, api, options)
79+
80+
// spawn sub-process of self for modern build
81+
const cliBin = require('path').resolve(__dirname, '../../../bin/vue-cli-service.js')
82+
await execa('node', [cliBin, 'build', ...rawArgs], {
83+
stdio: 'inherit',
84+
env: {
85+
VUE_CLI_MODERN_BUILD: true
86+
}
87+
})
88+
} else {
89+
// sub-process for modern build
90+
const moduleBuildArgs = { ...args, moduleBuild: true, clean: false }
91+
await build(moduleBuildArgs, api, options)
8392
}
84-
delete process.env.VUE_CLI_BUILD_TARGET
8593
})
8694
}
8795

@@ -104,8 +112,8 @@ async function build (args, api, options) {
104112
const mode = api.service.mode
105113
if (args.target === 'app') {
106114
const bundleTag = args.module
107-
? args.modernBuild
108-
? `modern bundle `
115+
? args.moduleBuild
116+
? `module bundle `
109117
: `legacy bundle `
110118
: ``
111119
logWithSpinner(`Building ${bundleTag}for ${mode}...`)
@@ -125,7 +133,7 @@ async function build (args, api, options) {
125133
}
126134

127135
const targetDir = api.resolve(options.outputDir)
128-
const isLegacyBuild = args.target === 'app' && args.module && !args.modernBuild
136+
const isLegacyBuild = args.target === 'app' && args.module && !args.moduleBuild
129137

130138
// resolve raw webpack config
131139
let webpackConfig
@@ -162,7 +170,7 @@ async function build (args, api, options) {
162170
modifyConfig(webpackConfig, config => {
163171
config.plugins.push(new DashboardPlugin({
164172
type: 'build',
165-
modernBuild: args.modernBuild,
173+
moduleBuild: args.moduleBuild,
166174
keepAlive: args.keepAlive
167175
}))
168176
})

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

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

20-
if (args.module) {
20+
if (process.env.VUE_CLI_MODERN_MODE) {
2121
const ModernModePlugin = require('../../webpack/ModernModePlugin')
22-
if (!args.modernBuild) {
22+
const SafariNomoduleFixPlugin = require('../../webpack/SafariNomoduleFixPlugin')
23+
24+
if (!args.moduleBuild) {
2325
// Inject plugin to extract build stats and write to disk
2426
config
25-
.plugin('modern-mode-legacy')
26-
.use(ModernModePlugin, [{
27-
targetDir,
28-
isModernBuild: false,
29-
unsafeInline: args['unsafe-inline']
30-
}])
27+
.plugin('modern-mode-legacy')
28+
.use(ModernModePlugin, [{
29+
targetDir,
30+
isModuleBuild: false
31+
}])
3132
} else {
32-
// Inject plugin to read non-modern build stats and inject HTML
3333
config
34-
.plugin('modern-mode-modern')
35-
.use(ModernModePlugin, [{
36-
targetDir,
37-
isModernBuild: true,
34+
.plugin('safari-nomodule-fix')
35+
.use(SafariNomoduleFixPlugin, [{
3836
unsafeInline: args['unsafe-inline'],
3937
// as we may generate an addition file asset (if `no-unsafe-inline` specified)
4038
// we need to provide the correct directory for that file to place in
4139
jsDirectory: require('../../util/getAssetPath')(options, 'js')
4240
}])
41+
42+
// Inject plugin to read non-modern build stats and inject HTML
43+
config
44+
.plugin('modern-mode-modern')
45+
.use(ModernModePlugin, [{
46+
targetDir,
47+
isModuleBuild: true
48+
}])
4349
}
4450
}
4551

packages/@vue/cli-service/lib/util/targets.js

+26-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
const { semver } = require('@vue/cli-shared-utils')
44
const { default: getTargets } = require('@babel/helper-compilation-targets')
55

6-
const allModernTargets = getTargets(
6+
// See the result at <https://github.com/babel/babel/blob/v7.13.15/packages/babel-compat-data/data/native-modules.json>
7+
const allModuleTargets = getTargets(
78
{ esmodules: true },
89
{ ignoreBrowserslistConfig: true }
910
)
@@ -32,19 +33,38 @@ function getIntersectionTargets (targets, constraintTargets) {
3233
return intersection
3334
}
3435

35-
function getModernTargets (targets) {
36+
function getModuleTargets (targets) {
3637
// use the intersection of modern mode browsers and user defined targets config
37-
return getIntersectionTargets(targets, allModernTargets)
38+
return getIntersectionTargets(targets, allModuleTargets)
3839
}
3940

41+
function doAllTargetsSupportModule (targets) {
42+
const browserList = Object.keys(targets)
43+
44+
return browserList.every(browserName => {
45+
if (!allModuleTargets[browserName]) {
46+
return false
47+
}
48+
49+
return semver.gte(
50+
semver.coerce(targets[browserName]),
51+
semver.coerce(allModuleTargets[browserName])
52+
)
53+
})
54+
}
55+
56+
// get browserslist targets in current working directory
4057
const projectTargets = getTargets()
41-
const projectModernTargets = getModernTargets(projectTargets)
58+
const projectModuleTargets = getModuleTargets(projectTargets)
59+
const allProjectTargetsSupportModule = doAllTargetsSupportModule(projectTargets)
4260

4361
module.exports = {
4462
getTargets,
45-
getModernTargets,
63+
getModuleTargets,
4664
getIntersectionTargets,
65+
doAllTargetsSupportModule,
4766

4867
projectTargets,
49-
projectModernTargets
68+
projectModuleTargets,
69+
allProjectTargetsSupportModule
5070
}

packages/@vue/cli-service/lib/webpack/DashboardPlugin.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ function getTimeMessage (timer) {
3333
class DashboardPlugin {
3434
constructor (options) {
3535
this.type = options.type
36-
if (this.type === 'build' && options.modernBuild) {
36+
if (this.type === 'build' && options.moduleBuild) {
3737
this.type = 'build-modern'
3838
}
3939
this.watching = false
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,17 @@
11
const fs = require('fs-extra')
22
const path = require('path')
33
const HtmlWebpackPlugin = require('html-webpack-plugin')
4-
5-
const { semver } = require('@vue/cli-shared-utils')
6-
const { projectModernTargets } = require('../util/targets')
7-
8-
const minSafariVersion = projectModernTargets.safari
9-
const minIOSVersion = projectModernTargets.ios
10-
const supportsSafari10 =
11-
(minSafariVersion && semver.lt(semver.coerce(minSafariVersion), '11.0.0')) ||
12-
(minIOSVersion && semver.lt(semver.coerce(minIOSVersion), '11.0.0'))
13-
const needsSafariFix = supportsSafari10
14-
15-
// https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc
16-
const safariFix = `!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();`
17-
184
class ModernModePlugin {
19-
constructor ({ targetDir, isModernBuild, unsafeInline, jsDirectory }) {
5+
constructor ({ targetDir, isModuleBuild }) {
206
this.targetDir = targetDir
21-
this.isModernBuild = isModernBuild
22-
this.unsafeInline = unsafeInline
23-
this.jsDirectory = jsDirectory
7+
this.isModuleBuild = isModuleBuild
248
}
259

2610
apply (compiler) {
27-
if (!this.isModernBuild) {
11+
if (!this.isModuleBuild) {
2812
this.applyLegacy(compiler)
2913
} else {
30-
this.applyModern(compiler)
14+
this.applyModule(compiler)
3115
}
3216
}
3317

@@ -53,7 +37,7 @@ class ModernModePlugin {
5337
})
5438
}
5539

56-
applyModern (compiler) {
40+
applyModule (compiler) {
5741
const ID = `vue-cli-modern-bundle`
5842
compiler.hooks.compilation.tap(ID, compilation => {
5943
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync(ID, async (data, cb) => {
@@ -87,36 +71,6 @@ class ModernModePlugin {
8771
.filter(a => a.tagName === 'script' && a.attributes)
8872
legacyAssets.forEach(a => { a.attributes.nomodule = '' })
8973

90-
if (needsSafariFix) {
91-
if (this.unsafeInline) {
92-
// inject inline Safari 10 nomodule fix
93-
tags.push({
94-
tagName: 'script',
95-
closeTag: true,
96-
innerHTML: safariFix
97-
})
98-
} else {
99-
// inject the fix as an external script
100-
const safariFixPath = path.join(this.jsDirectory, 'safari-nomodule-fix.js')
101-
const fullSafariFixPath = path.join(compilation.options.output.publicPath, safariFixPath)
102-
compilation.assets[safariFixPath] = {
103-
source: function () {
104-
return Buffer.from(safariFix)
105-
},
106-
size: function () {
107-
return Buffer.byteLength(safariFix)
108-
}
109-
}
110-
tags.push({
111-
tagName: 'script',
112-
closeTag: true,
113-
attributes: {
114-
src: fullSafariFixPath
115-
}
116-
})
117-
}
118-
}
119-
12074
tags.push(...legacyAssets)
12175
await fs.remove(tempFilename)
12276
cb()
@@ -129,5 +83,4 @@ class ModernModePlugin {
12983
}
13084
}
13185

132-
ModernModePlugin.safariFix = safariFix
13386
module.exports = ModernModePlugin

0 commit comments

Comments
 (0)