Skip to content

Commit

Permalink
refactor: improve the polyfill importing logic of modern mode (#5513)
Browse files Browse the repository at this point in the history
TODO:
- should remove core-js from `dependencies` in the next major.
  • Loading branch information
haoqunjiang authored May 25, 2020
1 parent 538a028 commit 91ca0b1
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 43 deletions.
29 changes: 22 additions & 7 deletions packages/@vue/babel-preset-app/__tests__/babel-preset.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,37 +48,52 @@ test('polyfill detection', () => {
expect(code).toMatch('"core-js/modules/es.map"')
})

test('modern mode always skips polyfills', () => {
test('modern mode always skips unnecessary polyfills', () => {
process.env.VUE_CLI_MODERN_BUILD = true
let { code } = babel.transformSync(`
const a = new Map()
console.log(globalThis)
`.trim(), {
babelrc: false,
presets: [[preset, {
targets: { ie: 9 },
targets: { ie: 9, safari: '12' },
useBuiltIns: 'usage'
}]],
filename: 'test-entry-file.js'
})
// default includes
expect(code).not.toMatch(getAbsolutePolyfill('es.promise'))
// default includes that are supported in all modern browsers should be skipped
expect(code).not.toMatch('es.assign')
// though es.promise is not supported in all modern browsers
// (modern: safari >= 10.1, es.promise: safrai >= 11)
// the custom configuration only expects to support safari >= 12
// so it can be skipped
expect(code).not.toMatch('es.promise"')
// es.promise.finally is supported in safari >= 13.0.3
// so still needs to be included
expect(code).toMatch('es.promise.finally')

// usage-based detection
expect(code).not.toMatch('"core-js/modules/es.map"')
// Map is supported in all modern browsers
expect(code).not.toMatch('es.map')
// globalThis is not supported until safari 12.1
expect(code).toMatch('es.global-this')

;({ code } = babel.transformSync(`
const a = new Map()
`.trim(), {
babelrc: false,
presets: [[preset, {
targets: { ie: 9 },
targets: { ie: 9, safari: '12' },
useBuiltIns: 'entry'
}]],
filename: 'test-entry-file.js'
}))
// default includes
expect(code).not.toMatch(getAbsolutePolyfill('es.promise'))
expect(code).not.toMatch('es.promise"')
expect(code).not.toMatch('es.promise.finally')
// usage-based detection
expect(code).not.toMatch('"core-js/modules/es.map"')
expect(code).not.toMatch('es.global-this')
delete process.env.VUE_CLI_MODERN_BUILD
})

Expand Down
110 changes: 76 additions & 34 deletions packages/@vue/babel-preset-app/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const path = require('path')
const semver = require('semver')

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

function getPolyfills (targets, includes, { ignoreBrowserslistConfig, configPath }) {
const getTargets = require('@babel/helper-compilation-targets').default
const builtInTargets = getTargets(targets, { ignoreBrowserslistConfig, configPath })
const {
default: getTargets,
isRequired
} = require('@babel/helper-compilation-targets')

function getIntersectionTargets (targets, constraintTargets) {
const intersection = Object.keys(constraintTargets).reduce(
(results, browser) => {
// exclude the browsers that the user does not need
if (!targets[browser]) {
return results
}

// if the user-specified version is higher the minimum version that supports esmodule, than use it
results[browser] = semver.gt(
semver.coerce(constraintTargets[browser]),
semver.coerce(targets[browser])
)
? constraintTargets[browser]
: targets[browser]

return results
},
{}
)

return intersection
}

function getModernTargets (targets) {
const allModernTargets = getTargets(
{ esmodules: true },
{ ignoreBrowserslistConfig: true }
)

// use the intersection of modern mode browsers and user defined targets config
return getIntersectionTargets(targets, allModernTargets)
}

function getWCTargets (targets) {
// targeting browsers that at least support ES2015 classes
// https://github.com/babel/babel/blob/v7.9.6/packages/babel-compat-data/data/plugins.json#L194-L204
const allWCTargets = getTargets(
{
browsers: [
'Chrome >= 46',
'Firefox >= 45',
'Safari >= 10',
'Edge >= 13',
'iOS >= 10',
'Electron >= 0.36'
]
},
{ ignoreBrowserslistConfig: true }
)

// use the intersection of browsers supporting Web Components and user defined targets config
return getIntersectionTargets(targets, allWCTargets)
}

function getPolyfills (targets, includes) {
// if no targets specified, include all default polyfills
if (!targets && !Object.keys(builtInTargets).length) {
if (!targets || !Object.keys(targets).length) {
return includes
}

const { list } = require('core-js-compat')({ targets: builtInTargets })
return includes.filter(item => list.includes(item))
const compatData = require('core-js-compat').data
return includes.filter(item => isRequired(item, targets, { compatData }))
}

module.exports = (context, options = {}) => {
Expand All @@ -36,7 +94,7 @@ module.exports = (context, options = {}) => {
// dropping them may break some projects.
// So in the following blocks we don't directly test the `NODE_ENV`.
// Rather, we turn it into the two commonly used feature flags.
if (process.env.NODE_ENV === 'test') {
if (!process.env.VUE_CLI_TEST && process.env.NODE_ENV === 'test') {
// Both Jest & Mocha set NODE_ENV to 'test'.
// And both requires the `node` target.
process.env.VUE_CLI_BABEL_TARGET_NODE = 'true'
Expand All @@ -62,7 +120,7 @@ module.exports = (context, options = {}) => {
bugfixes = true,
targets: rawTargets,
spec,
ignoreBrowserslistConfig = !!process.env.VUE_CLI_MODERN_BUILD,
ignoreBrowserslistConfig,
configPath,
include,
exclude,
Expand All @@ -88,29 +146,17 @@ module.exports = (context, options = {}) => {
version = runtimeVersion
} = options

// resolve targets
let targets
// resolve targets for preset-env
let targets = getTargets(rawTargets, { ignoreBrowserslistConfig, configPath })
if (process.env.VUE_CLI_BABEL_TARGET_NODE) {
// running tests in Node.js
targets = { node: 'current' }
} else if (process.env.VUE_CLI_BUILD_TARGET === 'wc' || process.env.VUE_CLI_BUILD_TARGET === 'wc-async') {
// targeting browsers that at least support ES2015 classes
// https://github.com/babel/babel/blob/master/packages/babel-preset-env/data/plugins.json#L52-L61
targets = {
browsers: [
'Chrome >= 49',
'Firefox >= 45',
'Safari >= 10',
'Edge >= 13',
'iOS >= 10',
'Electron >= 0.36'
]
}
targets = getWCTargets(targets)
} else if (process.env.VUE_CLI_MODERN_BUILD) {
// targeting browsers that support <script type="module">
targets = { esmodules: true }
} else {
targets = rawTargets
// targeting browsers that at least support <script type="module">
targets = getModernTargets(targets)
}

// included-by-default polyfills. These are common polyfills that 3rd party
Expand All @@ -122,13 +168,9 @@ module.exports = (context, options = {}) => {
if (
buildTarget === 'app' &&
useBuiltIns === 'usage' &&
!process.env.VUE_CLI_BABEL_TARGET_NODE &&
!process.env.VUE_CLI_MODERN_BUILD
!process.env.VUE_CLI_BABEL_TARGET_NODE
) {
polyfills = getPolyfills(targets, userPolyfills || defaultPolyfills, {
ignoreBrowserslistConfig,
configPath
})
polyfills = getPolyfills(targets, userPolyfills || defaultPolyfills)
plugins.push([
require('./polyfillsPlugin'),
{ polyfills, entryFiles, useAbsolutePath: !!absoluteRuntime }
Expand All @@ -139,7 +181,7 @@ module.exports = (context, options = {}) => {

const envOptions = {
bugfixes,
corejs: useBuiltIns ? 3 : false,
corejs: useBuiltIns ? require('core-js/package.json').version : false,
spec,
loose,
debug,
Expand Down Expand Up @@ -206,7 +248,7 @@ module.exports = (context, options = {}) => {
presets: [
[require('@babel/preset-env'), {
useBuiltIns,
corejs: useBuiltIns ? 3 : false
corejs: useBuiltIns ? require('core-js/package.json').version : false
}]
]
}]
Expand Down
11 changes: 9 additions & 2 deletions packages/@vue/babel-preset-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,16 @@
"@vue/babel-preset-jsx": "^1.1.2",
"babel-plugin-dynamic-import-node": "^2.3.3",
"core-js": "^3.6.5",
"core-js-compat": "^3.6.5"
"core-js-compat": "^3.6.5",
"semver": "^6.1.0"
},
"peerDependencies": {
"@babel/core": "*"
"@babel/core": "*",
"core-js": "^3"
},
"peerDependenciesMeta": {
"core-js": {
"optional": true
}
}
}
3 changes: 3 additions & 0 deletions packages/@vue/babel-preset-app/polyfillsPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ const { addSideEffect } = require('@babel/helper-module-imports')

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

0 comments on commit 91ca0b1

Please sign in to comment.