Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: improve the polyfill importing logic of modern mode #5513

Merged
merged 6 commits into from May 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 22 additions & 7 deletions packages/@vue/babel-preset-app/__tests__/babel-preset.spec.js
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
@@ -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
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
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