Skip to content
This repository has been archived by the owner on May 11, 2018. It is now read-only.

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

Merged
merged 34 commits into from
Aug 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
937a4cf
Bump browserslist.
yavorsky Feb 6, 2017
6036b8a
Always run browserslist with targets as defaults.
yavorsky Feb 6, 2017
4a4a09e
Add fixtures for browserslist config file.
yavorsky Feb 6, 2017
37461b5
Add fixtures for browserslist in package json.
yavorsky Feb 6, 2017
60247eb
Add 2 tests for objectToBrowserslist.
yavorsky Feb 6, 2017
b70f72e
Update yarn.lock.
yavorsky Feb 6, 2017
fd334c1
Replace explicit path with fileContext.
yavorsky Feb 23, 2017
335343f
Use fileContext for built-ins filter.
yavorsky Mar 1, 2017
bbf0ba5
Prettify for easier merge
yavorsky Mar 18, 2017
29efa70
Merge branch '2.0' into browserslist-config
yavorsky Mar 18, 2017
17438b3
Fix lost after merge variables.
yavorsky Mar 19, 2017
2480177
Bump babel-cli
yavorsky Mar 20, 2017
7c91339
Add fileContext to `getTargets`.
yavorsky Mar 20, 2017
949ed1f
Replace includes with indexOf
yavorsky Mar 20, 2017
38e8ebe
Remove unused isBrowsersQueryValid.
yavorsky Mar 20, 2017
b869e6a
Add test for getTargets + fileContext.
yavorsky Mar 20, 2017
eb2543e
Remove log option
yavorsky Mar 20, 2017
dd9ad68
Prevent browserslist config search with false/null
yavorsky Mar 20, 2017
6e0ddb4
Add ignoreBrowserslistConfig option.
yavorsky Mar 22, 2017
ebc051e
Test for ignoreBrowserslistConfig.
yavorsky Mar 22, 2017
1882837
Add ignoreBrowserslistConfig to README.
yavorsky Mar 22, 2017
b74a5bf
browserslist config: Readme updates.
yavorsky Mar 25, 2017
fea6d0e
Merge branch '2.0' into browserslist-config
yavorsky Jun 29, 2017
a98d2dd
Add ignoreBrowsersConfig/dirname options
yavorsky Jun 30, 2017
05d4882
Rebase and make some changes with options.
yavorsky Jun 30, 2017
ba3298d
README: Add `dirname` desription.
yavorsky Jul 1, 2017
7d16f02
Merge branch '2.0' into browserslist-config
yavorsky Jul 1, 2017
70ae0c8
[skip ci]
hzoo Jul 19, 2017
72765ee
[skip ci]
existentialism Jul 31, 2017
0e48211
[skip ci]
existentialism Jul 31, 2017
e7b189a
[skip ci]
existentialism Jul 31, 2017
cbcb8a0
Rename dirname option to configPath
existentialism Aug 1, 2017
939ed63
rerun prettier
existentialism Aug 1, 2017
a8329b4
derp
existentialism Aug 1, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down
5 changes: 2 additions & 3 deletions scripts/build-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
4 changes: 3 additions & 1 deletion src/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Expand Down
8 changes: 6 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,18 @@ export default function buildPreset(
opts: Object = {},
): { plugins: Array<Plugin> } {
const {
configPath,
debug,
exclude: optionsExclude,
forceAllTransforms,
ignoreBrowserslistConfig,
include: optionsInclude,
loose,
modules,
spec,
targets: optionsTargets,
useBuiltIns,
} = normalizeOptions(opts);

// TODO: remove this in next major
let hasUglifyTarget = false;

Expand All @@ -132,7 +133,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);

Expand Down
49 changes: 46 additions & 3 deletions src/normalize-options.js
Original file line number Diff line number Diff line change
@@ -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),
Expand All @@ -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-/, "");

Expand All @@ -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,
Expand All @@ -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",
) => {
Expand All @@ -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 => {
Expand All @@ -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),
Expand Down
93 changes: 54 additions & 39 deletions src/targets-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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(" ");
Expand Down Expand Up @@ -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 = {};

// Parse browsers target via browserslist
if (isBrowsersQueryValid(targets.browsers)) {
targetOpts = getLowestVersions(browserslist(targets.browsers));
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);

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

// 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;
}
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 });
}

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

Expand Down
2 changes: 2 additions & 0 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading