Skip to content

Commit 91ca0b1

Browse files
authored
refactor: improve the polyfill importing logic of modern mode (#5513)
TODO: - should remove core-js from `dependencies` in the next major.
1 parent 538a028 commit 91ca0b1

File tree

4 files changed

+110
-43
lines changed

4 files changed

+110
-43
lines changed

packages/@vue/babel-preset-app/__tests__/babel-preset.spec.js

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -48,37 +48,52 @@ test('polyfill detection', () => {
4848
expect(code).toMatch('"core-js/modules/es.map"')
4949
})
5050

51-
test('modern mode always skips polyfills', () => {
51+
test('modern mode always skips unnecessary polyfills', () => {
5252
process.env.VUE_CLI_MODERN_BUILD = true
5353
let { code } = babel.transformSync(`
5454
const a = new Map()
55+
console.log(globalThis)
5556
`.trim(), {
5657
babelrc: false,
5758
presets: [[preset, {
58-
targets: { ie: 9 },
59+
targets: { ie: 9, safari: '12' },
5960
useBuiltIns: 'usage'
6061
}]],
6162
filename: 'test-entry-file.js'
6263
})
63-
// default includes
64-
expect(code).not.toMatch(getAbsolutePolyfill('es.promise'))
64+
// default includes that are supported in all modern browsers should be skipped
65+
expect(code).not.toMatch('es.assign')
66+
// though es.promise is not supported in all modern browsers
67+
// (modern: safari >= 10.1, es.promise: safrai >= 11)
68+
// the custom configuration only expects to support safari >= 12
69+
// so it can be skipped
70+
expect(code).not.toMatch('es.promise"')
71+
// es.promise.finally is supported in safari >= 13.0.3
72+
// so still needs to be included
73+
expect(code).toMatch('es.promise.finally')
74+
6575
// usage-based detection
66-
expect(code).not.toMatch('"core-js/modules/es.map"')
76+
// Map is supported in all modern browsers
77+
expect(code).not.toMatch('es.map')
78+
// globalThis is not supported until safari 12.1
79+
expect(code).toMatch('es.global-this')
6780

6881
;({ code } = babel.transformSync(`
6982
const a = new Map()
7083
`.trim(), {
7184
babelrc: false,
7285
presets: [[preset, {
73-
targets: { ie: 9 },
86+
targets: { ie: 9, safari: '12' },
7487
useBuiltIns: 'entry'
7588
}]],
7689
filename: 'test-entry-file.js'
7790
}))
7891
// default includes
79-
expect(code).not.toMatch(getAbsolutePolyfill('es.promise'))
92+
expect(code).not.toMatch('es.promise"')
93+
expect(code).not.toMatch('es.promise.finally')
8094
// usage-based detection
8195
expect(code).not.toMatch('"core-js/modules/es.map"')
96+
expect(code).not.toMatch('es.global-this')
8297
delete process.env.VUE_CLI_MODERN_BUILD
8398
})
8499

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

Lines changed: 76 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const path = require('path')
2+
const semver = require('semver')
23

34
const defaultPolyfills = [
45
// promise polyfill alone doesn't work in IE,
@@ -9,21 +10,78 @@ const defaultPolyfills = [
910
// this is needed for object rest spread support in templates
1011
// as vue-template-es2015-compiler 1.8+ compiles it to Object.assign() calls.
1112
'es.object.assign',
12-
// #2012 es6.promise replaces native Promise in FF and causes missing finally
13+
// #2012 es.promise replaces native Promise in FF and causes missing finally
1314
'es.promise.finally'
1415
]
1516

16-
function getPolyfills (targets, includes, { ignoreBrowserslistConfig, configPath }) {
17-
const getTargets = require('@babel/helper-compilation-targets').default
18-
const builtInTargets = getTargets(targets, { ignoreBrowserslistConfig, configPath })
17+
const {
18+
default: getTargets,
19+
isRequired
20+
} = require('@babel/helper-compilation-targets')
1921

22+
function getIntersectionTargets (targets, constraintTargets) {
23+
const intersection = Object.keys(constraintTargets).reduce(
24+
(results, browser) => {
25+
// exclude the browsers that the user does not need
26+
if (!targets[browser]) {
27+
return results
28+
}
29+
30+
// if the user-specified version is higher the minimum version that supports esmodule, than use it
31+
results[browser] = semver.gt(
32+
semver.coerce(constraintTargets[browser]),
33+
semver.coerce(targets[browser])
34+
)
35+
? constraintTargets[browser]
36+
: targets[browser]
37+
38+
return results
39+
},
40+
{}
41+
)
42+
43+
return intersection
44+
}
45+
46+
function getModernTargets (targets) {
47+
const allModernTargets = getTargets(
48+
{ esmodules: true },
49+
{ ignoreBrowserslistConfig: true }
50+
)
51+
52+
// use the intersection of modern mode browsers and user defined targets config
53+
return getIntersectionTargets(targets, allModernTargets)
54+
}
55+
56+
function getWCTargets (targets) {
57+
// targeting browsers that at least support ES2015 classes
58+
// https://github.com/babel/babel/blob/v7.9.6/packages/babel-compat-data/data/plugins.json#L194-L204
59+
const allWCTargets = getTargets(
60+
{
61+
browsers: [
62+
'Chrome >= 46',
63+
'Firefox >= 45',
64+
'Safari >= 10',
65+
'Edge >= 13',
66+
'iOS >= 10',
67+
'Electron >= 0.36'
68+
]
69+
},
70+
{ ignoreBrowserslistConfig: true }
71+
)
72+
73+
// use the intersection of browsers supporting Web Components and user defined targets config
74+
return getIntersectionTargets(targets, allWCTargets)
75+
}
76+
77+
function getPolyfills (targets, includes) {
2078
// if no targets specified, include all default polyfills
21-
if (!targets && !Object.keys(builtInTargets).length) {
79+
if (!targets || !Object.keys(targets).length) {
2280
return includes
2381
}
2482

25-
const { list } = require('core-js-compat')({ targets: builtInTargets })
26-
return includes.filter(item => list.includes(item))
83+
const compatData = require('core-js-compat').data
84+
return includes.filter(item => isRequired(item, targets, { compatData }))
2785
}
2886

2987
module.exports = (context, options = {}) => {
@@ -36,7 +94,7 @@ module.exports = (context, options = {}) => {
3694
// dropping them may break some projects.
3795
// So in the following blocks we don't directly test the `NODE_ENV`.
3896
// Rather, we turn it into the two commonly used feature flags.
39-
if (process.env.NODE_ENV === 'test') {
97+
if (!process.env.VUE_CLI_TEST && process.env.NODE_ENV === 'test') {
4098
// Both Jest & Mocha set NODE_ENV to 'test'.
4199
// And both requires the `node` target.
42100
process.env.VUE_CLI_BABEL_TARGET_NODE = 'true'
@@ -62,7 +120,7 @@ module.exports = (context, options = {}) => {
62120
bugfixes = true,
63121
targets: rawTargets,
64122
spec,
65-
ignoreBrowserslistConfig = !!process.env.VUE_CLI_MODERN_BUILD,
123+
ignoreBrowserslistConfig,
66124
configPath,
67125
include,
68126
exclude,
@@ -88,29 +146,17 @@ module.exports = (context, options = {}) => {
88146
version = runtimeVersion
89147
} = options
90148

91-
// resolve targets
92-
let targets
149+
// resolve targets for preset-env
150+
let targets = getTargets(rawTargets, { ignoreBrowserslistConfig, configPath })
93151
if (process.env.VUE_CLI_BABEL_TARGET_NODE) {
94152
// running tests in Node.js
95153
targets = { node: 'current' }
96154
} else if (process.env.VUE_CLI_BUILD_TARGET === 'wc' || process.env.VUE_CLI_BUILD_TARGET === 'wc-async') {
97155
// targeting browsers that at least support ES2015 classes
98-
// https://github.com/babel/babel/blob/master/packages/babel-preset-env/data/plugins.json#L52-L61
99-
targets = {
100-
browsers: [
101-
'Chrome >= 49',
102-
'Firefox >= 45',
103-
'Safari >= 10',
104-
'Edge >= 13',
105-
'iOS >= 10',
106-
'Electron >= 0.36'
107-
]
108-
}
156+
targets = getWCTargets(targets)
109157
} else if (process.env.VUE_CLI_MODERN_BUILD) {
110-
// targeting browsers that support <script type="module">
111-
targets = { esmodules: true }
112-
} else {
113-
targets = rawTargets
158+
// targeting browsers that at least support <script type="module">
159+
targets = getModernTargets(targets)
114160
}
115161

116162
// included-by-default polyfills. These are common polyfills that 3rd party
@@ -122,13 +168,9 @@ module.exports = (context, options = {}) => {
122168
if (
123169
buildTarget === 'app' &&
124170
useBuiltIns === 'usage' &&
125-
!process.env.VUE_CLI_BABEL_TARGET_NODE &&
126-
!process.env.VUE_CLI_MODERN_BUILD
171+
!process.env.VUE_CLI_BABEL_TARGET_NODE
127172
) {
128-
polyfills = getPolyfills(targets, userPolyfills || defaultPolyfills, {
129-
ignoreBrowserslistConfig,
130-
configPath
131-
})
173+
polyfills = getPolyfills(targets, userPolyfills || defaultPolyfills)
132174
plugins.push([
133175
require('./polyfillsPlugin'),
134176
{ polyfills, entryFiles, useAbsolutePath: !!absoluteRuntime }
@@ -139,7 +181,7 @@ module.exports = (context, options = {}) => {
139181

140182
const envOptions = {
141183
bugfixes,
142-
corejs: useBuiltIns ? 3 : false,
184+
corejs: useBuiltIns ? require('core-js/package.json').version : false,
143185
spec,
144186
loose,
145187
debug,
@@ -206,7 +248,7 @@ module.exports = (context, options = {}) => {
206248
presets: [
207249
[require('@babel/preset-env'), {
208250
useBuiltIns,
209-
corejs: useBuiltIns ? 3 : false
251+
corejs: useBuiltIns ? require('core-js/package.json').version : false
210252
}]
211253
]
212254
}]

packages/@vue/babel-preset-app/package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,16 @@
3535
"@vue/babel-preset-jsx": "^1.1.2",
3636
"babel-plugin-dynamic-import-node": "^2.3.3",
3737
"core-js": "^3.6.5",
38-
"core-js-compat": "^3.6.5"
38+
"core-js-compat": "^3.6.5",
39+
"semver": "^6.1.0"
3940
},
4041
"peerDependencies": {
41-
"@babel/core": "*"
42+
"@babel/core": "*",
43+
"core-js": "^3"
44+
},
45+
"peerDependenciesMeta": {
46+
"core-js": {
47+
"optional": true
48+
}
4249
}
4350
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ const { addSideEffect } = require('@babel/helper-module-imports')
22

33
// slightly modifiled from @babel/preset-env/src/utils
44
// use an absolute path for core-js modules, to fix conflicts of different core-js versions
5+
// TODO: remove the `useAbsolutePath` option in v5,
6+
// because `core-js` is sure to be present in newer projects;
7+
// we only need absolute path for babel runtime helpers, not for polyfills
58
function getModulePath (mod, useAbsolutePath) {
69
const modPath =
710
mod === 'regenerator-runtime'

0 commit comments

Comments
 (0)