From 45370e3553dcc3a00787adba0f46c7595650bfc3 Mon Sep 17 00:00:00 2001 From: Henry Zhu Date: Tue, 13 Dec 2016 16:16:54 -0500 Subject: [PATCH] add exclude option, rename whitelist to include (#89) --- experimental/babel-preset-env/README.md | 35 +++++--- experimental/babel-preset-env/src/index.js | 74 +++++++++++----- .../src/transform-polyfill-require-plugin.js | 6 +- .../exclude-regenerator/actual.js | 1 + .../exclude-regenerator/expected.js | 85 +++++++++++++++++++ .../exclude-regenerator/options.json | 9 ++ .../fixtures/preset-options/exclude/actual.js | 3 + .../preset-options/exclude/expected.js | 3 + .../preset-options/exclude/options.json | 12 +++ .../{whitelist => include}/actual.js | 0 .../{whitelist => include}/expected.js | 0 .../{whitelist => include}/options.json | 2 +- experimental/babel-preset-env/test/index.js | 44 +++++++++- 13 files changed, 237 insertions(+), 37 deletions(-) create mode 100644 experimental/babel-preset-env/test/fixtures/preset-options/exclude-regenerator/actual.js create mode 100644 experimental/babel-preset-env/test/fixtures/preset-options/exclude-regenerator/expected.js create mode 100644 experimental/babel-preset-env/test/fixtures/preset-options/exclude-regenerator/options.json create mode 100644 experimental/babel-preset-env/test/fixtures/preset-options/exclude/actual.js create mode 100644 experimental/babel-preset-env/test/fixtures/preset-options/exclude/expected.js create mode 100644 experimental/babel-preset-env/test/fixtures/preset-options/exclude/options.json rename experimental/babel-preset-env/test/fixtures/preset-options/{whitelist => include}/actual.js (100%) rename experimental/babel-preset-env/test/fixtures/preset-options/{whitelist => include}/expected.js (100%) rename experimental/babel-preset-env/test/fixtures/preset-options/{whitelist => include}/options.json (63%) diff --git a/experimental/babel-preset-env/README.md b/experimental/babel-preset-env/README.md index d40030fdcf..6c74a36ddb 100644 --- a/experimental/babel-preset-env/README.md +++ b/experimental/babel-preset-env/README.md @@ -113,15 +113,29 @@ Can be `false` to not transform modules. Defaults to `false` `console.log` out the targets and plugins being used as well as the version specified in `/data/plugins.json`. -### `whitelist`: `Array` +### `include`: `Array` + +> `whitelist` is deprecated and will be removed in the next major in favor of this. Defaults to `[]` -Enable a whitelist of plugins to always include. +An array of plugins to always include. + +Valid options include any of the [babel plugins](/data/plugin-features.js) or [built-ins](/data/built-in-features.js) such as `transform-es2015-arrow-functions` or `map`, `set`, `object.assign`. + +> For the built-ins like `es6.typed.data-view` just put `typed.data-view`. Useful if there is a bug in a native implementation, or a combination of a non-supported feature + a supported one doesn't work. Ex: Node 4 supports native classes but not spread. +### `exclude`: `Array` + +Defaults to `[]` +An array of plugins to always exclude/remove. +The possible options are the same as the `include` option. + +Useful for "blacklisting" a transform like `transform-regenerator` if you don't use generators and don't want to include `regeneratorRuntime` (when using `useBuiltIns`) or for using another plugin like [fast-async](https://github.com/MatAtBread/fast-async) instead of `async-to-gen`(http://babeljs.io/docs/plugins/transform-async-generator-functions/). + ### `useBuiltIns`: `boolean` Defaults to `false`. @@ -262,27 +276,22 @@ transform-async-to-generator {} syntax-trailing-function-commas {} ``` -### Example with `whitelist` +### Example with `include`/`exclude` + +> always include arrow functions, explicitly blacklist generators ```js -// target chrome 52 with whitelist on arrow functions { "presets": [ ["env", { "targets": { - "chrome": 52 + "browsers": ["last 2 versions", "safari >= 7"] }, - "whitelist": ["transform-es2015-arrow-functions"] + "include": ["transform-es2015-arrow-functions"], + "exclude": ["transform-regenerator"] }] ] } - -Using plugins: - -transform-exponentiation-operator {} -transform-async-to-generator {} -syntax-trailing-function-commas {} -transform-es2015-arrow-functions {} ``` ## Caveats diff --git a/experimental/babel-preset-env/src/index.js b/experimental/babel-preset-env/src/index.js index df304fb48b..18178e99a8 100644 --- a/experimental/babel-preset-env/src/index.js +++ b/experimental/babel-preset-env/src/index.js @@ -1,4 +1,5 @@ import pluginList from "../data/plugins.json"; +import pluginFeatures from "../data/plugin-features"; import builtInsList from "../data/built-ins.json"; import browserslist from "browserslist"; import transformPolyfillRequirePlugin from "./transform-polyfill-require-plugin"; @@ -11,6 +12,12 @@ export const MODULE_TRANSFORMATIONS = { "umd": "transform-es2015-modules-umd" }; +export const validIncludesAndExcludes = [ + ...Object.keys(pluginFeatures), + ...Object.values(MODULE_TRANSFORMATIONS), + ...Object.keys(builtInsList).slice(4) // remove the `es6.` +]; + /** * Determine if a transformation is required * @param {Object} supportedEnvironments An Object containing environment keys and the lowest @@ -156,27 +163,45 @@ export const validateModulesOption = (modulesOpt = "commonjs") => { return modulesOpt; }; -export const validateWhitelistOption = (whitelistOpt = []) => { - if (!Array.isArray(whitelistOpt)) { - throw new Error(`The 'whitelist' option must be an Array of plugins - { - "presets": [ - ["env", { - "targets": { - "chrome": 50 - }, - "whitelist": ["transform-es2015-arrow-functions"] - }] - ] - } - was passed "${whitelistOpt}" instead - `); +export function validatePluginsOption(opts = [], type) { + if (!Array.isArray(opts)) { + throw new Error(`The '${type}' option must be an Array of plugins/built-ins`); } - return whitelistOpt; -}; + let unknownOpts = []; + opts.forEach((opt) => { + if (validIncludesAndExcludes.indexOf(opt) === -1) { + unknownOpts.push(opt); + } + }); + + if (unknownOpts.length > 0) { + throw new Error(`Invalid plugins/built-ins '${unknownOpts}' passed to '${type}' option. + Check data/[plugin-features|built-in-features].js in babel-preset-env`); + } + + return opts; +} + +const validateIncludeOption = (opts) => validatePluginsOption(opts, "include"); +const validateExcludeOption = (opts) => validatePluginsOption(opts, "exclude"); + +export function checkDuplicateIncludeExcludes(include, exclude) { + let duplicates = []; + include.forEach((opt) => { + if (exclude.indexOf(opt) >= 0) { + duplicates.push(opt); + } + }); + + if (duplicates.length > 0) { + throw new Error(`Duplicate plugins/built-ins: '${duplicates}' found + in both the "include" and "exclude" options.`); + } +} let hasBeenLogged = false; +let hasBeenWarned = false; const logPlugin = (plugin, targets, list) => { const envList = list[plugin]; @@ -192,7 +217,15 @@ const logPlugin = (plugin, targets, list) => { export default function buildPreset(context, opts = {}) { const loose = validateLooseOption(opts.loose); const moduleType = validateModulesOption(opts.modules); - const whitelist = validateWhitelistOption(opts.whitelist); + // TODO: remove whitelist in favor of include in next major + if (opts.whitelist && !hasBeenWarned) { + hasBeenWarned = true; + console.warn(`The "whitelist" option has been deprecated + in favor of "include" to match the newly added "exclude" option (instead of "blacklist").`); + } + const include = validateIncludeOption(opts.whitelist || opts.include); + const exclude = validateExcludeOption(opts.exclude); + checkDuplicateIncludeExcludes(include, exclude); const targets = getTargets(opts.targets); const debug = opts.debug; const useBuiltIns = opts.useBuiltIns; @@ -226,7 +259,10 @@ export default function buildPreset(context, opts = {}) { } } - const allTransformations = [...transformations, ...whitelist]; + const allTransformations = transformations + .filter((plugin) => exclude.indexOf(plugin) === -1) + .concat(include); + const regenerator = allTransformations.indexOf("transform-regenerator") >= 0; const modulePlugin = moduleType !== false && MODULE_TRANSFORMATIONS[moduleType]; const plugins = []; diff --git a/experimental/babel-preset-env/src/transform-polyfill-require-plugin.js b/experimental/babel-preset-env/src/transform-polyfill-require-plugin.js index 87702c4c47..201543129d 100644 --- a/experimental/babel-preset-env/src/transform-polyfill-require-plugin.js +++ b/experimental/babel-preset-env/src/transform-polyfill-require-plugin.js @@ -2,7 +2,7 @@ function isPolyfillSource(value) { return value === "babel-polyfill" || value === "core-js"; } -const whitelist = [ +const alwaysInclude = [ "web.timers", "web.immediate", "web.dom.iterable" @@ -70,7 +70,7 @@ export default function ({ types: t }) { } path.replaceWithMultiple( - createImports([...state.opts.polyfills, ...whitelist], "import", state.opts.regenerator) + createImports([...state.opts.polyfills, ...alwaysInclude], "import", state.opts.regenerator) ); } }, @@ -91,7 +91,7 @@ to the "transform-polyfill-require" plugin } bodyPath.replaceWithMultiple( - createImports([...state.opts.polyfills, ...whitelist], "require", state.opts.regenerator) + createImports([...state.opts.polyfills, ...alwaysInclude], "require", state.opts.regenerator) ); } }); diff --git a/experimental/babel-preset-env/test/fixtures/preset-options/exclude-regenerator/actual.js b/experimental/babel-preset-env/test/fixtures/preset-options/exclude-regenerator/actual.js new file mode 100644 index 0000000000..d3984c50aa --- /dev/null +++ b/experimental/babel-preset-env/test/fixtures/preset-options/exclude-regenerator/actual.js @@ -0,0 +1 @@ +import "babel-polyfill"; diff --git a/experimental/babel-preset-env/test/fixtures/preset-options/exclude-regenerator/expected.js b/experimental/babel-preset-env/test/fixtures/preset-options/exclude-regenerator/expected.js new file mode 100644 index 0000000000..3dc2619431 --- /dev/null +++ b/experimental/babel-preset-env/test/fixtures/preset-options/exclude-regenerator/expected.js @@ -0,0 +1,85 @@ +import "core-js/modules/es6.typed.data-view"; +import "core-js/modules/es6.typed.int8-array"; +import "core-js/modules/es6.typed.uint8-array"; +import "core-js/modules/es6.typed.uint8-clamped-array"; +import "core-js/modules/es6.typed.int16-array"; +import "core-js/modules/es6.typed.uint16-array"; +import "core-js/modules/es6.typed.int32-array"; +import "core-js/modules/es6.typed.uint32-array"; +import "core-js/modules/es6.typed.float32-array"; +import "core-js/modules/es6.typed.float64-array"; +import "core-js/modules/es6.map"; +import "core-js/modules/es6.set"; +import "core-js/modules/es6.weak-map"; +import "core-js/modules/es6.weak-set"; +import "core-js/modules/es6.reflect.apply"; +import "core-js/modules/es6.reflect.construct"; +import "core-js/modules/es6.reflect.define-property"; +import "core-js/modules/es6.reflect.delete-property"; +import "core-js/modules/es6.reflect.get"; +import "core-js/modules/es6.reflect.get-own-property-descriptor"; +import "core-js/modules/es6.reflect.get-prototype-of"; +import "core-js/modules/es6.reflect.has"; +import "core-js/modules/es6.reflect.is-extensible"; +import "core-js/modules/es6.reflect.own-keys"; +import "core-js/modules/es6.reflect.prevent-extensions"; +import "core-js/modules/es6.reflect.set"; +import "core-js/modules/es6.reflect.set-prototype-of"; +import "core-js/modules/es6.promise"; +import "core-js/modules/es6.symbol"; +import "core-js/modules/es6.object.assign"; +import "core-js/modules/es6.object.is"; +import "core-js/modules/es6.object.set-prototype-of"; +import "core-js/modules/es6.function.name"; +import "core-js/modules/es6.string.raw"; +import "core-js/modules/es6.string.from-code-point"; +import "core-js/modules/es6.string.code-point-at"; +import "core-js/modules/es6.string.repeat"; +import "core-js/modules/es6.string.starts-with"; +import "core-js/modules/es6.string.ends-with"; +import "core-js/modules/es6.string.includes"; +import "core-js/modules/es6.regexp.flags"; +import "core-js/modules/es6.regexp.match"; +import "core-js/modules/es6.regexp.replace"; +import "core-js/modules/es6.regexp.split"; +import "core-js/modules/es6.regexp.search"; +import "core-js/modules/es6.array.from"; +import "core-js/modules/es6.array.of"; +import "core-js/modules/es6.array.copy-within"; +import "core-js/modules/es6.array.find"; +import "core-js/modules/es6.array.find-index"; +import "core-js/modules/es6.array.fill"; +import "core-js/modules/es6.array.iterator"; +import "core-js/modules/es6.number.is-finite"; +import "core-js/modules/es6.number.is-integer"; +import "core-js/modules/es6.number.is-safe-integer"; +import "core-js/modules/es6.number.is-nan"; +import "core-js/modules/es6.number.epsilon"; +import "core-js/modules/es6.number.min-safe-integer"; +import "core-js/modules/es6.number.max-safe-integer"; +import "core-js/modules/es6.math.acosh"; +import "core-js/modules/es6.math.asinh"; +import "core-js/modules/es6.math.atanh"; +import "core-js/modules/es6.math.cbrt"; +import "core-js/modules/es6.math.clz32"; +import "core-js/modules/es6.math.cosh"; +import "core-js/modules/es6.math.expm1"; +import "core-js/modules/es6.math.fround"; +import "core-js/modules/es6.math.hypot"; +import "core-js/modules/es6.math.imul"; +import "core-js/modules/es6.math.log1p"; +import "core-js/modules/es6.math.log10"; +import "core-js/modules/es6.math.log2"; +import "core-js/modules/es6.math.sign"; +import "core-js/modules/es6.math.sinh"; +import "core-js/modules/es6.math.tanh"; +import "core-js/modules/es6.math.trunc"; +import "core-js/modules/es7.array.includes.js"; +import "core-js/modules/es7.object.values"; +import "core-js/modules/es7.object.entries"; +import "core-js/modules/es7.object.get-own-property-descriptors"; +import "core-js/modules/es7.string.pad-start"; +import "core-js/modules/es7.string.pad-end"; +import "core-js/modules/web.timers"; +import "core-js/modules/web.immediate"; +import "core-js/modules/web.dom.iterable"; diff --git a/experimental/babel-preset-env/test/fixtures/preset-options/exclude-regenerator/options.json b/experimental/babel-preset-env/test/fixtures/preset-options/exclude-regenerator/options.json new file mode 100644 index 0000000000..ba73a45cce --- /dev/null +++ b/experimental/babel-preset-env/test/fixtures/preset-options/exclude-regenerator/options.json @@ -0,0 +1,9 @@ +{ + "presets": [ + ["../../../../lib", { + "modules": false, + "useBuiltIns": true, + "exclude": ["transform-regenerator"] + }] + ] +} diff --git a/experimental/babel-preset-env/test/fixtures/preset-options/exclude/actual.js b/experimental/babel-preset-env/test/fixtures/preset-options/exclude/actual.js new file mode 100644 index 0000000000..c70b44b5a4 --- /dev/null +++ b/experimental/babel-preset-env/test/fixtures/preset-options/exclude/actual.js @@ -0,0 +1,3 @@ +async function a() { + await 1; +} diff --git a/experimental/babel-preset-env/test/fixtures/preset-options/exclude/expected.js b/experimental/babel-preset-env/test/fixtures/preset-options/exclude/expected.js new file mode 100644 index 0000000000..5c88836e9e --- /dev/null +++ b/experimental/babel-preset-env/test/fixtures/preset-options/exclude/expected.js @@ -0,0 +1,3 @@ +async function a() { + await 1; +} \ No newline at end of file diff --git a/experimental/babel-preset-env/test/fixtures/preset-options/exclude/options.json b/experimental/babel-preset-env/test/fixtures/preset-options/exclude/options.json new file mode 100644 index 0000000000..72d58de065 --- /dev/null +++ b/experimental/babel-preset-env/test/fixtures/preset-options/exclude/options.json @@ -0,0 +1,12 @@ +{ + "presets": [ + ["../../../../lib", { + "modules": false, + "useBuiltIns": true, + "exclude": [ + "transform-async-to-generator", + "transform-regenerator" + ] + }] + ] +} diff --git a/experimental/babel-preset-env/test/fixtures/preset-options/whitelist/actual.js b/experimental/babel-preset-env/test/fixtures/preset-options/include/actual.js similarity index 100% rename from experimental/babel-preset-env/test/fixtures/preset-options/whitelist/actual.js rename to experimental/babel-preset-env/test/fixtures/preset-options/include/actual.js diff --git a/experimental/babel-preset-env/test/fixtures/preset-options/whitelist/expected.js b/experimental/babel-preset-env/test/fixtures/preset-options/include/expected.js similarity index 100% rename from experimental/babel-preset-env/test/fixtures/preset-options/whitelist/expected.js rename to experimental/babel-preset-env/test/fixtures/preset-options/include/expected.js diff --git a/experimental/babel-preset-env/test/fixtures/preset-options/whitelist/options.json b/experimental/babel-preset-env/test/fixtures/preset-options/include/options.json similarity index 63% rename from experimental/babel-preset-env/test/fixtures/preset-options/whitelist/options.json rename to experimental/babel-preset-env/test/fixtures/preset-options/include/options.json index b36882f25b..abc0443b29 100644 --- a/experimental/babel-preset-env/test/fixtures/preset-options/whitelist/options.json +++ b/experimental/babel-preset-env/test/fixtures/preset-options/include/options.json @@ -2,7 +2,7 @@ "presets": [ ["../../../../lib", { "targets": {}, - "whitelist": ["transform-es2015-modules-commonjs"], + "include": ["transform-es2015-modules-commonjs"], "modules": false }] ] diff --git a/experimental/babel-preset-env/test/index.js b/experimental/babel-preset-env/test/index.js index f80af2a05e..17e45c3102 100644 --- a/experimental/babel-preset-env/test/index.js +++ b/experimental/babel-preset-env/test/index.js @@ -6,7 +6,10 @@ const electronToChromiumData = require("../data/electron-to-chromium"); const { validateModulesOption, - validateLooseOption + validateLooseOption, + validatePluginsOption, + validIncludesAndExcludes, + checkDuplicateIncludeExcludes } = babelPresetEnv; describe("babel-preset-env", () => { @@ -214,5 +217,44 @@ describe("babel-preset-env", () => { assert(validateModulesOption([])); }, Error); }); + + describe("validatePluginsOption", function() { + it("should return an empty array if undefined", function() { + assert.deepEqual(validatePluginsOption(), []); + }); + + it("should return itself if in features", function() { + assert.deepEqual( + validatePluginsOption(validIncludesAndExcludes), + validIncludesAndExcludes + ); + }); + + it("should throw if not in features", function() { + assert.throws(() => { + validatePluginsOption(["asdf"]); + }, Error); + }); + }); + + describe("checkDuplicateIncludeExcludes", function() { + it("should throw if duplicate names in both", function() { + assert.throws(() => { + checkDuplicateIncludeExcludes( + ["transform-regenerator", "map"], + ["transform-regenerator", "map"] + ); + }, Error); + }); + + it("should not throw if no duplicate names in both", function() { + assert.doesNotThrow(() => { + checkDuplicateIncludeExcludes( + ["transform-regenerator"], + ["map"] + ); + }, Error); + }); + }); }); });