Extract targets parser and compat data from preset-env (#10899)

* Extract targets parser and compat data from preset-env

* Review by Jùnliàng

* isItemRequired -> targetsSupported

* Export isRequired
This commit is contained in:
Nicolò Ribaudo 2020-01-10 03:15:20 +01:00 committed by GitHub
parent 5f807ae45b
commit 04354d1556
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 936 additions and 707 deletions

2
.gitignore vendored
View File

@ -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

View File

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

View File

@ -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",

View File

@ -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;

View File

@ -0,0 +1,4 @@
scripts
src
test
*.log

View File

@ -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");

View File

@ -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"
}
}

View File

@ -0,0 +1,3 @@
{
"transform-regenerator": []
}

View File

@ -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");

View File

@ -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");

View File

@ -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"
}
}

View File

@ -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");

View File

@ -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`);

View File

@ -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;
}

View File

@ -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);

View File

@ -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; },
};
}

View File

@ -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);

View File

@ -0,0 +1,3 @@
src
test
*.log

View File

@ -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"
}
}

View File

@ -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;
}, {});
}

View File

@ -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<string>,
excludes?: Set<string>,
} = {},
) {
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<string>,
excludes: Set<string>,
targets: Targets,
defaultIncludes: Array<string> | null,
defaultExcludes?: Array<string> | null,
pluginSyntaxMap?: Map<string, string | null>,
) {
const result = new Set<string>();
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;
}

View File

@ -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<string> {
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<string> | 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<string>): 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<Object>): 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<Object>,
};
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;
}

View File

@ -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",
};

View File

@ -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;
}, {});
}

View File

@ -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",
};

View File

@ -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,
};

View File

@ -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;
}

View File

@ -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);

View File

@ -1,5 +1,5 @@
import browserslist from "browserslist";
import getTargets from "../lib/targets-parser";
import getTargets from "..";
describe("getTargets", () => {
it("parses", () => {

View File

@ -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();
});
});

View File

@ -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();
});
});
});

View File

@ -0,0 +1,3 @@
// TODO: Remove in Babel 8
module.exports = require("@babel/compat-data/native-modules");

View File

@ -0,0 +1,3 @@
// TODO: Remove in Babel 8
module.exports = require("@babel/compat-data/native-modules");

View File

@ -0,0 +1,4 @@
// TODO: Remove in Babel 8
// https://github.com/vuejs/vue-cli/issues/3671
module.exports = require("./corejs2-built-ins.json");

View File

@ -0,0 +1,3 @@
// TODO: Remove in Babel 8
module.exports = require("@babel/compat-data/corejs2-built-ins");

View File

@ -0,0 +1,3 @@
// TODO: Remove in Babel 8
module.exports = require("@babel/compat-data/corejs2-built-ins");

View File

@ -0,0 +1,3 @@
// TODO: Remove in Babel 8
module.exports = require("@babel/compat-data/plugins");

View File

@ -0,0 +1,3 @@
// TODO: Remove in Babel 8
module.exports = require("@babel/compat-data/plugins");

View File

@ -1,3 +1,3 @@
module.exports = {
safari: "tp",
};
// TODO: Remove in Babel 8
module.exports = require("@babel/helper-compilation-targets").unreleasedLabels;

View File

@ -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"
}
}

View File

@ -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, ", ")

View File

@ -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<string>,
excludes: Set<string>,
targets: Targets,
defaultIncludes: Array<string> | null,
defaultExcludes?: Array<string> | null,
pluginSyntaxMap?: Map<string, string | null>,
) {
const result = new Set<string>();
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<string>,
overlapping: Map<string, Set<string>>,
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));
});
}

View File

@ -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,

View File

@ -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 {

View File

@ -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",
};

View File

@ -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,

View File

@ -1,6 +1,6 @@
// @flow
import type { Targets } from "../../types";
import type { Targets } from "@babel/helper-compilation-targets";
export const defaultWebIncludes = [
"web.timers",

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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<string> => {
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<string> | 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<string>): 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<Object>): 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<Object>,
};
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";

View File

@ -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.

View File

@ -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<T>(
first: Set<T>,
second: Set<T>,
@ -52,71 +26,6 @@ export function intersection<T>(
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 },

View File

@ -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();
});
});