diff --git a/.gitignore b/.gitignore index 0c202a1dbd18..e66e4456b432 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,8 @@ dist package-lock.json !/.github/actions/*/package-lock.json +/packages/babel-compat-data/build + /packages/babel-runtime/helpers/*.js !/packages/babel-runtime/helpers/toArray.js !/packages/babel-runtime/helpers/iterableToArray.js diff --git a/.prettierignore b/.prettierignore index e86c7aa505d3..19e8316f03eb 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,7 @@ package.json packages/babel-preset-env/data +packages/babel-compat-data/data +packages/babel-compat-data/scripts/data/overlapping-plugins.js packages/*/test/fixtures/**/input.* packages/*/test/fixtures/**/exec.* packages/*/test/fixtures/**/output.* diff --git a/Gulpfile.js b/Gulpfile.js index a91d4e54b84d..9af1c2a79478 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -181,7 +181,12 @@ function buildRollup(packages) { }, }), rollupCommonJs({ - include: [/node_modules/, "packages/babel-preset-env/data/**"], + include: [ + /node_modules/, + "packages/babel-preset-env/data/*.js", + // Rollup doesn't read export maps, so it loads the cjs fallback + "packages/babel-compat-data/*.js", + ], namedExports: { "babel-plugin-dynamic-import-node/utils.js": [ "createDynamicImportTransform", diff --git a/babel.config.js b/babel.config.js index 367b87d714da..5102c9141d9d 100644 --- a/babel.config.js +++ b/babel.config.js @@ -30,24 +30,16 @@ module.exports = function(api) { switch (env) { // Configs used during bundling builds. case "rollup": - convertESM = false; - ignoreLib = false; - // rollup-commonjs will converts node_modules to ESM - unambiguousSources.push( - "**/node_modules", - // todo: remove this after it is rewritten into ESM - "packages/babel-preset-env/data" - ); envOpts.targets = { node: nodeVersion, }; - break; case "standalone": convertESM = false; ignoreLib = false; unambiguousSources.push( "**/node_modules", - "packages/babel-preset-env/data" + "packages/babel-preset-env/data", + "packages/babel-compat-data" ); // targets to browserslists: defaults break; diff --git a/packages/babel-compat-data/.npmignore b/packages/babel-compat-data/.npmignore new file mode 100644 index 000000000000..57e1671459aa --- /dev/null +++ b/packages/babel-compat-data/.npmignore @@ -0,0 +1,4 @@ +scripts +src +test +*.log diff --git a/packages/babel-compat-data/corejs2-built-ins.js b/packages/babel-compat-data/corejs2-built-ins.js new file mode 100644 index 000000000000..ccbe72f0d58b --- /dev/null +++ b/packages/babel-compat-data/corejs2-built-ins.js @@ -0,0 +1,4 @@ +// Node < 13.3 doesn't support export maps in package.json. +// Use this proxy file as a fallback. + +module.exports = require("./data/corejs2-built-ins.json"); diff --git a/packages/babel-compat-data/data/built-in-modules.json b/packages/babel-compat-data/data/built-in-modules.json new file mode 100644 index 000000000000..7e33f27eb290 --- /dev/null +++ b/packages/babel-compat-data/data/built-in-modules.json @@ -0,0 +1,12 @@ +{ + "es6.module": { + "edge": "16", + "firefox": "60", + "chrome": "61", + "safari": "10.1", + "opera": "48", + "ios_saf": "10.3", + "and_chr": "74", + "and_ff": "66" + } +} diff --git a/packages/babel-preset-env/data/corejs2-built-ins.json b/packages/babel-compat-data/data/corejs2-built-ins.json similarity index 100% rename from packages/babel-preset-env/data/corejs2-built-ins.json rename to packages/babel-compat-data/data/corejs2-built-ins.json diff --git a/packages/babel-preset-env/data/built-in-modules.json b/packages/babel-compat-data/data/native-modules.json similarity index 100% rename from packages/babel-preset-env/data/built-in-modules.json rename to packages/babel-compat-data/data/native-modules.json diff --git a/packages/babel-compat-data/data/overlapping-plugins.json b/packages/babel-compat-data/data/overlapping-plugins.json new file mode 100644 index 000000000000..2cd7597e6081 --- /dev/null +++ b/packages/babel-compat-data/data/overlapping-plugins.json @@ -0,0 +1,3 @@ +{ + "transform-regenerator": [] +} \ No newline at end of file diff --git a/packages/babel-preset-env/data/plugins.json b/packages/babel-compat-data/data/plugins.json similarity index 100% rename from packages/babel-preset-env/data/plugins.json rename to packages/babel-compat-data/data/plugins.json diff --git a/packages/babel-compat-data/native-modules.js b/packages/babel-compat-data/native-modules.js new file mode 100644 index 000000000000..efa3031c0a53 --- /dev/null +++ b/packages/babel-compat-data/native-modules.js @@ -0,0 +1,4 @@ +// Node < 13.3 doesn't support export maps in package.json. +// Use this proxy file as a fallback. + +module.exports = require("./data/native-modules.json"); diff --git a/packages/babel-compat-data/overlapping-plugins.js b/packages/babel-compat-data/overlapping-plugins.js new file mode 100644 index 000000000000..9f7d8efde56f --- /dev/null +++ b/packages/babel-compat-data/overlapping-plugins.js @@ -0,0 +1,4 @@ +// Node < 13.3 doesn't support export maps in package.json. +// Use this proxy file as a fallback. + +module.exports = require("./data/overlapping-plugins.json"); diff --git a/packages/babel-compat-data/package.json b/packages/babel-compat-data/package.json new file mode 100644 index 000000000000..112e1058cfdf --- /dev/null +++ b/packages/babel-compat-data/package.json @@ -0,0 +1,36 @@ +{ + "name": "@babel/compat-data", + "version": "0.0.0", + "author": "The Babel Team (https://babeljs.io/team)", + "license": "MIT", + "description": "", + "repository": "https://github.com/babel/babel/tree/master/packages/babel-compat-data", + "publishConfig": { + "access": "public" + }, + "exports": { + "./plugins": "./data/plugins.json", + "./native-modules": "./data/native-modules.json", + "./corejs2-built-ins": "./data/corejs2-built-ins.json", + "./overlapping-plugins": "./data/overlapping-plugins.json" + }, + "scripts": { + "build-data": "./scripts/download-compat-table.sh; node ./scripts/build-data.js; node ./scripts/build-modules-support.js; node ./scripts/build-overlapping-plugins.js" + }, + "keywords": [ + "babel", + "compat-table", + "compat-data" + ], + "dependencies": { + "browserslist": "^4.8.2", + "invariant": "^2.2.4", + "semver": "^7.1.1" + }, + "devDependencies": { + "caniuse-db": "1.0.30000969", + "electron-to-chromium": "1.3.113", + "lodash": "^4.17.15", + "@babel/helper-compilation-targets": "^0.0.0" + } +} diff --git a/packages/babel-compat-data/plugins.js b/packages/babel-compat-data/plugins.js new file mode 100644 index 000000000000..74a9d60f2310 --- /dev/null +++ b/packages/babel-compat-data/plugins.js @@ -0,0 +1,4 @@ +// Node < 13.3 doesn't support export maps in package.json. +// Use this proxy file as a fallback. + +module.exports = require("./data/plugins.json"); diff --git a/packages/babel-preset-env/scripts/build-data.js b/packages/babel-compat-data/scripts/build-data.js similarity index 98% rename from packages/babel-preset-env/scripts/build-data.js rename to packages/babel-compat-data/scripts/build-data.js index e8bb4b1db54f..b4ac76c25c4e 100644 --- a/packages/babel-preset-env/scripts/build-data.js +++ b/packages/babel-compat-data/scripts/build-data.js @@ -7,7 +7,7 @@ const flattenDeep = require("lodash/flattenDeep"); const isEqual = require("lodash/isEqual"); const mapValues = require("lodash/mapValues"); const pickBy = require("lodash/pickBy"); -const unreleasedLabels = require("../data/unreleased-labels"); +const { unreleasedLabels } = require("babel/helper-compilation-targets"); const electronToChromiumVersions = require("electron-to-chromium").versions; const electronToChromiumKeys = Object.keys( @@ -293,7 +293,7 @@ const generateData = (environments, features) => { ["plugin", "corejs2-built-in"].forEach(target => { const newData = generateData( environments, - require(`../data/${target}-features`) + require(`./data/${target}-features`) ); const dataPath = path.join(__dirname, `../data/${target}s.json`); diff --git a/packages/babel-preset-env/scripts/build-modules-support.js b/packages/babel-compat-data/scripts/build-modules-support.js similarity index 100% rename from packages/babel-preset-env/scripts/build-modules-support.js rename to packages/babel-compat-data/scripts/build-modules-support.js diff --git a/packages/babel-compat-data/scripts/build-overlapping-plugins.js b/packages/babel-compat-data/scripts/build-overlapping-plugins.js new file mode 100644 index 000000000000..24e34cfeb92e --- /dev/null +++ b/packages/babel-compat-data/scripts/build-overlapping-plugins.js @@ -0,0 +1,15 @@ +"use strict"; + +const fs = require("fs"); +const overlappingPlugins = require("./data/overlapping-plugins"); + +fs.writeFileSync( + __dirname + "/../data/overlapping-plugins.json", + JSON.stringify(overlappingPlugins, replacer, 2) +); + +function replacer(key, val) { + if (val instanceof Set) return Array.from(val); + if (val instanceof Map) return Object.fromEntries(val); + return val; +} diff --git a/packages/babel-preset-env/data/corejs2-built-in-features.js b/packages/babel-compat-data/scripts/data/corejs2-built-in-features.js similarity index 85% rename from packages/babel-preset-env/data/corejs2-built-in-features.js rename to packages/babel-compat-data/scripts/data/corejs2-built-in-features.js index df73164fb6a7..2beb9bbed4c8 100644 --- a/packages/babel-preset-env/data/corejs2-built-in-features.js +++ b/packages/babel-compat-data/scripts/data/corejs2-built-in-features.js @@ -26,7 +26,7 @@ const typedArrayMethods = [ "typed arrays / %TypedArray%[Symbol.species]", ]; -const es = { +module.exports = { // compat-table missing babel6 mapping // "es6.array.concat": { // features: [ @@ -34,7 +34,8 @@ const es = { // "well-known symbols / Symbol.species, Array.prototype.concat", // ] // }, - "es6.array.copy-within": "Array.prototype methods / Array.prototype.copyWithin", + "es6.array.copy-within": + "Array.prototype methods / Array.prototype.copyWithin", "es6.array.every": "Array methods / Array.prototype.every", "es6.array.fill": "Array.prototype methods / Array.prototype.fill", "es6.array.filter": { @@ -46,7 +47,8 @@ const es = { }, "es6.array.find": "Array.prototype methods / Array.prototype.find", "es6.array.find-index": "Array.prototype methods / Array.prototype.findIndex", - "es7.array.flat-map": "Array.prototype.{flat, flatMap} / Array.prototype.flatMap", + "es7.array.flat-map": + "Array.prototype.{flat, flatMap} / Array.prototype.flatMap", "es6.array.for-each": "Array methods / Array.prototype.forEach", "es6.array.from": "Array static methods / Array.from", "es7.array.includes": "Array.prototype.includes", @@ -90,8 +92,8 @@ const es = { "es6.function.has-instance": "well-known symbols / Symbol.hasInstance", "es6.function.name": { features: [ - "function \"name\" property / function statements", - "function \"name\" property / function expressions", + 'function "name" property / function statements', + 'function "name" property / function expressions', ], }, @@ -174,13 +176,19 @@ const es = { ], }, "es6.object.define-property": "Object static methods / Object.defineProperty", - "es6.object.define-properties": "Object static methods / Object.defineProperties", + "es6.object.define-properties": + "Object static methods / Object.defineProperties", "es7.object.entries": "Object static methods / Object.entries", - "es6.object.freeze": "Object static methods accept primitives / Object.freeze", - "es6.object.get-own-property-descriptor": "Object static methods accept primitives / Object.getOwnPropertyDescriptor", - "es7.object.get-own-property-descriptors": "Object static methods / Object.getOwnPropertyDescriptors", - "es6.object.get-own-property-names": "Object static methods accept primitives / Object.getOwnPropertyNames", - "es6.object.get-prototype-of": "Object static methods accept primitives / Object.getPrototypeOf", + "es6.object.freeze": + "Object static methods accept primitives / Object.freeze", + "es6.object.get-own-property-descriptor": + "Object static methods accept primitives / Object.getOwnPropertyDescriptor", + "es7.object.get-own-property-descriptors": + "Object static methods / Object.getOwnPropertyDescriptors", + "es6.object.get-own-property-names": + "Object static methods accept primitives / Object.getOwnPropertyNames", + "es6.object.get-prototype-of": + "Object static methods accept primitives / Object.getPrototypeOf", "es7.object.lookup-getter": { features: [ "Object.prototype getter/setter methods / __lookupGetter__", @@ -199,15 +207,20 @@ const es = { "Object.prototype getter/setter methods / __lookupSetter__, data properties can shadow accessors", ], }, - "es6.object.prevent-extensions": "Object static methods accept primitives / Object.preventExtensions", + "es6.object.prevent-extensions": + "Object static methods accept primitives / Object.preventExtensions", "es6.object.to-string": "well-known symbols / Symbol.toStringTag", "es6.object.is": "Object static methods / Object.is", - "es6.object.is-frozen": "Object static methods accept primitives / Object.isFrozen", - "es6.object.is-sealed": "Object static methods accept primitives / Object.isSealed", - "es6.object.is-extensible": "Object static methods accept primitives / Object.isExtensible", + "es6.object.is-frozen": + "Object static methods accept primitives / Object.isFrozen", + "es6.object.is-sealed": + "Object static methods accept primitives / Object.isSealed", + "es6.object.is-extensible": + "Object static methods accept primitives / Object.isExtensible", "es6.object.keys": "Object static methods accept primitives / Object.keys", "es6.object.seal": "Object static methods accept primitives / Object.seal", - "es6.object.set-prototype-of": "Object static methods / Object.setPrototypeOf", + "es6.object.set-prototype-of": + "Object static methods / Object.setPrototypeOf", "es7.object.values": "Object static methods / Object.values", "es6.promise": { @@ -224,7 +237,8 @@ const es = { "es6.reflect.define-property": "Reflect / Reflect.defineProperty", "es6.reflect.delete-property": "Reflect / Reflect.deleteProperty", "es6.reflect.get": "Reflect / Reflect.get", - "es6.reflect.get-own-property-descriptor": "Reflect / Reflect.getOwnPropertyDescriptor", + "es6.reflect.get-own-property-descriptor": + "Reflect / Reflect.getOwnPropertyDescriptor", "es6.reflect.get-prototype-of": "Reflect / Reflect.getPrototypeOf", "es6.reflect.has": "Reflect / Reflect.has", "es6.reflect.is-extensible": "Reflect / Reflect.isExtensible", @@ -240,11 +254,16 @@ const es = { ], }, "es6.regexp.flags": "RegExp.prototype properties / RegExp.prototype.flags", - "es6.regexp.match": "RegExp.prototype properties / RegExp.prototype[Symbol.match]", - "es6.regexp.replace": "RegExp.prototype properties / RegExp.prototype[Symbol.replace]", - "es6.regexp.split": "RegExp.prototype properties / RegExp.prototype[Symbol.split]", - "es6.regexp.search": "RegExp.prototype properties / RegExp.prototype[Symbol.search]", - "es6.regexp.to-string": "miscellaneous / RegExp.prototype.toString generic and uses \"flags\" property", + "es6.regexp.match": + "RegExp.prototype properties / RegExp.prototype[Symbol.match]", + "es6.regexp.replace": + "RegExp.prototype properties / RegExp.prototype[Symbol.replace]", + "es6.regexp.split": + "RegExp.prototype properties / RegExp.prototype[Symbol.split]", + "es6.regexp.search": + "RegExp.prototype properties / RegExp.prototype[Symbol.search]", + "es6.regexp.to-string": + 'miscellaneous / RegExp.prototype.toString generic and uses "flags" property', // This is explicit due to prevent the stage-1 Set proposals under the // category "Set methods" from being included. @@ -295,15 +314,18 @@ const es = { "es6.string.big": "String.prototype HTML methods", "es6.string.blink": "String.prototype HTML methods", "es6.string.bold": "String.prototype HTML methods", - "es6.string.code-point-at": "String.prototype methods / String.prototype.codePointAt", - "es6.string.ends-with": "String.prototype methods / String.prototype.endsWith", + "es6.string.code-point-at": + "String.prototype methods / String.prototype.codePointAt", + "es6.string.ends-with": + "String.prototype methods / String.prototype.endsWith", "es6.string.fixed": "String.prototype HTML methods", "es6.string.fontcolor": "String.prototype HTML methods", "es6.string.fontsize": "String.prototype HTML methods", "es6.string.from-code-point": "String static methods / String.fromCodePoint", "es6.string.includes": "String.prototype methods / String.prototype.includes", "es6.string.italics": "String.prototype HTML methods", - "es6.string.iterator": "String.prototype methods / String.prototype[Symbol.iterator]", + "es6.string.iterator": + "String.prototype methods / String.prototype[Symbol.iterator]", "es6.string.link": "String.prototype HTML methods", // "String.prototype methods / String.prototype.normalize" not implemented "es7.string.pad-start": "String padding / String.prototype.padStart", @@ -311,7 +333,8 @@ const es = { "es6.string.raw": "String static methods / String.raw", "es6.string.repeat": "String.prototype methods / String.prototype.repeat", "es6.string.small": "String.prototype HTML methods", - "es6.string.starts-with": "String.prototype methods / String.prototype.startsWith", + "es6.string.starts-with": + "String.prototype methods / String.prototype.startsWith", "es6.string.strike": "String.prototype HTML methods", "es6.string.sub": "String.prototype HTML methods", "es6.string.sup": "String.prototype HTML methods", @@ -353,7 +376,3 @@ const es = { "es6.weak-set": "WeakSet", }; - -const proposals = require("./shipped-proposals").builtIns; - -module.exports = Object.assign({}, es, proposals); diff --git a/packages/babel-preset-env/data/overlapping-plugins.js b/packages/babel-compat-data/scripts/data/overlapping-plugins.js similarity index 71% rename from packages/babel-preset-env/data/overlapping-plugins.js rename to packages/babel-compat-data/scripts/data/overlapping-plugins.js index e4e9904f90b0..56cb8d896d6f 100644 --- a/packages/babel-preset-env/data/overlapping-plugins.js +++ b/packages/babel-compat-data/scripts/data/overlapping-plugins.js @@ -4,13 +4,13 @@ module.exports = new Map(); // async -> regenerator is better than async -> generator -> regenerator ifIncluded("transform-regenerator") - // https://github.com/babel/babel/issues/10678 + // Temporarly disabled: https://github.com/babel/babel/issues/10678 // .isUnnecessary("transform-async-to-generator"); function ifIncluded(name) { const set = new Set(); module.exports.set(name, set); return { - isUnnecessary(name) { set.add(name); return this; } + isUnnecessary(name) { set.add(name); return this; }, }; } diff --git a/packages/babel-preset-env/data/plugin-features.js b/packages/babel-compat-data/scripts/data/plugin-features.js similarity index 86% rename from packages/babel-preset-env/data/plugin-features.js rename to packages/babel-compat-data/scripts/data/plugin-features.js index 3a84be381683..df9a0f174dd4 100644 --- a/packages/babel-preset-env/data/plugin-features.js +++ b/packages/babel-compat-data/scripts/data/plugin-features.js @@ -1,4 +1,4 @@ -const es = { +module.exports = { "transform-template-literals": { features: ["template literals"], }, @@ -65,10 +65,7 @@ const es = { ], }, "transform-destructuring": { - features: [ - "destructuring, assignment", - "destructuring, declarations", - ], + features: ["destructuring, assignment", "destructuring, declarations"], }, "transform-block-scoping": { features: ["const", "let"], @@ -98,11 +95,9 @@ const es = { "proposal-json-strings": "JSON superset", "proposal-optional-catch-binding": "optional catch binding", "transform-named-capturing-groups-regex": "RegExp named capture groups", - "transform-member-expression-literals": "Object/array literal extensions / Reserved words as property names", - "transform-property-literals": "Object/array literal extensions / Reserved words as property names", + "transform-member-expression-literals": + "Object/array literal extensions / Reserved words as property names", + "transform-property-literals": + "Object/array literal extensions / Reserved words as property names", "transform-reserved-words": "Miscellaneous / Unreserved words", }; - -const proposals = require("./shipped-proposals").features; - -module.exports = Object.assign({}, es, proposals); diff --git a/packages/babel-preset-env/scripts/download-compat-table.sh b/packages/babel-compat-data/scripts/download-compat-table.sh similarity index 100% rename from packages/babel-preset-env/scripts/download-compat-table.sh rename to packages/babel-compat-data/scripts/download-compat-table.sh diff --git a/packages/babel-helper-compilation-targets/.npmignore b/packages/babel-helper-compilation-targets/.npmignore new file mode 100644 index 000000000000..f9806945836e --- /dev/null +++ b/packages/babel-helper-compilation-targets/.npmignore @@ -0,0 +1,3 @@ +src +test +*.log diff --git a/packages/babel-helper-compilation-targets/package.json b/packages/babel-helper-compilation-targets/package.json new file mode 100644 index 000000000000..251a902a20aa --- /dev/null +++ b/packages/babel-helper-compilation-targets/package.json @@ -0,0 +1,32 @@ +{ + "name": "@babel/helper-compilation-targets", + "version": "0.0.0", + "author": "The Babel Team (https://babeljs.io/team)", + "license": "MIT", + "description": "Engine compat data used in @babel/preset-env", + "repository": "https://github.com/babel/babel/tree/master/packages/babel-helper-compilation-targets", + "main": "lib/index.js", + "type": "commonjs", + "exports": false, + "publishConfig": { + "access": "public" + }, + "keywords": [ + "babel", + "babel-plugin" + ], + "dependencies": { + "@babel/compat-data": "^0.0.0", + "browserslist": "^4.8.2", + "levenary": "^1.1.0", + "invariant": "^2.2.4", + "semver": "^7.1.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + }, + "devDependencies": { + "@babel/core": "^7.7.5", + "@babel/helper-plugin-test-runner": "^7.7.4" + } +} diff --git a/packages/babel-helper-compilation-targets/src/debug.js b/packages/babel-helper-compilation-targets/src/debug.js new file mode 100644 index 000000000000..aec840d2eba2 --- /dev/null +++ b/packages/babel-helper-compilation-targets/src/debug.js @@ -0,0 +1,40 @@ +// @flow + +import semver from "semver"; +import { prettifyVersion } from "./pretty"; +import { + semverify, + isUnreleasedVersion, + getLowestImplementedVersion, +} from "./utils"; +import type { Targets } from "./types"; + +export function getInclusionReasons( + item: string, + targetVersions: Targets, + list: { [key: string]: Targets }, +) { + const minVersions = list[item] || {}; + + return Object.keys(targetVersions).reduce((result, env) => { + const minVersion = getLowestImplementedVersion(minVersions, env); + const targetVersion = targetVersions[env]; + + if (!minVersion) { + result[env] = prettifyVersion(targetVersion); + } else { + const minIsUnreleased = isUnreleasedVersion(minVersion, env); + const targetIsUnreleased = isUnreleasedVersion(targetVersion, env); + + if ( + !targetIsUnreleased && + (minIsUnreleased || + semver.lt(targetVersion.toString(), semverify(minVersion))) + ) { + result[env] = prettifyVersion(targetVersion); + } + } + + return result; + }, {}); +} diff --git a/packages/babel-helper-compilation-targets/src/filter-items.js b/packages/babel-helper-compilation-targets/src/filter-items.js new file mode 100644 index 000000000000..ac8809433bb9 --- /dev/null +++ b/packages/babel-helper-compilation-targets/src/filter-items.js @@ -0,0 +1,111 @@ +// @flow + +import semver from "semver"; + +import pluginsCompatData from "@babel/compat-data/plugins"; + +import type { Targets } from "./types"; +import { + getLowestImplementedVersion, + isUnreleasedVersion, + semverify, +} from "./utils"; + +export function targetsSupported(target: Targets, support: Targets) { + const targetEnvironments = Object.keys(target); + + if (targetEnvironments.length === 0) { + return false; + } + + const unsupportedEnvironments = targetEnvironments.filter(environment => { + const lowestImplementedVersion = getLowestImplementedVersion( + support, + environment, + ); + + // Feature is not implemented in that environment + if (!lowestImplementedVersion) { + return true; + } + + const lowestTargetedVersion = target[environment]; + + // If targets has unreleased value as a lowest version, then don't require a plugin. + if (isUnreleasedVersion(lowestTargetedVersion, environment)) { + return false; + } + + // Include plugin if it is supported in the unreleased environment, which wasn't specified in targets + if (isUnreleasedVersion(lowestImplementedVersion, environment)) { + return true; + } + + if (!semver.valid(lowestTargetedVersion.toString())) { + throw new Error( + `Invalid version passed for target "${environment}": "${lowestTargetedVersion}". ` + + "Versions must be in semver format (major.minor.patch)", + ); + } + + return semver.gt( + semverify(lowestImplementedVersion), + lowestTargetedVersion.toString(), + ); + }); + + return unsupportedEnvironments.length === 0; +} + +export function isRequired( + name: string, + targets: Targets, + { + compatData = pluginsCompatData, + includes, + excludes, + }: { + compatData?: { [feature: string]: Targets }, + includes?: Set, + excludes?: Set, + } = {}, +) { + if (excludes && excludes.has(name)) return false; + if (includes && includes.has(name)) return true; + return !targetsSupported(targets, compatData[name]); +} + +export default function filterItems( + list: { [feature: string]: Targets }, + includes: Set, + excludes: Set, + targets: Targets, + defaultIncludes: Array | null, + defaultExcludes?: Array | null, + pluginSyntaxMap?: Map, +) { + const result = new Set(); + const options = { compatData: list, includes, excludes }; + + for (const item in list) { + if (isRequired(item, targets, options)) { + result.add(item); + } else if (pluginSyntaxMap) { + const shippedProposalsSyntax = pluginSyntaxMap.get(item); + + if (shippedProposalsSyntax) { + result.add(shippedProposalsSyntax); + } + } + } + + if (defaultIncludes) { + defaultIncludes.forEach(item => !excludes.has(item) && result.add(item)); + } + + if (defaultExcludes) { + defaultExcludes.forEach(item => !includes.has(item) && result.delete(item)); + } + + return result; +} diff --git a/packages/babel-helper-compilation-targets/src/index.js b/packages/babel-helper-compilation-targets/src/index.js new file mode 100644 index 000000000000..747eb431211c --- /dev/null +++ b/packages/babel-helper-compilation-targets/src/index.js @@ -0,0 +1,256 @@ +// @flow + +import browserslist from "browserslist"; +import findSuggestion from "levenary"; +import invariant from "invariant"; +import browserModulesData from "@babel/compat-data/native-modules"; + +import { + semverify, + semverMin, + isUnreleasedVersion, + getLowestUnreleased, +} from "./utils"; +import { browserNameMap } from "./targets"; +import { TargetNames } from "./options"; +import type { Targets } from "./types"; + +export type { Targets }; + +export { prettifyTargets } from "./pretty"; +export { getInclusionReasons } from "./debug"; +export { default as filterItems, isRequired } from "./filter-items"; +export { unreleasedLabels } from "./targets"; + +const browserslistDefaults = browserslist.defaults; + +const validBrowserslistTargets = [ + ...Object.keys(browserslist.data), + ...Object.keys(browserslist.aliases), +]; + +function objectToBrowserslist(object: Targets): Array { + return Object.keys(object).reduce((list, targetName) => { + if (validBrowserslistTargets.indexOf(targetName) >= 0) { + const targetVersion = object[targetName]; + return list.concat(`${targetName} ${targetVersion}`); + } + return list; + }, []); +} + +function validateTargetNames(targets: Targets): void { + const validTargets = Object.keys(TargetNames); + for (const target in targets) { + if (!TargetNames[target]) { + throw new Error( + `Invalid Option: '${target}' is not a valid target + Maybe you meant to use '${findSuggestion(target, validTargets)}'?`, + ); + } + } +} + +export function isBrowsersQueryValid( + browsers: string | Array | Targets, +): boolean { + return typeof browsers === "string" || Array.isArray(browsers); +} + +function validateBrowsers(browsers) { + invariant( + typeof browsers === "undefined" || isBrowsersQueryValid(browsers), + `Invalid Option: '${browsers}' is not a valid browserslist query`, + ); + + return browsers; +} + +function mergeBrowsers(fromQuery: Targets, fromTarget: Targets) { + return Object.keys(fromTarget).reduce((queryObj, targKey) => { + if (targKey !== TargetNames.browsers) { + queryObj[targKey] = fromTarget[targKey]; + } + return queryObj; + }, fromQuery); +} + +function getLowestVersions(browsers: Array): Targets { + return browsers.reduce((all: Object, browser: string): Object => { + const [browserName, browserVersion] = browser.split(" "); + const normalizedBrowserName = browserNameMap[browserName]; + + if (!normalizedBrowserName) { + return all; + } + + try { + // Browser version can return as "10.0-10.2" + const splitVersion = browserVersion.split("-")[0].toLowerCase(); + const isSplitUnreleased = isUnreleasedVersion(splitVersion, browserName); + + if (!all[normalizedBrowserName]) { + all[normalizedBrowserName] = isSplitUnreleased + ? splitVersion + : semverify(splitVersion); + return all; + } + + const version = all[normalizedBrowserName]; + const isUnreleased = isUnreleasedVersion(version, browserName); + + if (isUnreleased && isSplitUnreleased) { + all[normalizedBrowserName] = getLowestUnreleased( + version, + splitVersion, + browserName, + ); + } else if (isUnreleased) { + all[normalizedBrowserName] = semverify(splitVersion); + } else if (!isUnreleased && !isSplitUnreleased) { + const parsedBrowserVersion = semverify(splitVersion); + + all[normalizedBrowserName] = semverMin(version, parsedBrowserVersion); + } + } catch (e) {} + + return all; + }, {}); +} + +function outputDecimalWarning(decimalTargets: Array): void { + if (!decimalTargets || !decimalTargets.length) { + return; + } + + console.log("Warning, the following targets are using a decimal version:"); + console.log(""); + decimalTargets.forEach(({ target, value }) => + console.log(` ${target}: ${value}`), + ); + console.log(""); + console.log( + "We recommend using a string for minor/patch versions to avoid numbers like 6.10", + ); + console.log("getting parsed as 6.1, which can lead to unexpected behavior."); + console.log(""); +} + +function semverifyTarget(target, value) { + try { + return semverify(value); + } catch (error) { + throw new Error( + `Invalid Option: '${value}' is not a valid value for 'targets.${target}'.`, + ); + } +} + +const targetParserMap = { + __default(target, value) { + const version = isUnreleasedVersion(value, target) + ? value.toLowerCase() + : semverifyTarget(target, value); + return [target, version]; + }, + + // Parse `node: true` and `node: "current"` to version + node(target, value) { + const parsed = + value === true || value === "current" + ? process.versions.node + : semverifyTarget(target, value); + return [target, parsed]; + }, +}; + +type ParsedResult = { + targets: Targets, + decimalWarnings: Array, +}; + +export default function getTargets( + targets: Object = {}, + options: Object = {}, +): Targets { + const targetOpts: Targets = {}; + + validateTargetNames(targets); + + // `esmodules` as a target indicates the specific set of browsers supporting ES Modules. + // These values OVERRIDE the `browsers` field. + if (targets.esmodules) { + const supportsESModules = browserModulesData["es6.module"]; + targets.browsers = Object.keys(supportsESModules) + .map(browser => `${browser} ${supportsESModules[browser]}`) + .join(", "); + } + + // Parse browsers target via browserslist + const browsersquery = validateBrowsers(targets.browsers); + + const hasTargets = Object.keys(targets).length > 0; + const shouldParseBrowsers = !!targets.browsers; + const shouldSearchForConfig = + !options.ignoreBrowserslistConfig && !hasTargets; + + if (shouldParseBrowsers || shouldSearchForConfig) { + // If no targets are passed, we need to overwrite browserslist's defaults + // so that we enable all transforms (acting like the now deprecated + // preset-latest). + // + // Note, if browserslist resolves the config (ex. package.json), then usage + // of `defaults` in queries will be different since we don't want to break + // the behavior of "no targets is the same as preset-latest". + if (!hasTargets) { + browserslist.defaults = objectToBrowserslist(targets); + } + + const browsers = browserslist(browsersquery, { + path: options.configPath, + mobileToDesktop: true, + }); + + const queryBrowsers = getLowestVersions(browsers); + targets = mergeBrowsers(queryBrowsers, targets); + + // Reset browserslist defaults + browserslist.defaults = browserslistDefaults; + } + + // Parse remaining targets + const parsed = Object.keys(targets) + .filter(value => value !== TargetNames.esmodules) + .sort() + .reduce( + (results: ParsedResult, target: string): ParsedResult => { + if (target !== TargetNames.browsers) { + const value = targets[target]; + + // Warn when specifying minor/patch as a decimal + if (typeof value === "number" && value % 1 !== 0) { + results.decimalWarnings.push({ target, value }); + } + + // Check if we have a target parser? + const parser = targetParserMap[target] || targetParserMap.__default; + const [parsedTarget, parsedValue] = parser(target, value); + + if (parsedValue) { + // Merge (lowest wins) + results.targets[parsedTarget] = parsedValue; + } + } + + return results; + }, + { + targets: targetOpts, + decimalWarnings: [], + }, + ); + + outputDecimalWarning(parsed.decimalWarnings); + + return parsed.targets; +} diff --git a/packages/babel-helper-compilation-targets/src/options.js b/packages/babel-helper-compilation-targets/src/options.js new file mode 100644 index 000000000000..eadc8dbd43c2 --- /dev/null +++ b/packages/babel-helper-compilation-targets/src/options.js @@ -0,0 +1,20 @@ +// @flow + +export const TargetNames = { + esmodules: "esmodules", + node: "node", + browsers: "browsers", + chrome: "chrome", + opera: "opera", + edge: "edge", + firefox: "firefox", + safari: "safari", + ie: "ie", + ios: "ios", + android: "android", + electron: "electron", + samsung: "samsung", + + // deprecated + uglify: "uglify", +}; diff --git a/packages/babel-helper-compilation-targets/src/pretty.js b/packages/babel-helper-compilation-targets/src/pretty.js new file mode 100644 index 000000000000..bc8bdd96c148 --- /dev/null +++ b/packages/babel-helper-compilation-targets/src/pretty.js @@ -0,0 +1,36 @@ +import semver from "semver"; +import { unreleasedLabels } from "./targets"; + +export function prettifyVersion(version: string) { + if (typeof version !== "string") { + return version; + } + + const parts = [semver.major(version)]; + const minor = semver.minor(version); + const patch = semver.patch(version); + + if (minor || patch) { + parts.push(minor); + } + + if (patch) { + parts.push(patch); + } + + return parts.join("."); +} + +export function prettifyTargets(targets: Targets): Targets { + return Object.keys(targets).reduce((results, target) => { + let value = targets[target]; + + const unreleasedLabel = unreleasedLabels[target]; + if (typeof value === "string" && unreleasedLabel !== value) { + value = prettifyVersion(value); + } + + results[target] = value; + return results; + }, {}); +} diff --git a/packages/babel-helper-compilation-targets/src/targets.js b/packages/babel-helper-compilation-targets/src/targets.js new file mode 100644 index 000000000000..401d7904a995 --- /dev/null +++ b/packages/babel-helper-compilation-targets/src/targets.js @@ -0,0 +1,20 @@ +export const unreleasedLabels = { + safari: "tp", +}; + +export const browserNameMap = { + and_chr: "chrome", + and_ff: "firefox", + android: "android", + chrome: "chrome", + edge: "edge", + firefox: "firefox", + ie: "ie", + ie_mob: "ie", + ios_saf: "ios", + node: "node", + op_mob: "opera", + opera: "opera", + safari: "safari", + samsung: "samsung", +}; diff --git a/packages/babel-helper-compilation-targets/src/types.js b/packages/babel-helper-compilation-targets/src/types.js new file mode 100644 index 000000000000..28b4c2b3f34d --- /dev/null +++ b/packages/babel-helper-compilation-targets/src/types.js @@ -0,0 +1,19 @@ +// @flow + +// Targets +export type Target = + | "node" + | "chrome" + | "opera" + | "edge" + | "firefox" + | "safari" + | "ie" + | "ios" + | "android" + | "electron" + | "samsung"; + +export type Targets = { + [target: Target]: string, +}; diff --git a/packages/babel-helper-compilation-targets/src/utils.js b/packages/babel-helper-compilation-targets/src/utils.js new file mode 100644 index 000000000000..31eeae8c8e23 --- /dev/null +++ b/packages/babel-helper-compilation-targets/src/utils.js @@ -0,0 +1,64 @@ +// @flow + +import invariant from "invariant"; +import semver from "semver"; + +import { unreleasedLabels } from "./targets"; +import type { Target, Targets } from "./types"; + +const versionRegExp = /^(\d+|\d+.\d+)$/; + +export function semverMin(first: ?string, second: string): string { + return first && semver.lt(first, second) ? first : second; +} + +// Convert version to a semver value. +// 2.5 -> 2.5.0; 1 -> 1.0.0; +export function semverify(version: number | string): string { + if (typeof version === "string" && semver.valid(version)) { + return version; + } + + invariant( + typeof version === "number" || + (typeof version === "string" && versionRegExp.test(version)), + `'${version}' is not a valid version`, + ); + + const split = version.toString().split("."); + while (split.length < 3) { + split.push("0"); + } + return split.join("."); +} + +export function isUnreleasedVersion( + version: string | number, + env: string, +): boolean { + const unreleasedLabel = unreleasedLabels[env]; + return ( + !!unreleasedLabel && unreleasedLabel === version.toString().toLowerCase() + ); +} + +export function getLowestUnreleased(a: string, b: string, env: string): string { + const unreleasedLabel = unreleasedLabels[env]; + const hasUnreleased = [a, b].some(item => item === unreleasedLabel); + if (hasUnreleased) { + return a === hasUnreleased ? b : a || b; + } + return semverMin(a, b); +} + +export function getLowestImplementedVersion( + plugin: Targets, + environment: Target, +): string { + const result = plugin[environment]; + // When Android support data is absent, use Chrome data as fallback + if (!result && environment === "android") { + return plugin.chrome; + } + return result; +} diff --git a/packages/babel-preset-env/test/utils.spec.js b/packages/babel-helper-compilation-targets/test/pretty.spec.js similarity index 56% rename from packages/babel-preset-env/test/utils.spec.js rename to packages/babel-helper-compilation-targets/test/pretty.spec.js index a5ca4256cda5..6164cde8e918 100644 --- a/packages/babel-preset-env/test/utils.spec.js +++ b/packages/babel-helper-compilation-targets/test/pretty.spec.js @@ -1,27 +1,6 @@ -"use strict"; - -const utils = require("../lib/utils"); - -const { prettifyTargets, prettifyVersion, semverify } = utils; - -describe("utils", () => { - describe("semverify", () => { - it("returns", () => { - expect(semverify("1")).toBe("1.0.0"); - expect(semverify("1.0")).toBe("1.0.0"); - expect(semverify("1.0.0")).toBe("1.0.0"); - expect(semverify(1)).toBe("1.0.0"); - expect(semverify(1.2)).toBe("1.2.0"); - }); - - it("throws", () => { - const invalidSemver = () => { - semverify("invalid"); - }; - expect(invalidSemver).toThrow(); - }); - }); +import { prettifyTargets, prettifyVersion } from "../lib/pretty"; +describe("pretty", () => { describe("prettifyVersion", () => { it("returns", () => { expect(prettifyVersion(true)).toBe(true); diff --git a/packages/babel-preset-env/test/targets-parser.spec.js b/packages/babel-helper-compilation-targets/test/targets-parser.spec.js similarity index 99% rename from packages/babel-preset-env/test/targets-parser.spec.js rename to packages/babel-helper-compilation-targets/test/targets-parser.spec.js index 7b9f669d2d01..adc0aa647e80 100644 --- a/packages/babel-preset-env/test/targets-parser.spec.js +++ b/packages/babel-helper-compilation-targets/test/targets-parser.spec.js @@ -1,5 +1,5 @@ import browserslist from "browserslist"; -import getTargets from "../lib/targets-parser"; +import getTargets from ".."; describe("getTargets", () => { it("parses", () => { diff --git a/packages/babel-helper-compilation-targets/test/targets-supported.js b/packages/babel-helper-compilation-targets/test/targets-supported.js new file mode 100644 index 000000000000..fd018172ef29 --- /dev/null +++ b/packages/babel-helper-compilation-targets/test/targets-supported.js @@ -0,0 +1,82 @@ +"use strict"; + +const { targetsSupported } = require("../lib/filter-items"); + +describe("targetsSupported", () => { + const MAX_VERSION = `${Number.MAX_SAFE_INTEGER}.0.0`; + + it("returns false if no targets are specified", () => { + expect(targetsSupported({}, {})).toBe(false); + }); + + it("returns false if plugin feature is not implemented in one or more targets", () => { + let targets; + const plugin = { + edge: false, + firefox: 45, + chrome: 49, + }; + + targets = { + chrome: MAX_VERSION, + firefox: MAX_VERSION, + }; + expect(targetsSupported(targets, plugin)).toBe(true); + + targets = { + edge: "12", + }; + expect(targetsSupported(targets, plugin)).toBe(false); + }); + + it("returns true if plugin feature is implemented by lower than target", () => { + const plugin = { + chrome: 49, + }; + const targets = { + chrome: MAX_VERSION, + }; + + expect(targetsSupported(targets, plugin)).toBe(true); + }); + + it("returns true if plugin feature is implemented is equal to target", () => { + const plugin = { + chrome: 49, + }; + const targets = { + chrome: "49.0.0", + }; + expect(targetsSupported(targets, plugin)).toBe(true); + }); + + it("returns false if plugin feature is implemented is greater than target", () => { + const plugin = { + chrome: 50, + }; + const targets = { + chrome: "49.0.0", + }; + expect(targetsSupported(targets, plugin)).toBe(false); + }); + + it("returns when target is a decimal", () => { + const plugin = { + node: 6.9, + }; + const targets = { + node: "6.10.0", + }; + expect(targetsSupported(targets, plugin)).toBe(true); + }); + + it("throws an error if target version is invalid", () => { + const plugin = { + chrome: 50, + }; + const targets = { + chrome: 55, + }; + expect(() => targetsSupported(targets, plugin)).toThrow(); + }); +}); diff --git a/packages/babel-helper-compilation-targets/test/utils.spec.js b/packages/babel-helper-compilation-targets/test/utils.spec.js new file mode 100644 index 000000000000..77068400663d --- /dev/null +++ b/packages/babel-helper-compilation-targets/test/utils.spec.js @@ -0,0 +1,20 @@ +import { semverify } from "../lib/utils"; + +describe("utils", () => { + describe("semverify", () => { + it("returns", () => { + expect(semverify("1")).toBe("1.0.0"); + expect(semverify("1.0")).toBe("1.0.0"); + expect(semverify("1.0.0")).toBe("1.0.0"); + expect(semverify(1)).toBe("1.0.0"); + expect(semverify(1.2)).toBe("1.2.0"); + }); + + it("throws", () => { + const invalidSemver = () => { + semverify("invalid"); + }; + expect(invalidSemver).toThrow(); + }); + }); +}); diff --git a/packages/babel-preset-env/data/built-in-modules.js b/packages/babel-preset-env/data/built-in-modules.js new file mode 100644 index 000000000000..161ddb8ea7ab --- /dev/null +++ b/packages/babel-preset-env/data/built-in-modules.js @@ -0,0 +1,3 @@ +// TODO: Remove in Babel 8 + +module.exports = require("@babel/compat-data/native-modules"); diff --git a/packages/babel-preset-env/data/built-in-modules.json.js b/packages/babel-preset-env/data/built-in-modules.json.js new file mode 100644 index 000000000000..161ddb8ea7ab --- /dev/null +++ b/packages/babel-preset-env/data/built-in-modules.json.js @@ -0,0 +1,3 @@ +// TODO: Remove in Babel 8 + +module.exports = require("@babel/compat-data/native-modules"); diff --git a/packages/babel-preset-env/data/built-ins.js b/packages/babel-preset-env/data/built-ins.js new file mode 100644 index 000000000000..38f8a09add7a --- /dev/null +++ b/packages/babel-preset-env/data/built-ins.js @@ -0,0 +1,4 @@ +// TODO: Remove in Babel 8 +// https://github.com/vuejs/vue-cli/issues/3671 + +module.exports = require("./corejs2-built-ins.json"); diff --git a/packages/babel-preset-env/data/corejs2-built-ins.js b/packages/babel-preset-env/data/corejs2-built-ins.js new file mode 100644 index 000000000000..486f3b1e1b2a --- /dev/null +++ b/packages/babel-preset-env/data/corejs2-built-ins.js @@ -0,0 +1,3 @@ +// TODO: Remove in Babel 8 + +module.exports = require("@babel/compat-data/corejs2-built-ins"); diff --git a/packages/babel-preset-env/data/corejs2-built-ins.json.js b/packages/babel-preset-env/data/corejs2-built-ins.json.js new file mode 100644 index 000000000000..486f3b1e1b2a --- /dev/null +++ b/packages/babel-preset-env/data/corejs2-built-ins.json.js @@ -0,0 +1,3 @@ +// TODO: Remove in Babel 8 + +module.exports = require("@babel/compat-data/corejs2-built-ins"); diff --git a/packages/babel-preset-env/data/plugins.js b/packages/babel-preset-env/data/plugins.js new file mode 100644 index 000000000000..b5d6b4361f10 --- /dev/null +++ b/packages/babel-preset-env/data/plugins.js @@ -0,0 +1,3 @@ +// TODO: Remove in Babel 8 + +module.exports = require("@babel/compat-data/plugins"); diff --git a/packages/babel-preset-env/data/plugins.json.js b/packages/babel-preset-env/data/plugins.json.js new file mode 100644 index 000000000000..b5d6b4361f10 --- /dev/null +++ b/packages/babel-preset-env/data/plugins.json.js @@ -0,0 +1,3 @@ +// TODO: Remove in Babel 8 + +module.exports = require("@babel/compat-data/plugins"); diff --git a/packages/babel-preset-env/data/unreleased-labels.js b/packages/babel-preset-env/data/unreleased-labels.js index bc5130567d2f..112d859930d8 100644 --- a/packages/babel-preset-env/data/unreleased-labels.js +++ b/packages/babel-preset-env/data/unreleased-labels.js @@ -1,3 +1,3 @@ -module.exports = { - safari: "tp", -}; +// TODO: Remove in Babel 8 + +module.exports = require("@babel/helper-compilation-targets").unreleasedLabels; diff --git a/packages/babel-preset-env/package.json b/packages/babel-preset-env/package.json index 32eb9a33e54b..3b335e702e14 100644 --- a/packages/babel-preset-env/package.json +++ b/packages/babel-preset-env/package.json @@ -10,10 +10,9 @@ }, "repository": "https://github.com/babel/babel/tree/master/packages/babel-preset-env", "main": "lib/index.js", - "scripts": { - "build-data": "./scripts/download-compat-table.sh; node ./scripts/build-data.js; node ./scripts/build-modules-support.js" - }, "dependencies": { + "@babel/compat-data": "^0.0.0", + "@babel/helper-compilation-targets": "^0.0.0", "@babel/helper-module-imports": "^7.7.4", "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-proposal-async-generator-functions": "^7.7.4", @@ -74,8 +73,6 @@ "@babel/core": "^7.7.7", "@babel/helper-fixtures": "^7.6.3", "@babel/helper-plugin-test-runner": "^7.7.4", - "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "caniuse-db": "1.0.30000969", - "electron-to-chromium": "1.3.113" + "@babel/plugin-syntax-dynamic-import": "^7.2.0" } } diff --git a/packages/babel-preset-env/src/debug.js b/packages/babel-preset-env/src/debug.js index 58c98e5daedb..b91499e72275 100644 --- a/packages/babel-preset-env/src/debug.js +++ b/packages/babel-preset-env/src/debug.js @@ -1,14 +1,9 @@ // @flow -/*eslint quotes: ["error", "double", { "avoidEscape": true }]*/ -import semver from "semver"; -import { - isUnreleasedVersion, - prettifyVersion, - semverify, - getLowestImplementedVersion, -} from "./utils"; -import type { Targets } from "./types"; +import { + getInclusionReasons, + type Targets, +} from "@babel/helper-compilation-targets"; const wordEnds = (size: number) => { return size > 1 ? "s" : ""; @@ -21,29 +16,7 @@ export const logPluginOrPolyfill = ( targetVersions: Targets, list: { [key: string]: Targets }, ) => { - const minVersions = list[item] || {}; - - const filteredList = Object.keys(targetVersions).reduce((result, env) => { - const minVersion = getLowestImplementedVersion(minVersions, env); - const targetVersion = targetVersions[env]; - - if (!minVersion) { - result[env] = prettifyVersion(targetVersion); - } else { - const minIsUnreleased = isUnreleasedVersion(minVersion, env); - const targetIsUnreleased = isUnreleasedVersion(targetVersion, env); - - if ( - !targetIsUnreleased && - (minIsUnreleased || - semver.lt(targetVersion.toString(), semverify(minVersion))) - ) { - result[env] = prettifyVersion(targetVersion); - } - } - - return result; - }, {}); + const filteredList = getInclusionReasons(item, targetVersions, list); const formattedTargets = JSON.stringify(filteredList) .replace(/,/g, ", ") diff --git a/packages/babel-preset-env/src/filter-items.js b/packages/babel-preset-env/src/filter-items.js index cd1a377f995b..a0041b3f082b 100644 --- a/packages/babel-preset-env/src/filter-items.js +++ b/packages/babel-preset-env/src/filter-items.js @@ -1,103 +1,11 @@ // @flow -import semver from "semver"; -import { - semverify, - isUnreleasedVersion, - getLowestImplementedVersion, -} from "./utils"; - -import type { Targets } from "./types"; - -export function isPluginRequired( - supportedEnvironments: Targets, - plugin: Targets, -) { - const targetEnvironments = Object.keys(supportedEnvironments); - - if (targetEnvironments.length === 0) { - return true; - } - - const isRequiredForEnvironments = targetEnvironments.filter(environment => { - const lowestImplementedVersion = getLowestImplementedVersion( - plugin, - environment, - ); - // Feature is not implemented in that environment - if (!lowestImplementedVersion) { - return true; - } - const lowestTargetedVersion = supportedEnvironments[environment]; - - // If targets has unreleased value as a lowest version, then don't require a plugin. - if (isUnreleasedVersion(lowestTargetedVersion, environment)) { - return false; - } - - // Include plugin if it is supported in the unreleased environment, which wasn't specified in targets - if (isUnreleasedVersion(lowestImplementedVersion, environment)) { - return true; - } - - if (!semver.valid(lowestTargetedVersion.toString())) { - throw new Error( - `Invalid version passed for target "${environment}": "${lowestTargetedVersion}". ` + - "Versions must be in semver format (major.minor.patch)", - ); - } - - return semver.gt( - semverify(lowestImplementedVersion), - lowestTargetedVersion.toString(), - ); - }); - - return isRequiredForEnvironments.length > 0; -} - -export default function( - list: { [feature: string]: Targets }, - includes: Set, - excludes: Set, - targets: Targets, - defaultIncludes: Array | null, - defaultExcludes?: Array | null, - pluginSyntaxMap?: Map, -) { - const result = new Set(); - - for (const item in list) { - if ( - !excludes.has(item) && - (isPluginRequired(targets, list[item]) || includes.has(item)) - ) { - result.add(item); - } else if (pluginSyntaxMap) { - const shippedProposalsSyntax = pluginSyntaxMap.get(item); - - if (shippedProposalsSyntax) { - result.add(shippedProposalsSyntax); - } - } - } - - if (defaultIncludes) { - defaultIncludes.forEach(item => !excludes.has(item) && result.add(item)); - } - - if (defaultExcludes) { - defaultExcludes.forEach(item => !includes.has(item) && result.delete(item)); - } - - return result; -} export function removeUnnecessaryItems( items: Set, - overlapping: Map>, + overlapping: { [name: string]: string[] }, ) { items.forEach(item => { - const names = overlapping.get(item); - if (names) names.forEach(name => items.delete(name)); + // $FlowIgnore Flow doesn't support calls in optional chains + overlapping[item]?.forEach(name => items.delete(name)); }); } diff --git a/packages/babel-preset-env/src/index.js b/packages/babel-preset-env/src/index.js index 17740c4c3586..19cc41121464 100644 --- a/packages/babel-preset-env/src/index.js +++ b/packages/babel-preset-env/src/index.js @@ -3,12 +3,12 @@ import { SemVer } from "semver"; import { logPluginOrPolyfill } from "./debug"; import getOptionSpecificExcludesFor from "./get-option-specific-excludes"; -import filterItems, { removeUnnecessaryItems } from "./filter-items"; +import { removeUnnecessaryItems } from "./filter-items"; import moduleTransformations from "./module-transformations"; import normalizeOptions from "./normalize-options"; -import pluginList from "../data/plugins.json"; +import pluginList from "@babel/compat-data/plugins"; import { proposalPlugins, pluginSyntaxMap } from "../data/shipped-proposals"; -import overlappingPlugins from "../data/overlapping-plugins"; +import overlappingPlugins from "@babel/compat-data/overlapping-plugins"; import addCoreJS2UsagePlugin from "./polyfills/corejs2/usage-plugin"; import addCoreJS3UsagePlugin from "./polyfills/corejs3/usage-plugin"; @@ -17,15 +17,25 @@ import replaceCoreJS2EntryPlugin from "./polyfills/corejs2/entry-plugin"; import replaceCoreJS3EntryPlugin from "./polyfills/corejs3/entry-plugin"; import removeRegeneratorEntryPlugin from "./polyfills/regenerator/entry-plugin"; -import getTargets from "./targets-parser"; +import getTargets, { + prettifyTargets, + filterItems, + isRequired, + type Targets, +} from "@babel/helper-compilation-targets"; import availablePlugins from "./available-plugins"; -import { filterStageFromList, prettifyTargets } from "./utils"; +import { filterStageFromList } from "./utils"; import { declare } from "@babel/helper-plugin-utils"; import typeof ModuleTransformationsType from "./module-transformations"; -import type { BuiltInsOption, Targets, ModuleOption } from "./types"; +import type { BuiltInsOption, ModuleOption } from "./types"; -export { isPluginRequired } from "./filter-items"; +// TODO: Remove in Babel 8 +export function isPluginRequired(targets: Targets, support: Targets) { + return !isRequired("fake-name", targets, { + compatData: { "fake-name": support }, + }); +} const pluginListWithoutProposals = filterStageFromList( pluginList, diff --git a/packages/babel-preset-env/src/normalize-options.js b/packages/babel-preset-env/src/normalize-options.js index 79998befaeb4..667805bb5703 100644 --- a/packages/babel-preset-env/src/normalize-options.js +++ b/packages/babel-preset-env/src/normalize-options.js @@ -1,14 +1,13 @@ // @flow import corejs3Polyfills from "core-js-compat/data"; +import findSuggestion from "levenary"; import invariant from "invariant"; import { coerce, SemVer } from "semver"; -import corejs2Polyfills from "../data/corejs2-built-ins.json"; -import pluginsList from "../data/plugins.json"; +import corejs2Polyfills from "@babel/compat-data/corejs2-built-ins"; +import pluginsList from "@babel/compat-data/plugins"; import moduleTransformations from "./module-transformations"; import { TopLevelOptions, ModulesOption, UseBuiltInsOption } from "./options"; import { defaultWebIncludes } from "./polyfills/corejs2/get-platform-specific-default"; -import { isBrowsersQueryValid } from "./targets-parser"; -import findSuggestion from "levenary"; import type { BuiltInsOption, @@ -26,7 +25,7 @@ const validateTopLevelOptions = (options: Options) => { if (!TopLevelOptions[option]) { throw new Error( `Invalid Option: ${option} is not a valid top-level option. - Maybe you meant to use '${findSuggestion(validOptions, option)}'?`, + Maybe you meant to use '${findSuggestion(option, validOptions)}'?`, ); } } @@ -121,7 +120,7 @@ export const checkDuplicateIncludeExcludes = ( const normalizeTargets = targets => { // TODO: Allow to use only query or strings as a targets from next breaking change. - if (isBrowsersQueryValid(targets)) { + if (typeof targets === "string" || Array.isArray(targets)) { return { browsers: targets }; } return { diff --git a/packages/babel-preset-env/src/options.js b/packages/babel-preset-env/src/options.js index f8bf5d0bba6c..b84423eca7d1 100644 --- a/packages/babel-preset-env/src/options.js +++ b/packages/babel-preset-env/src/options.js @@ -31,22 +31,3 @@ export const UseBuiltInsOption = { entry: "entry", usage: "usage", }; - -export const TargetNames = { - esmodules: "esmodules", - node: "node", - browsers: "browsers", - chrome: "chrome", - opera: "opera", - edge: "edge", - firefox: "firefox", - safari: "safari", - ie: "ie", - ios: "ios", - android: "android", - electron: "electron", - samsung: "samsung", - - // deprecated - uglify: "uglify", -}; diff --git a/packages/babel-preset-env/src/polyfills/corejs2/entry-plugin.js b/packages/babel-preset-env/src/polyfills/corejs2/entry-plugin.js index 82fa4dd9f237..da8010e2c731 100644 --- a/packages/babel-preset-env/src/polyfills/corejs2/entry-plugin.js +++ b/packages/babel-preset-env/src/polyfills/corejs2/entry-plugin.js @@ -1,8 +1,8 @@ // @flow -import corejs2Polyfills from "../../../data/corejs2-built-ins.json"; +import corejs2Polyfills from "@babel/compat-data/corejs2-built-ins"; +import { filterItems } from "@babel/helper-compilation-targets"; import getPlatformSpecificDefaultFor from "./get-platform-specific-default"; -import filterItems from "../../filter-items"; import { createImport, isPolyfillSource, diff --git a/packages/babel-preset-env/src/polyfills/corejs2/get-platform-specific-default.js b/packages/babel-preset-env/src/polyfills/corejs2/get-platform-specific-default.js index aa89ebfb1900..3674a2be8d4e 100644 --- a/packages/babel-preset-env/src/polyfills/corejs2/get-platform-specific-default.js +++ b/packages/babel-preset-env/src/polyfills/corejs2/get-platform-specific-default.js @@ -1,6 +1,6 @@ // @flow -import type { Targets } from "../../types"; +import type { Targets } from "@babel/helper-compilation-targets"; export const defaultWebIncludes = [ "web.timers", diff --git a/packages/babel-preset-env/src/polyfills/corejs2/usage-plugin.js b/packages/babel-preset-env/src/polyfills/corejs2/usage-plugin.js index ffe73bf8d59b..a9269237e5ae 100644 --- a/packages/babel-preset-env/src/polyfills/corejs2/usage-plugin.js +++ b/packages/babel-preset-env/src/polyfills/corejs2/usage-plugin.js @@ -1,8 +1,8 @@ // @flow -import corejs2Polyfills from "../../../data/corejs2-built-ins.json"; +import corejs2Polyfills from "@babel/compat-data/corejs2-built-ins"; +import { filterItems } from "@babel/helper-compilation-targets"; import getPlatformSpecificDefaultFor from "./get-platform-specific-default"; -import filterItems from "../../filter-items"; import { BuiltIns, StaticProperties, diff --git a/packages/babel-preset-env/src/polyfills/corejs3/entry-plugin.js b/packages/babel-preset-env/src/polyfills/corejs3/entry-plugin.js index e30b284d8ac2..3ec5875d2b85 100644 --- a/packages/babel-preset-env/src/polyfills/corejs3/entry-plugin.js +++ b/packages/babel-preset-env/src/polyfills/corejs3/entry-plugin.js @@ -3,7 +3,7 @@ import corejs3Polyfills from "core-js-compat/data"; import corejsEntries from "core-js-compat/entries"; import getModulesListForTargetVersion from "core-js-compat/get-modules-list-for-target-version"; -import filterItems from "../../filter-items"; +import { filterItems } from "@babel/helper-compilation-targets"; import { has, intersection, diff --git a/packages/babel-preset-env/src/polyfills/corejs3/usage-plugin.js b/packages/babel-preset-env/src/polyfills/corejs3/usage-plugin.js index 687a07c4367d..11eabb97b642 100644 --- a/packages/babel-preset-env/src/polyfills/corejs3/usage-plugin.js +++ b/packages/babel-preset-env/src/polyfills/corejs3/usage-plugin.js @@ -3,7 +3,7 @@ import corejs3Polyfills from "core-js-compat/data"; import corejs3ShippedProposalsList from "./shipped-proposals"; import getModulesListForTargetVersion from "core-js-compat/get-modules-list-for-target-version"; -import filterItems from "../../filter-items"; +import { filterItems } from "@babel/helper-compilation-targets"; import { BuiltIns, StaticProperties, diff --git a/packages/babel-preset-env/src/targets-parser.js b/packages/babel-preset-env/src/targets-parser.js index f2284e715ca4..3697cb58f26f 100644 --- a/packages/babel-preset-env/src/targets-parser.js +++ b/packages/babel-preset-env/src/targets-parser.js @@ -1,261 +1,7 @@ -// @flow +// TODO: Remove in Babel 8 -import browserslist from "browserslist"; -import invariant from "invariant"; -import semver from "semver"; -import findSuggestion from "levenary"; -import { semverify, isUnreleasedVersion, getLowestUnreleased } from "./utils"; -import browserModulesData from "../data/built-in-modules.json"; -import { TargetNames } from "./options"; -import type { Targets } from "./types"; - -const browserslistDefaults = browserslist.defaults; - -const validBrowserslistTargets = [ - ...Object.keys(browserslist.data), - ...Object.keys(browserslist.aliases), -]; - -const objectToBrowserslist = (object: Targets): Array => { - return Object.keys(object).reduce((list, targetName) => { - if (validBrowserslistTargets.indexOf(targetName) >= 0) { - const targetVersion = object[targetName]; - return list.concat(`${targetName} ${targetVersion}`); - } - return list; - }, []); -}; - -const validateTargetNames = (targets: Targets): void => { - const validTargets = Object.keys(TargetNames); - for (const target in targets) { - if (!TargetNames[target]) { - throw new Error( - `Invalid Option: '${target}' is not a valid target - Maybe you meant to use '${findSuggestion(validTargets, target)}'?`, - ); - } - } -}; - -const browserNameMap = { - and_chr: "chrome", - and_ff: "firefox", - android: "android", - chrome: "chrome", - edge: "edge", - firefox: "firefox", - ie: "ie", - ie_mob: "ie", - ios_saf: "ios", - node: "node", - op_mob: "opera", - opera: "opera", - safari: "safari", - samsung: "samsung", -}; - -export const isBrowsersQueryValid = ( - browsers: string | Array | Targets, -): boolean => typeof browsers === "string" || Array.isArray(browsers); - -const validateBrowsers = browsers => { - invariant( - typeof browsers === "undefined" || isBrowsersQueryValid(browsers), - `Invalid Option: '${browsers}' is not a valid browserslist query`, - ); - - return browsers; -}; - -export const semverMin = (first: ?string, second: string): string => { - return first && semver.lt(first, second) ? first : second; -}; - -const mergeBrowsers = (fromQuery: Targets, fromTarget: Targets) => { - return Object.keys(fromTarget).reduce((queryObj, targKey) => { - if (targKey !== TargetNames.browsers) { - queryObj[targKey] = fromTarget[targKey]; - } - return queryObj; - }, fromQuery); -}; - -const getLowestVersions = (browsers: Array): Targets => { - return browsers.reduce((all: Object, browser: string): Object => { - const [browserName, browserVersion] = browser.split(" "); - const normalizedBrowserName = browserNameMap[browserName]; - - if (!normalizedBrowserName) { - return all; - } - - try { - // Browser version can return as "10.0-10.2" - const splitVersion = browserVersion.split("-")[0].toLowerCase(); - const isSplitUnreleased = isUnreleasedVersion(splitVersion, browserName); - - if (!all[normalizedBrowserName]) { - all[normalizedBrowserName] = isSplitUnreleased - ? splitVersion - : semverify(splitVersion); - return all; - } - - const version = all[normalizedBrowserName]; - const isUnreleased = isUnreleasedVersion(version, browserName); - - if (isUnreleased && isSplitUnreleased) { - all[normalizedBrowserName] = getLowestUnreleased( - version, - splitVersion, - browserName, - ); - } else if (isUnreleased) { - all[normalizedBrowserName] = semverify(splitVersion); - } else if (!isUnreleased && !isSplitUnreleased) { - const parsedBrowserVersion = semverify(splitVersion); - - all[normalizedBrowserName] = semverMin(version, parsedBrowserVersion); - } - } catch (e) {} - - return all; - }, {}); -}; - -const outputDecimalWarning = (decimalTargets: Array): void => { - if (!decimalTargets || !decimalTargets.length) { - return; - } - - console.log("Warning, the following targets are using a decimal version:"); - console.log(""); - decimalTargets.forEach(({ target, value }) => - console.log(` ${target}: ${value}`), - ); - console.log(""); - console.log( - "We recommend using a string for minor/patch versions to avoid numbers like 6.10", - ); - console.log("getting parsed as 6.1, which can lead to unexpected behavior."); - console.log(""); -}; - -const semverifyTarget = (target, value) => { - try { - return semverify(value); - } catch (error) { - throw new Error( - `Invalid Option: '${value}' is not a valid value for 'targets.${target}'.`, - ); - } -}; - -const targetParserMap = { - __default: (target, value) => { - const version = isUnreleasedVersion(value, target) - ? value.toLowerCase() - : semverifyTarget(target, value); - return [target, version]; - }, - - // Parse `node: true` and `node: "current"` to version - node: (target, value) => { - const parsed = - value === true || value === "current" - ? process.versions.node - : semverifyTarget(target, value); - return [target, parsed]; - }, -}; - -type ParsedResult = { - targets: Targets, - decimalWarnings: Array, -}; - -const getTargets = (targets: Object = {}, options: Object = {}): Targets => { - const targetOpts: Targets = {}; - - validateTargetNames(targets); - - // `esmodules` as a target indicates the specific set of browsers supporting ES Modules. - // These values OVERRIDE the `browsers` field. - if (targets.esmodules) { - const supportsESModules = browserModulesData["es6.module"]; - targets.browsers = Object.keys(supportsESModules) - .map(browser => `${browser} ${supportsESModules[browser]}`) - .join(", "); - } - - // Parse browsers target via browserslist - const browsersquery = validateBrowsers(targets.browsers); - - const hasTargets = Object.keys(targets).length > 0; - const shouldParseBrowsers = !!targets.browsers; - const shouldSearchForConfig = - !options.ignoreBrowserslistConfig && !hasTargets; - - if (shouldParseBrowsers || shouldSearchForConfig) { - // If no targets are passed, we need to overwrite browserslist's defaults - // so that we enable all transforms (acting like the now deprecated - // preset-latest). - // - // Note, if browserslist resolves the config (ex. package.json), then usage - // of `defaults` in queries will be different since we don't want to break - // the behavior of "no targets is the same as preset-latest". - if (!hasTargets) { - browserslist.defaults = objectToBrowserslist(targets); - } - - const browsers = browserslist(browsersquery, { - path: options.configPath, - mobileToDesktop: true, - }); - - const queryBrowsers = getLowestVersions(browsers); - targets = mergeBrowsers(queryBrowsers, targets); - - // Reset browserslist defaults - browserslist.defaults = browserslistDefaults; - } - - // Parse remaining targets - const parsed = Object.keys(targets) - .filter(value => value !== TargetNames.esmodules) - .sort() - .reduce( - (results: ParsedResult, target: string): ParsedResult => { - if (target !== TargetNames.browsers) { - const value = targets[target]; - - // Warn when specifying minor/patch as a decimal - if (typeof value === "number" && value % 1 !== 0) { - results.decimalWarnings.push({ target, value }); - } - - // Check if we have a target parser? - const parser = targetParserMap[target] || targetParserMap.__default; - const [parsedTarget, parsedValue] = parser(target, value); - - if (parsedValue) { - // Merge (lowest wins) - results.targets[parsedTarget] = parsedValue; - } - } - - return results; - }, - { - targets: targetOpts, - decimalWarnings: [], - }, - ); - - outputDecimalWarning(parsed.decimalWarnings); - - return parsed.targets; -}; - -export default getTargets; +export { + default, + isBrowsersQueryValid, + semverMin, +} from "@babel/helper-compilation-targets"; diff --git a/packages/babel-preset-env/src/types.js b/packages/babel-preset-env/src/types.js index c1c3157eb27d..1066c665f1a6 100644 --- a/packages/babel-preset-env/src/types.js +++ b/packages/babel-preset-env/src/types.js @@ -2,24 +2,7 @@ import { ModulesOption, UseBuiltInsOption } from "./options"; import type { NormalizedCorejsOption } from "./normalize-options"; - -// Targets -export type Target = - | "node" - | "chrome" - | "opera" - | "edge" - | "firefox" - | "safari" - | "ie" - | "ios" - | "android" - | "electron" - | "samsung"; - -export type Targets = { - [target: Target]: string, -}; +import type { Targets } from "@babel/helper-compilation-targets"; // Options // Use explicit modules to prevent typo errors. diff --git a/packages/babel-preset-env/src/utils.js b/packages/babel-preset-env/src/utils.js index e61e4e4d72ce..c7a330fe9f51 100644 --- a/packages/babel-preset-env/src/utils.js +++ b/packages/babel-preset-env/src/utils.js @@ -2,12 +2,8 @@ import * as t from "@babel/types"; import type { NodePath } from "@babel/traverse"; -import invariant from "invariant"; -import semver from "semver"; import { addSideEffect } from "@babel/helper-module-imports"; -import unreleasedLabels from "../data/unreleased-labels"; -import { semverMin } from "./targets-parser"; -import type { Target, Targets } from "./types"; +import type { Targets } from "@babel/helper-compilation-targets"; export const has = Object.hasOwnProperty.call.bind(Object.hasOwnProperty); @@ -18,28 +14,6 @@ export function getType(target: any): string { .toLowerCase(); } -const versionRegExp = /^(\d+|\d+.\d+)$/; - -// Convert version to a semver value. -// 2.5 -> 2.5.0; 1 -> 1.0.0; -export function semverify(version: number | string): string { - if (typeof version === "string" && semver.valid(version)) { - return version; - } - - invariant( - typeof version === "number" || - (typeof version === "string" && versionRegExp.test(version)), - `'${version}' is not a valid version`, - ); - - const split = version.toString().split("."); - while (split.length < 3) { - split.push("0"); - } - return split.join("."); -} - export function intersection( first: Set, second: Set, @@ -52,71 +26,6 @@ export function intersection( return result; } -export function prettifyVersion(version: string) { - if (typeof version !== "string") { - return version; - } - - const parts = [semver.major(version)]; - const minor = semver.minor(version); - const patch = semver.patch(version); - - if (minor || patch) { - parts.push(minor); - } - - if (patch) { - parts.push(patch); - } - - return parts.join("."); -} - -export function prettifyTargets(targets: Targets): Targets { - return Object.keys(targets).reduce((results, target) => { - let value = targets[target]; - - const unreleasedLabel = unreleasedLabels[target]; - if (typeof value === "string" && unreleasedLabel !== value) { - value = prettifyVersion(value); - } - - results[target] = value; - return results; - }, {}); -} - -export function isUnreleasedVersion( - version: string | number, - env: string, -): boolean { - const unreleasedLabel = unreleasedLabels[env]; - return ( - !!unreleasedLabel && unreleasedLabel === version.toString().toLowerCase() - ); -} - -export function getLowestUnreleased(a: string, b: string, env: string): string { - const unreleasedLabel = unreleasedLabels[env]; - const hasUnreleased = [a, b].some(item => item === unreleasedLabel); - if (hasUnreleased) { - return a === hasUnreleased ? b : a || b; - } - return semverMin(a, b); -} - -export function getLowestImplementedVersion( - plugin: Targets, - environment: Target, -): string { - const result = plugin[environment]; - // When Android support data is absent, use Chrome data as fallback - if (!result && environment === "android") { - return plugin.chrome; - } - return result; -} - export function filterStageFromList( list: { [feature: string]: Targets }, stageList: { [feature: string]: boolean }, diff --git a/packages/babel-preset-env/test/is-plugin-required.js b/packages/babel-preset-env/test/is-plugin-required.js deleted file mode 100644 index be999b7c9828..000000000000 --- a/packages/babel-preset-env/test/is-plugin-required.js +++ /dev/null @@ -1,82 +0,0 @@ -"use strict"; - -const presetEnv = require("../"); - -describe("isPluginRequired", () => { - const MAX_VERSION = `${Number.MAX_SAFE_INTEGER}.0.0`; - - it("returns true if no targets are specified", () => { - expect(presetEnv.isPluginRequired({}, {})).toBe(true); - }); - - it("returns true if plugin feature is not implemented in one or more targets", () => { - let targets; - const plugin = { - edge: false, - firefox: 45, - chrome: 49, - }; - - targets = { - chrome: MAX_VERSION, - firefox: MAX_VERSION, - }; - expect(presetEnv.isPluginRequired(targets, plugin)).toBe(false); - - targets = { - edge: "12", - }; - expect(presetEnv.isPluginRequired(targets, plugin)).toBe(true); - }); - - it("returns false if plugin feature is implemented by lower than target", () => { - const plugin = { - chrome: 49, - }; - const targets = { - chrome: MAX_VERSION, - }; - - expect(presetEnv.isPluginRequired(targets, plugin)).toBe(false); - }); - - it("returns false if plugin feature is implemented is equal to target", () => { - const plugin = { - chrome: 49, - }; - const targets = { - chrome: "49.0.0", - }; - expect(presetEnv.isPluginRequired(targets, plugin)).toBe(false); - }); - - it("returns true if plugin feature is implemented is greater than target", () => { - const plugin = { - chrome: 50, - }; - const targets = { - chrome: "49.0.0", - }; - expect(presetEnv.isPluginRequired(targets, plugin)).toBe(true); - }); - - it("returns when target is a decimal", () => { - const plugin = { - node: 6.9, - }; - const targets = { - node: "6.10.0", - }; - expect(presetEnv.isPluginRequired(targets, plugin)).toBe(false); - }); - - it("throws an error if target version is invalid", () => { - const plugin = { - chrome: 50, - }; - const targets = { - chrome: 55, - }; - expect(() => presetEnv.isPluginRequired(targets, plugin)).toThrow(); - }); -});