Skip to content

Commit

Permalink
feat(babel): better Babel polyfill defaults
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
yyx990803 committed May 10, 2018
1 parent a8af883 commit 4e7d57f
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 11 deletions.
21 changes: 20 additions & 1 deletion packages/@vue/babel-preset-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This is the default Babel preset used in all Vue CLI projects.
- `targets` is determined:
- using `browserslist` field in `package.json` when building for browsers
- set to `{ node: 'current' }` when running unit tests in Node.js
- Includes `Promise` and `Object.assign` polyfills by default so that they are usable even in non-transpiled dependencies (only for environments that need them)
- [@babel/plugin-transform-runtime](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-runtime)
- Only enabled for helpers since polyfills are handled by `babel-preset-env`
- [dynamic import syntax](https://github.com/tc39/proposal-dynamic-import)
Expand Down Expand Up @@ -45,7 +46,25 @@ This is the default Babel preset used in all Vue CLI projects.

Default: `'usage'`

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.
Explicitly set `useBuiltIns` option for `babel-preset-env`.

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:

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;

2. OR, you can explicitly include the needed polyfills using the [polyfills](#polyfills) option for this preset.

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.

See [babel-preset-env docs](https://github.com/babel/babel/tree/master/packages/babel-preset-env#usebuiltins) for more details.

- **polyfills**

Default: `['es6.promise', 'es6.object.assign']`

A list of [core-js](https://github.com/zloirock/core-js) polyfills to force-include when using `useBuiltIns: 'usage'`.

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`**.

- **jsx**

Expand Down
14 changes: 11 additions & 3 deletions packages/@vue/babel-preset-app/__tests__/babel-preset.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,32 @@ const defaultOptions = {

test('polyfill detection', () => {
let { code } = babel.transformSync(`
const a = Promise.resolve()
const a = new Map()
`.trim(), {
babelrc: false,
presets: [[preset, {
targets: { node: 'current' }
}]]
})
// default includes
expect(code).not.toMatch(`import "core-js/modules/es6.promise"`)
expect(code).not.toMatch(`import "core-js/modules/es6.object.assign"`)
// usage-based detection
expect(code).not.toMatch(`import "core-js/modules/es6.map"`)

;({ code } = babel.transformSync(`
const a = Promise.resolve()
const a = new Map()
`.trim(), {
babelrc: false,
presets: [[preset, {
targets: { ie: 9 }
}]]
}))
// default includes
expect(code).toMatch(`import "core-js/modules/es6.promise"`)
expect(code).toMatch(`import "core-js/modules/es6.object.assign"`)
// usage-based detection
expect(code).toMatch(`import "core-js/modules/es6.map"`)
})

test('object spread', () => {
Expand Down Expand Up @@ -52,7 +60,7 @@ test('async/await', () => {
// should use regenerator runtime
expect(code).toMatch(`import "regenerator-runtime/runtime"`)
// should use required helper instead of inline
expect(code).toMatch(/@babel.*runtime\/helpers\/asyncToGenerator/)
expect(code).toMatch(/@babel.*runtime\/helpers\/.*asyncToGenerator/)
})

test('jsx', () => {
Expand Down
67 changes: 60 additions & 7 deletions packages/@vue/babel-preset-app/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
const path = require('path')

const defaultPolyfills = [
'es6.promise',
'es6.object.assign'
]

function getPolyfills (targets, includes, { ignoreBrowserslistConfig, configPath }) {
const { isPluginRequired } = require('@babel/preset-env')
const builtInsList = require('@babel/preset-env/data/built-ins.json')
const getTargets = require('@babel/preset-env/lib/targets-parser').default
const builtInTargets = getTargets(targets, {
ignoreBrowserslistConfig,
configPath
})

return includes.filter(item => {
return isPluginRequired(builtInTargets, builtInsList[item])
})
}

module.exports = (context, options = {}) => {
const presets = []
const plugins = []
Expand All @@ -15,23 +34,55 @@ module.exports = (context, options = {}) => {
}

const {
polyfills: userPolyfills,
loose = false,
useBuiltIns = 'usage',
modules = false,
targets,
targets: rawTargets,
spec,
ignoreBrowserslistConfig,
configPath,
include,
exclude,
shippedProposals,
forceAllTransforms,
decoratorsLegacy
} = options

const targets = process.env.VUE_CLI_BABEL_TARGET_NODE
? { node: 'current' }
: rawTargets

// included-by-default polyfills. These are common polyfills that 3rd party
// dependencies may rely on (e.g. Vuex relies on Promise), but since with
// useBuiltIns: 'usage' we won't be running Babel on these deps, they need to
// be force-included.
let polyfills
const buildTarget = process.env.VUE_CLI_TARGET || 'app'
if (buildTarget === 'app' && useBuiltIns === 'usage') {
polyfills = getPolyfills(targets, userPolyfills || defaultPolyfills, {
ignoreBrowserslistConfig,
configPath
})
plugins.push([require('./polyfillsPlugin'), { polyfills }])
} else {
polyfills = []
}

const envOptions = {
spec,
loose,
modules,
targets,
useBuiltIns
}
// target running node version (this is set by unit testing plugins)
if (process.env.VUE_CLI_BABEL_TARGET_NODE) {
envOptions.targets = { node: 'current' }
useBuiltIns,
ignoreBrowserslistConfig,
configPath,
include,
exclude: polyfills.concat(exclude || []),
shippedProposals,
forceAllTransforms
}

// cli-plugin-jest sets this to true because Jest runs without bundling
if (process.env.VUE_CLI_BABEL_TRANSPILE_MODULES) {
envOptions.modules = 'commonjs'
Expand All @@ -53,7 +104,9 @@ module.exports = (context, options = {}) => {
// transform runtime, but only for helpers
plugins.push([require('@babel/plugin-transform-runtime'), {
polyfill: false,
regenerator: false,
regenerator: useBuiltIns !== 'usage',
useBuiltIns: useBuiltIns !== false,
useESModules: true,
moduleName: path.dirname(require.resolve('@babel/runtime/package.json'))
}])

Expand Down
22 changes: 22 additions & 0 deletions packages/@vue/babel-preset-app/polyfillsPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// add polyfill imports to the first file encountered.
module.exports = ({ types }) => {
let entryFile
return {
name: 'vue-cli-inject-polyfills',
visitor: {
Program (path, state) {
if (!entryFile) {
entryFile = state.filename
} else if (state.filename !== entryFile) {
return
}

const { polyfills } = state.opts
const { createImport } = require('@babel/preset-env/lib/utils')
polyfills.forEach(p => {
createImport(path, p)
})
}
}
}
}

0 comments on commit 4e7d57f

Please sign in to comment.