diff --git a/packages/@vue/babel-preset-app/__tests__/babel-preset.spec.js b/packages/@vue/babel-preset-app/__tests__/babel-preset.spec.js index 0fdef29d97..1b2832f00f 100644 --- a/packages/@vue/babel-preset-app/__tests__/babel-preset.spec.js +++ b/packages/@vue/babel-preset-app/__tests__/babel-preset.spec.js @@ -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 }) diff --git a/packages/@vue/babel-preset-app/index.js b/packages/@vue/babel-preset-app/index.js index f199db12c0..be8775f33e 100644 --- a/packages/@vue/babel-preset-app/index.js +++ b/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, @@ -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 = {}) => { @@ -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' @@ -62,7 +120,7 @@ module.exports = (context, options = {}) => { bugfixes = true, targets: rawTargets, spec, - ignoreBrowserslistConfig = !!process.env.VUE_CLI_MODERN_BUILD, + ignoreBrowserslistConfig, configPath, include, exclude, @@ -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