Skip to content

Commit 4e7d57f

Browse files
committed
feat(babel): better Babel polyfill defaults
- Now includes polyfills for Promise and Object.assign by default; - New option "polyfills" for @vue/babel-preset-app allows customization of included-by-default polyfills.
1 parent a8af883 commit 4e7d57f

File tree

4 files changed

+113
-11
lines changed

4 files changed

+113
-11
lines changed

packages/@vue/babel-preset-app/README.md

+20-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This is the default Babel preset used in all Vue CLI projects.
1212
- `targets` is determined:
1313
- using `browserslist` field in `package.json` when building for browsers
1414
- set to `{ node: 'current' }` when running unit tests in Node.js
15+
- Includes `Promise` and `Object.assign` polyfills by default so that they are usable even in non-transpiled dependencies (only for environments that need them)
1516
- [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-runtime)
1617
- Only enabled for helpers since polyfills are handled by `babel-preset-env`
1718
- [dynamic import syntax](https://github.com/tc39/proposal-dynamic-import)
@@ -45,7 +46,25 @@ This is the default Babel preset used in all Vue CLI projects.
4546

4647
Default: `'usage'`
4748

48-
Explicitly set `useBuiltIns` option for `babel-preset-env`. See [babel-preset-env docs](https://github.com/babel/babel/tree/master/packages/babel-preset-env#usebuiltins) for more details.
49+
Explicitly set `useBuiltIns` option for `babel-preset-env`.
50+
51+
The default value is `'usage'`, which adds imports to polyfills based on the usage in transpiled code. Note that the usage detection does not apply to your dependencies (which are excluded by `cli-plugin-babel` by default). If one of your dependencies need polyfills, you have three options:
52+
53+
1. Add that dependency to the `transpileDependencies` option in `vue.config.js`. This would enable the same usage-based polyfill detection for that dependency as well;
54+
55+
2. OR, you can explicitly include the needed polyfills using the [polyfills](#polyfills) option for this preset.
56+
57+
3. Use `useBuiltIns: 'entry'` and then add `import '@babel/polyfill'` to your entry file. This will import **ALL** polyfills based on your `browserslist` targets so that you don't need to worry about dependency polyfills anymore, but will likely bloat your final bundle with some unused polyfills.
58+
59+
See [babel-preset-env docs](https://github.com/babel/babel/tree/master/packages/babel-preset-env#usebuiltins) for more details.
60+
61+
- **polyfills**
62+
63+
Default: `['es6.promise', 'es6.object.assign']`
64+
65+
A list of [core-js](https://github.com/zloirock/core-js) polyfills to force-include when using `useBuiltIns: 'usage'`.
66+
67+
Use this option when you have 3rd party dependencies that are not processed by Babel but have specific polyfill requirements. **These polyfills are automatically excluded if they are not needed for your target environments specified via `browserslist`**.
4968

5069
- **jsx**
5170

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

+11-3
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,32 @@ const defaultOptions = {
77

88
test('polyfill detection', () => {
99
let { code } = babel.transformSync(`
10-
const a = Promise.resolve()
10+
const a = new Map()
1111
`.trim(), {
1212
babelrc: false,
1313
presets: [[preset, {
1414
targets: { node: 'current' }
1515
}]]
1616
})
17+
// default includes
1718
expect(code).not.toMatch(`import "core-js/modules/es6.promise"`)
19+
expect(code).not.toMatch(`import "core-js/modules/es6.object.assign"`)
20+
// usage-based detection
21+
expect(code).not.toMatch(`import "core-js/modules/es6.map"`)
1822

1923
;({ code } = babel.transformSync(`
20-
const a = Promise.resolve()
24+
const a = new Map()
2125
`.trim(), {
2226
babelrc: false,
2327
presets: [[preset, {
2428
targets: { ie: 9 }
2529
}]]
2630
}))
31+
// default includes
2732
expect(code).toMatch(`import "core-js/modules/es6.promise"`)
33+
expect(code).toMatch(`import "core-js/modules/es6.object.assign"`)
34+
// usage-based detection
35+
expect(code).toMatch(`import "core-js/modules/es6.map"`)
2836
})
2937

3038
test('object spread', () => {
@@ -52,7 +60,7 @@ test('async/await', () => {
5260
// should use regenerator runtime
5361
expect(code).toMatch(`import "regenerator-runtime/runtime"`)
5462
// should use required helper instead of inline
55-
expect(code).toMatch(/@babel.*runtime\/helpers\/asyncToGenerator/)
63+
expect(code).toMatch(/@babel.*runtime\/helpers\/.*asyncToGenerator/)
5664
})
5765

5866
test('jsx', () => {

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

+60-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
const path = require('path')
22

3+
const defaultPolyfills = [
4+
'es6.promise',
5+
'es6.object.assign'
6+
]
7+
8+
function getPolyfills (targets, includes, { ignoreBrowserslistConfig, configPath }) {
9+
const { isPluginRequired } = require('@babel/preset-env')
10+
const builtInsList = require('@babel/preset-env/data/built-ins.json')
11+
const getTargets = require('@babel/preset-env/lib/targets-parser').default
12+
const builtInTargets = getTargets(targets, {
13+
ignoreBrowserslistConfig,
14+
configPath
15+
})
16+
17+
return includes.filter(item => {
18+
return isPluginRequired(builtInTargets, builtInsList[item])
19+
})
20+
}
21+
322
module.exports = (context, options = {}) => {
423
const presets = []
524
const plugins = []
@@ -15,23 +34,55 @@ module.exports = (context, options = {}) => {
1534
}
1635

1736
const {
37+
polyfills: userPolyfills,
1838
loose = false,
1939
useBuiltIns = 'usage',
2040
modules = false,
21-
targets,
41+
targets: rawTargets,
42+
spec,
43+
ignoreBrowserslistConfig,
44+
configPath,
45+
include,
46+
exclude,
47+
shippedProposals,
48+
forceAllTransforms,
2249
decoratorsLegacy
2350
} = options
2451

52+
const targets = process.env.VUE_CLI_BABEL_TARGET_NODE
53+
? { node: 'current' }
54+
: rawTargets
55+
56+
// included-by-default polyfills. These are common polyfills that 3rd party
57+
// dependencies may rely on (e.g. Vuex relies on Promise), but since with
58+
// useBuiltIns: 'usage' we won't be running Babel on these deps, they need to
59+
// be force-included.
60+
let polyfills
61+
const buildTarget = process.env.VUE_CLI_TARGET || 'app'
62+
if (buildTarget === 'app' && useBuiltIns === 'usage') {
63+
polyfills = getPolyfills(targets, userPolyfills || defaultPolyfills, {
64+
ignoreBrowserslistConfig,
65+
configPath
66+
})
67+
plugins.push([require('./polyfillsPlugin'), { polyfills }])
68+
} else {
69+
polyfills = []
70+
}
71+
2572
const envOptions = {
73+
spec,
2674
loose,
2775
modules,
2876
targets,
29-
useBuiltIns
30-
}
31-
// target running node version (this is set by unit testing plugins)
32-
if (process.env.VUE_CLI_BABEL_TARGET_NODE) {
33-
envOptions.targets = { node: 'current' }
77+
useBuiltIns,
78+
ignoreBrowserslistConfig,
79+
configPath,
80+
include,
81+
exclude: polyfills.concat(exclude || []),
82+
shippedProposals,
83+
forceAllTransforms
3484
}
85+
3586
// cli-plugin-jest sets this to true because Jest runs without bundling
3687
if (process.env.VUE_CLI_BABEL_TRANSPILE_MODULES) {
3788
envOptions.modules = 'commonjs'
@@ -53,7 +104,9 @@ module.exports = (context, options = {}) => {
53104
// transform runtime, but only for helpers
54105
plugins.push([require('@babel/plugin-transform-runtime'), {
55106
polyfill: false,
56-
regenerator: false,
107+
regenerator: useBuiltIns !== 'usage',
108+
useBuiltIns: useBuiltIns !== false,
109+
useESModules: true,
57110
moduleName: path.dirname(require.resolve('@babel/runtime/package.json'))
58111
}])
59112

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// add polyfill imports to the first file encountered.
2+
module.exports = ({ types }) => {
3+
let entryFile
4+
return {
5+
name: 'vue-cli-inject-polyfills',
6+
visitor: {
7+
Program (path, state) {
8+
if (!entryFile) {
9+
entryFile = state.filename
10+
} else if (state.filename !== entryFile) {
11+
return
12+
}
13+
14+
const { polyfills } = state.opts
15+
const { createImport } = require('@babel/preset-env/lib/utils')
16+
polyfills.forEach(p => {
17+
createImport(path, p)
18+
})
19+
}
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)