Add browserslist config/package.json section support. (#161)

This commit is contained in:
Artem Yavorsky 2017-08-01 18:50:44 +03:00 committed by Brian Ng
parent 1dd3d14a2f
commit f7a096b08e
25 changed files with 273 additions and 94 deletions

View File

@ -60,12 +60,54 @@ If you are targeting IE 8 and Chrome 55 it will include all plugins required by
For example, if you are building on Node 4, arrow functions won't be converted, but they will if you build on Node 0.12.
### Support a `browsers` option like autoprefixer
### Support a `browsers` option like autoprefixer.
Use [browserslist](https://github.com/ai/browserslist) to declare supported environments by performing queries like `> 1%, last 2 versions`.
Ref: [#19](https://github.com/babel/babel-preset-env/pull/19)
### Browserslist support.
[Browserslist](https://github.com/ai/browserslist) is a library used to share a supported list of browsers between different front-end tools like [autoprefixer](https://github.com/postcss/autoprefixer), [stylelint](https://stylelint.io/), [eslint-plugin-compat](https://github.com/amilajack/eslint-plugin-compat) and many others.
By default, babel-preset-env will use [browserslist config sources](https://github.com/ai/browserslist#queries).
For example, to enable only the polyfills and plugins needed for a project targeting *last 2 versions* and *IE10*:
**.babelrc**
```json
{
"presets": [
["env", {
"useBuiltIns": true
}]
]
}
```
**browserslist**
```
Last 2 versions
IE 10
```
or
**package.json**
```
"browserslist": "last 2 versions, ie 10"
```
Browserslist config will be ignored if: 1) `targets.browsers` was specified 2) or with `ignoreBrowserslistConfig: true` option ([see more](#ignoreBrowserslistConfig)):
#### Targets merging.
1. If [targets.browsers](#browsers) is defined - the browserslist config will be ignored. The browsers specified in `targets` will be merged with [any other explicitly defined targets](#targets). If merged, targets defined explicitly will override the same targets received from `targets.browsers`.
2. If [targets.browsers](#browsers) is _not_ defined - the program will search browserslist file or `package.json` with `browserslist` field. The search will start from the working directory of the process or from the path specified by the `configPath` option, and go up to the system root. If both a browserslist file and configuration inside a `package.json` are found, an exception will be thrown.
3. If a browserslist config was found and other targets are defined (but not [targets.browsers](#browsers)), the targets will be merged in the same way as `targets` defined explicitly with `targets.browsers`.
## Install
With [npm](https://www.npmjs.com):
@ -290,6 +332,18 @@ ES6 support, but it is not yet stable. You can follow its progress in
require an alternative minifier which _does_ support ES6 syntax, we recommend
using [Babili](https://github.com/babel/babili).
### `configPath`
`string`, defaults to `process.cwd()`
The starting point where the config search for browserslist will start, and ascend to the system root until found.
### `ignoreBrowserslistConfig`
`boolean`, defaults to `false`
Toggles whether or not [browserslist config sources](https://github.com/ai/browserslist#queries) are used, which includes searching for any browserslist files or referencing the browserslist key inside package.json. This is useful for projects that use a browserslist config for files that won't be compiled with Babel.
---
## Examples

View File

@ -74,9 +74,8 @@ const interpolateAllResults = (rawBrowsers, tests) => {
browser = rawBrowsers[bid];
if (browser.equals && res[bid] === undefined) {
result = res[browser.equals];
res[bid] = browser.ignore_flagged && result === "flagged"
? false
: result;
res[bid] =
browser.ignore_flagged && result === "flagged" ? false : result;
// For each browser, check if the previous browser has the same
// browser full name (e.g. Firefox) or family name (e.g. Chakra) as this one.
} else if (

View File

@ -52,7 +52,9 @@ export const logEntryPolyfills = (
console.log(
`
[${filename}] Replaced \`babel-polyfill\` with the following polyfill${wordEnds(polyfills.size)}:`,
[${filename}] Replaced \`babel-polyfill\` with the following polyfill${wordEnds(
polyfills.size,
)}:`,
);
onDebug(polyfills);
};

View File

@ -109,9 +109,11 @@ export default function buildPreset(
opts: Object = {},
): { plugins: Array<Plugin> } {
const {
configPath,
debug,
exclude: optionsExclude,
forceAllTransforms,
ignoreBrowserslistConfig,
include: optionsInclude,
loose,
modules,
@ -119,7 +121,6 @@ export default function buildPreset(
targets: optionsTargets,
useBuiltIns,
} = normalizeOptions(opts);
// TODO: remove this in next major
let hasUglifyTarget = false;
@ -133,7 +134,10 @@ export default function buildPreset(
console.log("");
}
const targets = getTargets(optionsTargets);
const targets = getTargets(optionsTargets, {
ignoreBrowserslistConfig,
configPath,
});
const include = transformIncludesAndExcludes(optionsInclude);
const exclude = transformIncludesAndExcludes(optionsExclude);

View File

@ -1,11 +1,12 @@
//@flow
import invariant from "invariant";
import browserslist from "browserslist";
import builtInsList from "../data/built-ins.json";
import { defaultWebIncludes } from "./default-includes";
import moduleTransformations from "./module-transformations";
import pluginFeatures from "../data/plugin-features";
import type { Options, ModuleOption, BuiltInsOption } from "./types";
import type { Targets, Options, ModuleOption, BuiltInsOption } from "./types";
const validIncludesAndExcludes = new Set([
...Object.keys(pluginFeatures),
@ -27,13 +28,20 @@ export const validateIncludesAndExcludes = (
invariant(
unknownOpts.length === 0,
`Invalid Option: The plugins/built-ins '${unknownOpts.join(", ")}' passed to the '${type}' option are not
`Invalid Option: The plugins/built-ins '${unknownOpts.join(
", ",
)}' passed to the '${type}' option are not
valid. Please check data/[plugin-features|built-in-features].js in babel-preset-env`,
);
return opts;
};
const validBrowserslistTargets = [
...Object.keys(browserslist.data),
...Object.keys(browserslist.aliases),
];
export const normalizePluginName = (plugin: string): string =>
plugin.replace(/^babel-plugin-/, "");
@ -50,11 +58,23 @@ export const checkDuplicateIncludeExcludes = (
invariant(
duplicates.length === 0,
`Invalid Option: The plugins/built-ins '${duplicates.join(", ")}' were found in both the "include" and
`Invalid Option: The plugins/built-ins '${duplicates.join(
", ",
)}' were found in both the "include" and
"exclude" options.`,
);
};
export const validateConfigPathOption = (
configPath: string = process.cwd(),
) => {
invariant(
typeof configPath === "string",
`Invalid Option: The configPath option '${configPath}' is invalid, only strings are allowed.`,
);
return configPath;
};
export const validateBoolOption = (
name: string,
value: ?boolean,
@ -80,6 +100,15 @@ export const validateSpecOption = (specOpt: boolean) =>
export const validateForceAllTransformsOption = (forceAllTransforms: boolean) =>
validateBoolOption("forceAllTransforms", forceAllTransforms, false);
export const validateIgnoreBrowserslistConfig = (
ignoreBrowserslistConfig: boolean,
) =>
validateBoolOption(
"ignoreBrowserslistConfig",
ignoreBrowserslistConfig,
false,
);
export const validateModulesOption = (
modulesOpt: ModuleOption = "commonjs",
) => {
@ -93,6 +122,16 @@ export const validateModulesOption = (
return modulesOpt;
};
export const objectToBrowserslist = (object: Targets) => {
return Object.keys(object).reduce((list, targetName) => {
if (validBrowserslistTargets.indexOf(targetName) >= 0) {
const targetVersion = object[targetName];
return list.concat(`${targetName} ${targetVersion}`);
}
return list;
}, []);
};
export const validateUseBuiltInsOption = (
builtInsOpt: BuiltInsOption = false,
): BuiltInsOption => {
@ -119,11 +158,15 @@ export default function normalizeOptions(opts: Options) {
checkDuplicateIncludeExcludes(opts.include, opts.exclude);
return {
configPath: validateConfigPathOption(opts.configPath),
debug: opts.debug,
exclude: validateIncludesAndExcludes(opts.exclude, "exclude"),
forceAllTransforms: validateForceAllTransformsOption(
opts.forceAllTransforms,
),
ignoreBrowserslistConfig: validateIgnoreBrowserslistConfig(
opts.ignoreBrowserslistConfig,
),
include: validateIncludesAndExcludes(opts.include, "include"),
loose: validateLooseOption(opts.loose),
modules: validateModulesOption(opts.modules),

View File

@ -3,6 +3,7 @@
import browserslist from "browserslist";
import semver from "semver";
import { semverify } from "./utils";
import { objectToBrowserslist } from "./normalize-options";
import type { Targets } from "./types";
const browserNameMap = {
@ -22,6 +23,15 @@ 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 !== "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(" ");
@ -69,54 +79,59 @@ const targetParserMap = {
// Parse `node: true` and `node: "current"` to version
node: (target, value) => {
const parsed = value === true || value === "current"
? process.versions.node
: semverify(value);
const parsed =
value === true || value === "current"
? process.versions.node
: semverify(value);
return [target, parsed];
},
};
const getTargets = (targets: Object = {}): Targets => {
let targetOpts: Targets = {};
type ParsedResult = {
targets: Targets,
decimalWarnings: Array<Object>,
};
const getTargets = (targets: Object = {}, options: Object = {}): Targets => {
const targetOpts: Targets = {};
// Parse browsers target via browserslist;
const queryIsValid = isBrowsersQueryValid(targets.browsers);
const browsersquery = queryIsValid ? targets.browsers : null;
if (queryIsValid || !options.ignoreBrowserslistConfig) {
browserslist.defaults = objectToBrowserslist(targets);
// Parse browsers target via browserslist
if (isBrowsersQueryValid(targets.browsers)) {
targetOpts = getLowestVersions(browserslist(targets.browsers));
const browsers = browserslist(browsersquery, { path: options.configPath });
const queryBrowsers = getLowestVersions(browsers);
targets = mergeBrowsers(queryBrowsers, targets);
}
// Parse remaining targets
type ParsedResult = {
targets: Targets,
decimalWarnings: Array<Object>,
};
const parsed = Object.keys(targets).reduce(
(results: ParsedResult, target: string): ParsedResult => {
if (target !== "browsers") {
const value = targets[target];
const parsed = Object.keys(targets).sort().reduce((
results: ParsedResult,
target: string,
): ParsedResult => {
if (target !== "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;
}
// Warn when specifying minor/patch as a decimal
if (typeof value === "number" && value % 1 !== 0) {
results.decimalWarnings.push({ target, value });
}
return results;
},
{
targets: targetOpts,
decimalWarnings: [],
},
);
// 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);

View File

@ -12,9 +12,11 @@ export type ModuleOption = false | "amd" | "commonjs" | "systemjs" | "umd";
export type BuiltInsOption = false | "entry" | "usage";
export type Options = {
configPath: string,
debug: boolean,
exclude: Array<string>,
forceAllTransforms: boolean,
ignoreBrowserslistConfig: boolean,
include: Array<string>,
loose: boolean,
modules: ModuleOption,

View File

@ -12,8 +12,8 @@ Using targets:
{
"chrome": "54",
"electron": "0.36",
"node": "6.1",
"ie": "10"
"ie": "10",
"node": "6.1"
}
Using modules transform: commonjs
@ -25,10 +25,10 @@ Using plugins:
transform-es2015-block-scoping { "electron":"0.36", "ie":"10" }
transform-es2015-classes { "ie":"10" }
transform-es2015-computed-properties { "ie":"10" }
transform-es2015-destructuring { "electron":"0.36", "node":"6.1", "ie":"10" }
transform-es2015-destructuring { "electron":"0.36", "ie":"10", "node":"6.1" }
transform-es2015-duplicate-keys { "ie":"10" }
transform-es2015-for-of { "electron":"0.36", "node":"6.1", "ie":"10" }
transform-es2015-function-name { "electron":"0.36", "node":"6.1", "ie":"10" }
transform-es2015-for-of { "electron":"0.36", "ie":"10", "node":"6.1" }
transform-es2015-function-name { "electron":"0.36", "ie":"10", "node":"6.1" }
transform-es2015-literals { "ie":"10" }
transform-es2015-object-super { "ie":"10" }
transform-es2015-parameters { "electron":"0.36", "ie":"10" }
@ -39,28 +39,28 @@ Using plugins:
transform-es2015-typeof-symbol { "ie":"10" }
transform-es2015-unicode-regex { "electron":"0.36", "ie":"10" }
transform-regenerator { "electron":"0.36", "ie":"10" }
transform-exponentiation-operator { "electron":"0.36", "node":"6.1", "ie":"10" }
transform-async-to-generator { "chrome":"54", "electron":"0.36", "node":"6.1", "ie":"10" }
syntax-trailing-function-commas { "chrome":"54", "electron":"0.36", "node":"6.1", "ie":"10" }
transform-exponentiation-operator { "electron":"0.36", "ie":"10", "node":"6.1" }
transform-async-to-generator { "chrome":"54", "electron":"0.36", "ie":"10", "node":"6.1" }
syntax-trailing-function-commas { "chrome":"54", "electron":"0.36", "ie":"10", "node":"6.1" }
Using polyfills with `entry` option:
[src/in.js] Replaced `babel-polyfill` with the following polyfills:
es6.typed.array-buffer { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.typed.array-buffer { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.typed.data-view { "electron":"0.36" }
es6.typed.int8-array { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.typed.uint8-array { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.typed.uint8-clamped-array { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.typed.int16-array { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.typed.uint16-array { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.typed.int32-array { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.typed.uint32-array { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.typed.float32-array { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.typed.float64-array { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.map { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.set { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.weak-map { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.weak-set { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.typed.int8-array { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.typed.uint8-array { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.typed.uint8-clamped-array { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.typed.int16-array { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.typed.uint16-array { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.typed.int32-array { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.typed.uint32-array { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.typed.float32-array { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.typed.float64-array { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.map { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.set { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.weak-map { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.weak-set { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.reflect.apply { "electron":"0.36", "ie":"10" }
es6.reflect.construct { "electron":"0.36", "ie":"10" }
es6.reflect.define-property { "electron":"0.36", "ie":"10" }
@ -74,12 +74,12 @@ Using polyfills with `entry` option:
es6.reflect.prevent-extensions { "electron":"0.36", "ie":"10" }
es6.reflect.set { "electron":"0.36", "ie":"10" }
es6.reflect.set-prototype-of { "electron":"0.36", "ie":"10" }
es6.promise { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.symbol { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.promise { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.symbol { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.object.assign { "ie":"10" }
es6.object.is { "ie":"10" }
es6.object.set-prototype-of { "ie":"10" }
es6.function.name { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.function.name { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.string.raw { "ie":"10" }
es6.string.from-code-point { "ie":"10" }
es6.string.code-point-at { "ie":"10" }
@ -92,7 +92,7 @@ Using polyfills with `entry` option:
es6.regexp.replace { "electron":"0.36", "ie":"10" }
es6.regexp.split { "electron":"0.36", "ie":"10" }
es6.regexp.search { "electron":"0.36", "ie":"10" }
es6.array.from { "electron":"0.36", "node":"6.1", "ie":"10" }
es6.array.from { "electron":"0.36", "ie":"10", "node":"6.1" }
es6.array.of { "ie":"10" }
es6.array.copy-within { "ie":"10" }
es6.array.find { "ie":"10" }
@ -124,12 +124,12 @@ Using polyfills with `entry` option:
es6.math.tanh { "ie":"10" }
es6.math.trunc { "ie":"10" }
es7.array.includes { "ie":"10" }
es7.object.values { "electron":"0.36", "node":"6.1", "ie":"10" }
es7.object.entries { "electron":"0.36", "node":"6.1", "ie":"10" }
es7.object.get-own-property-descriptors { "electron":"0.36", "node":"6.1", "ie":"10" }
es7.string.pad-start { "chrome":"54", "electron":"0.36", "node":"6.1", "ie":"10" }
es7.string.pad-end { "chrome":"54", "electron":"0.36", "node":"6.1", "ie":"10" }
web.timers { "chrome":"54", "electron":"0.36", "node":"6.1", "ie":"10" }
web.immediate { "chrome":"54", "electron":"0.36", "node":"6.1", "ie":"10" }
web.dom.iterable { "chrome":"54", "electron":"0.36", "node":"6.1", "ie":"10" }
es7.object.values { "electron":"0.36", "ie":"10", "node":"6.1" }
es7.object.entries { "electron":"0.36", "ie":"10", "node":"6.1" }
es7.object.get-own-property-descriptors { "electron":"0.36", "ie":"10", "node":"6.1" }
es7.string.pad-start { "chrome":"54", "electron":"0.36", "ie":"10", "node":"6.1" }
es7.string.pad-end { "chrome":"54", "electron":"0.36", "ie":"10", "node":"6.1" }
web.timers { "chrome":"54", "electron":"0.36", "ie":"10", "node":"6.1" }
web.immediate { "chrome":"54", "electron":"0.36", "ie":"10", "node":"6.1" }
web.dom.iterable { "chrome":"54", "electron":"0.36", "ie":"10", "node":"6.1" }
src/in.js -> lib/in.js

View File

@ -3,8 +3,8 @@ babel-preset-env: `DEBUG` option
Using targets:
{
"chrome": "54",
"node": "6.10",
"ie": "10"
"ie": "10",
"node": "6.10"
}
Using modules transform: commonjs
@ -30,9 +30,9 @@ Using plugins:
transform-es2015-typeof-symbol { "ie":"10" }
transform-es2015-unicode-regex { "ie":"10" }
transform-regenerator { "ie":"10" }
transform-exponentiation-operator { "node":"6.10", "ie":"10" }
transform-async-to-generator { "chrome":"54", "node":"6.10", "ie":"10" }
syntax-trailing-function-commas { "chrome":"54", "node":"6.10", "ie":"10" }
transform-exponentiation-operator { "ie":"10", "node":"6.10" }
transform-async-to-generator { "chrome":"54", "ie":"10", "node":"6.10" }
syntax-trailing-function-commas { "chrome":"54", "ie":"10", "node":"6.10" }
Using polyfills with `entry` option:
@ -114,12 +114,12 @@ Using polyfills with `entry` option:
es6.math.tanh { "ie":"10" }
es6.math.trunc { "ie":"10" }
es7.array.includes { "ie":"10" }
es7.object.values { "node":"6.10", "ie":"10" }
es7.object.entries { "node":"6.10", "ie":"10" }
es7.object.get-own-property-descriptors { "node":"6.10", "ie":"10" }
es7.string.pad-start { "chrome":"54", "node":"6.10", "ie":"10" }
es7.string.pad-end { "chrome":"54", "node":"6.10", "ie":"10" }
web.timers { "chrome":"54", "node":"6.10", "ie":"10" }
web.immediate { "chrome":"54", "node":"6.10", "ie":"10" }
web.dom.iterable { "chrome":"54", "node":"6.10", "ie":"10" }
es7.object.values { "ie":"10", "node":"6.10" }
es7.object.entries { "ie":"10", "node":"6.10" }
es7.object.get-own-property-descriptors { "ie":"10", "node":"6.10" }
es7.string.pad-start { "chrome":"54", "ie":"10", "node":"6.10" }
es7.string.pad-end { "chrome":"54", "ie":"10", "node":"6.10" }
web.timers { "chrome":"54", "ie":"10", "node":"6.10" }
web.immediate { "chrome":"54", "ie":"10", "node":"6.10" }
web.dom.iterable { "chrome":"54", "ie":"10", "node":"6.10" }
src/in.js -> lib/in.js

View File

@ -0,0 +1,10 @@
{
"presets": [
["../../../../lib", {
"configPath": "../fixtures/preset-options/browserslist-config-ignore-with-false",
"modules": false,
"debug": true,
"ignoreBrowserslistConfig": true
}]
]
}

View File

@ -0,0 +1 @@
const a = 1;

View File

@ -0,0 +1 @@
const a = 1;

View File

@ -0,0 +1,9 @@
{
"presets": [
["../../../../lib", {
"configPath": "../fixtures/preset-options/browserslist-config",
"modules": false,
"debug": true
}]
]
}

View File

@ -0,0 +1,13 @@
{
"presets": [
["../../../../lib", {
"configPath": "../fixtures/preset-options/browserslist-package-ignore-with-array",
"targets": {
"chrome": 55,
"browsers": []
},
"modules": false,
"debug": true
}]
]
}

View File

@ -0,0 +1,3 @@
{
"browserslist": "> 2%"
}

View File

@ -0,0 +1 @@
const a = 1;

View File

@ -0,0 +1,12 @@
{
"presets": [
["../../../../lib", {
"configPath": "../fixtures/preset-options/browserslist-package",
"targets": {
"chrome": 55
},
"modules": false,
"debug": true
}]
]
}

View File

@ -0,0 +1,3 @@
{
"browserslist": "> 2%"
}