diff --git a/experimental/babel-preset-env/package.json b/experimental/babel-preset-env/package.json index 4cca9aaa4e..aaf02e4d5e 100644 --- a/experimental/babel-preset-env/package.json +++ b/experimental/babel-preset-env/package.json @@ -45,7 +45,8 @@ "babel-plugin-transform-es2015-unicode-regex": "^6.3.13", "babel-plugin-transform-exponentiation-operator": "^6.8.0", "babel-plugin-transform-regenerator": "^6.6.0", - "browserslist": "^1.4.0" + "browserslist": "^1.4.0", + "invariant": "^2.2.2" }, "devDependencies": { "babel-cli": "^6.11.4", diff --git a/experimental/babel-preset-env/src/default-includes.js b/experimental/babel-preset-env/src/default-includes.js new file mode 100644 index 0000000000..77477580e7 --- /dev/null +++ b/experimental/babel-preset-env/src/default-includes.js @@ -0,0 +1,5 @@ +export default [ + "web.timers", + "web.immediate", + "web.dom.iterable" +]; diff --git a/experimental/babel-preset-env/src/index.js b/experimental/babel-preset-env/src/index.js index 85ef642efa..4a271b57ea 100644 --- a/experimental/babel-preset-env/src/index.js +++ b/experimental/babel-preset-env/src/index.js @@ -1,29 +1,12 @@ -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"; + +import builtInsList from "../data/built-ins.json"; +import defaultInclude from "./default-includes"; import electronToChromium from "../data/electron-to-chromium"; - -export const MODULE_TRANSFORMATIONS = { - "amd": "transform-es2015-modules-amd", - "commonjs": "transform-es2015-modules-commonjs", - "systemjs": "transform-es2015-modules-systemjs", - "umd": "transform-es2015-modules-umd" -}; - -const defaultInclude = [ - "web.timers", - "web.immediate", - "web.dom.iterable" -]; - -export const validIncludesAndExcludes = [ - ...Object.keys(pluginFeatures), - ...Object.keys(MODULE_TRANSFORMATIONS).map((m) => MODULE_TRANSFORMATIONS[m]), - ...Object.keys(builtInsList), - ...defaultInclude -]; +import moduleTransformations from "./module-transformations"; +import normalizeOptions from "./normalize-options.js"; +import pluginList from "../data/plugins.json"; +import transformPolyfillRequirePlugin from "./transform-polyfill-require-plugin"; /** * Determine if a transformation is required @@ -51,7 +34,7 @@ export const isPluginRequired = (supportedEnvironments, plugin) => { const lowestTargetedVersion = supportedEnvironments[environment]; if (typeof lowestTargetedVersion === "string") { - throw new Error(`Target version must be a number, + throw new Error(`Target version must be a number, '${lowestTargetedVersion}' was given for '${environment}'`); } @@ -153,69 +136,7 @@ export const getTargets = (targets = {}) => { return targetOps; }; -// TODO: Allow specifying plugins as either shortened or full name -// babel-plugin-transform-es2015-classes -// transform-es2015-classes -export const validateLooseOption = (looseOpt = false) => { - if (typeof looseOpt !== "boolean") { - throw new Error("Preset env: 'loose' option must be a boolean."); - } - - return looseOpt; -}; - -export const validateModulesOption = (modulesOpt = "commonjs") => { - if (modulesOpt !== false && Object.keys(MODULE_TRANSFORMATIONS).indexOf(modulesOpt) === -1) { - throw new Error("The 'modules' option must be 'false' to indicate no modules\n" + - "or a module type which be be one of: 'commonjs' (default), 'amd', 'umd', 'systemjs'"); - } - - return modulesOpt; -}; - -export function validatePluginsOption(opts = [], type) { - if (!Array.isArray(opts)) { - throw new Error(`The '${type}' option must be an Array of plugins/built-ins`); - } - - const 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 { - all: opts, - plugins: opts.filter((opt) => !opt.match(/^(es\d+|web)\./)), - builtIns: opts.filter((opt) => opt.match(/^(es\d+|web)\./)) - }; -} - -const validateIncludeOption = (opts) => validatePluginsOption(opts, "include"); -const validateExcludeOption = (opts) => validatePluginsOption(opts, "exclude"); - -export function checkDuplicateIncludeExcludes(include, exclude) { - const 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] || {}; @@ -237,21 +158,19 @@ const filterItem = (targets, exclusions, list, item) => { return isRequired && notExcluded; }; +export const transformIncludesAndExculdes = (opts) => ({ + all: opts, + plugins: opts.filter((opt) => !opt.match(/^(es\d+|web)\./)), + builtIns: opts.filter((opt) => opt.match(/^(es\d+|web)\./)) +}); + export default function buildPreset(context, opts = {}) { - const loose = validateLooseOption(opts.loose); - const moduleType = validateModulesOption(opts.modules); - // 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.all, exclude.all); - const targets = getTargets(opts.targets); - const debug = opts.debug; - const useBuiltIns = opts.useBuiltIns; + const validatedOptions = normalizeOptions(opts); + const {debug, loose, moduleType, useBuiltIns} = validatedOptions; + + const targets = getTargets(validatedOptions.targets); + const include = transformIncludesAndExculdes(validatedOptions.include); + const exclude = transformIncludesAndExculdes(validatedOptions.exclude); const filterPlugins = filterItem.bind(null, targets, exclude.plugins, pluginList); const transformations = Object.keys(pluginList) @@ -287,7 +206,7 @@ export default function buildPreset(context, opts = {}) { } const regenerator = transformations.indexOf("transform-regenerator") >= 0; - const modulePlugin = moduleType !== false && MODULE_TRANSFORMATIONS[moduleType]; + const modulePlugin = moduleType !== false && moduleTransformations[moduleType]; const plugins = []; modulePlugin && diff --git a/experimental/babel-preset-env/src/module-transformations.js b/experimental/babel-preset-env/src/module-transformations.js new file mode 100644 index 0000000000..f966f01a18 --- /dev/null +++ b/experimental/babel-preset-env/src/module-transformations.js @@ -0,0 +1,6 @@ +export default { + "amd": "transform-es2015-modules-amd", + "commonjs": "transform-es2015-modules-commonjs", + "systemjs": "transform-es2015-modules-systemjs", + "umd": "transform-es2015-modules-umd" +}; diff --git a/experimental/babel-preset-env/src/normalize-options.js b/experimental/babel-preset-env/src/normalize-options.js new file mode 100644 index 0000000000..0af48ece18 --- /dev/null +++ b/experimental/babel-preset-env/src/normalize-options.js @@ -0,0 +1,93 @@ +import intersection from "lodash/intersection"; +import invariant from "invariant"; + +import builtInsList from "../data/built-ins.json"; +import defaultInclude from "./default-includes"; +import moduleTransformations from "./module-transformations"; +import pluginFeatures from "../data/plugin-features"; + +const hasBeenWarned = false; + +const validIncludesAndExcludes = [ + ...Object.keys(pluginFeatures), + ...Object.keys(moduleTransformations).map((m) => moduleTransformations[m]), + ...Object.keys(builtInsList), + ...defaultInclude +]; + +export const validateIncludesAndExcludes = (opts = [], type) => { + invariant( + Array.isArray(opts), + `Invalid Option: The '${type}' option must be an Array of plugins/built-ins` + ); + + const unknownOpts = opts.filter((opt) => !validIncludesAndExcludes.includes(opt)); + + invariant( + unknownOpts.length === 0, + `Invalid Option: The plugins/built-ins '${unknownOpts}' passed to the '${type}' option are not + valid. Please check data/[plugin-features|built-in-features].js in babel-preset-env` + ); + + return opts; +}; + +export const checkDuplicateIncludeExcludes = (include = [], exclude = []) => { + const duplicates = intersection(include, exclude); + + invariant( + duplicates.length === 0, + `Invalid Option: The plugins/built-ins '${duplicates}' were found in both the "include" and + "exclude" options.` + ); +}; + +// TODO: Allow specifying plugins as either shortened or full name +// babel-plugin-transform-es2015-classes +// transform-es2015-classes +export const validateLooseOption = (looseOpt = false) => { + invariant( + typeof looseOpt === "boolean", + "Invalid Option: The 'loose' option must be a boolean." + ); + + return looseOpt; +}; + +export const validateModulesOption = (modulesOpt = "commonjs") => { + invariant( + modulesOpt === false || Object.keys(moduleTransformations).includes(modulesOpt), + `Invalid Option: The 'modules' option must be either 'false' to indicate no modules, or a + module type which can be be one of: 'commonjs' (default), 'amd', 'umd', 'systemjs'.` + ); + + return modulesOpt; +}; + +export default function normalizeOptions(opts) { + // TODO: remove whitelist in favor of include in next major + if (opts.whitelist && !hasBeenWarned) { + console.warn( + `Deprecation Warning: The "whitelist" option has been deprecated in favor of "include" to + match the newly added "exclude" option (instead of "blacklist").` + ); + } + + invariant( + !(opts.whitelist && opts.include), + `Invalid Option: The "whitelist" and the "include" option are the same and one can be used at + a time` + ); + + checkDuplicateIncludeExcludes(opts.whitelist || opts.include, opts.exclude); + + return { + debug: opts.debug, + exclude: validateIncludesAndExcludes(opts.exclude, "exclude"), + include: validateIncludesAndExcludes(opts.whitelist || opts.include, "include"), + loose: validateLooseOption(opts.loose), + moduleType: validateModulesOption(opts.modules), + targets: opts.targets, + useBuiltIns: opts.useBuiltIns + }; +} diff --git a/experimental/babel-preset-env/test/index.js b/experimental/babel-preset-env/test/index.spec.js similarity index 58% rename from experimental/babel-preset-env/test/index.js rename to experimental/babel-preset-env/test/index.spec.js index 3fc6753009..c27e55dc53 100644 --- a/experimental/babel-preset-env/test/index.js +++ b/experimental/babel-preset-env/test/index.spec.js @@ -4,13 +4,6 @@ const babelPresetEnv = require("../lib/index.js"); const assert = require("assert"); const electronToChromiumData = require("../data/electron-to-chromium"); -const { - validateModulesOption, - validateLooseOption, - validatePluginsOption, - checkDuplicateIncludeExcludes -} = babelPresetEnv; - describe("babel-preset-env", () => { describe("getTargets", () => { it("should return the current node version with option 'current'", function() { @@ -173,115 +166,27 @@ describe("babel-preset-env", () => { }); }); - describe("validateLooseOption", () => { - it("`undefined` option returns false", () => { - assert(validateLooseOption() === false); + describe("transformIncludesAndExculdes", function() { + it("should return in transforms array", function() { + assert.deepEqual( + babelPresetEnv.transformIncludesAndExculdes(["transform-es2015-arrow-functions"]), + { + all: ["transform-es2015-arrow-functions"], + plugins: ["transform-es2015-arrow-functions"], + builtIns: [] + } + ); }); - it("`false` option returns false", () => { - assert(validateLooseOption(false) === false); - }); - - it("`true` option returns true", () => { - assert(validateLooseOption(true) === true); - }); - - it("array option is invalid", () => { - assert.throws(() => { - validateModulesOption([]); - }, Error); - }); - }); - - describe("validateModulesOption", () => { - it("`undefined` option returns commonjs", () => { - assert(validateModulesOption() === "commonjs"); - }); - - it("`false` option returns commonjs", () => { - assert(validateModulesOption(false) === false); - }); - - it("commonjs option is valid", () => { - assert(validateModulesOption("commonjs") === "commonjs"); - }); - - it("systemjs option is valid", () => { - assert(validateModulesOption("systemjs") === "systemjs"); - }); - - it("amd option is valid", () => { - assert(validateModulesOption("amd") === "amd"); - }); - - it("umd option is valid", () => { - assert(validateModulesOption("umd") === "umd"); - }); - - it("`true` option is invalid", () => { - assert.throws(() => { - validateModulesOption(true); - }, Error); - }); - - it("array option is invalid", () => { - assert.throws(() => { - assert(validateModulesOption([])); - }, Error); - }); - - describe("validatePluginsOption", function() { - it("should return empty arrays if undefined", function() { - assert.deepEqual(validatePluginsOption(), { all: [], plugins: [], builtIns: [] }); - }); - - it("should return in transforms array", function() { - assert.deepEqual( - validatePluginsOption(["transform-es2015-arrow-functions"]), - { - all: ["transform-es2015-arrow-functions"], - plugins: ["transform-es2015-arrow-functions"], - builtIns: [] - } - ); - }); - - it("should return in built-ins array", function() { - assert.deepEqual( - validatePluginsOption(["es6.map"]), - { - all: ["es6.map"], - plugins: [], - builtIns: ["es6.map"] - } - ); - }); - - 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); - }); + it("should return in built-ins array", function() { + assert.deepEqual( + babelPresetEnv.transformIncludesAndExculdes(["es6.map"]), + { + all: ["es6.map"], + plugins: [], + builtIns: ["es6.map"] + } + ); }); }); }); diff --git a/experimental/babel-preset-env/test/normalize-options.spec.js b/experimental/babel-preset-env/test/normalize-options.spec.js new file mode 100644 index 0000000000..3dbf16c9f6 --- /dev/null +++ b/experimental/babel-preset-env/test/normalize-options.spec.js @@ -0,0 +1,101 @@ +"use strict"; + +const normalizeOptions = require("../lib/normalize-options.js"); +const assert = require("assert"); + +const { + checkDuplicateIncludeExcludes, + validateIncludesAndExcludes, + validateLooseOption, + validateModulesOption +} = normalizeOptions; + +describe("normalize-options", () => { + describe("validateLooseOption", () => { + it("`undefined` option returns false", () => { + assert(validateLooseOption() === false); + }); + + it("`false` option returns false", () => { + assert(validateLooseOption(false) === false); + }); + + it("`true` option returns true", () => { + assert(validateLooseOption(true) === true); + }); + + it("array option is invalid", () => { + assert.throws(() => { + validateLooseOption([]); + }, 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); + }); + }); + + describe("validateModulesOption", () => { + it("`undefined` option returns commonjs", () => { + assert(validateModulesOption() === "commonjs"); + }); + + it("`false` option returns commonjs", () => { + assert(validateModulesOption(false) === false); + }); + + it("commonjs option is valid", () => { + assert(validateModulesOption("commonjs") === "commonjs"); + }); + + it("systemjs option is valid", () => { + assert(validateModulesOption("systemjs") === "systemjs"); + }); + + it("amd option is valid", () => { + assert(validateModulesOption("amd") === "amd"); + }); + + it("umd option is valid", () => { + assert(validateModulesOption("umd") === "umd"); + }); + + it("`true` option is invalid", () => { + assert.throws(() => { + validateModulesOption(true); + }, Error); + }); + + it("array option is invalid", () => { + assert.throws(() => { + assert(validateModulesOption([])); + }, Error); + }); + }); + describe("validateIncludesAndExcludes", function() { + it("should return empty arrays if undefined", function() { + assert.deepEqual(validateIncludesAndExcludes(), []); + }); + it("should throw if not in features", function() { + assert.throws(() => { + validateIncludesAndExcludes(["asdf"]); + }, Error); + }); + }); +});