This commit is contained in:
Artem Yavorsky
2017-05-15 16:59:48 +03:00
committed by Brian Ng
parent d6202dc741
commit e1cb75989f
17 changed files with 193 additions and 147 deletions

View File

@@ -1,9 +1,15 @@
{
"extends": "babel",
"extends": [
"babel",
"plugin:flowtype/recommended"
],
"parserOptions": {
"ecmaVersion": 7,
"sourceType": "module"
},
"plugins": [
"flowtype"
],
"rules": {
"arrow-parens": "off",
"indent": "off",

View File

@@ -2,9 +2,11 @@
.*/lib/.*
.*/test/.*
.*/build/.*
.*/scripts/.*
.*/node_modules/jest-validate/.*
[options]
strip_root=true
suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe
suppress_comment= \\(.\\|\n\\)*\\$FlowIssue
module.ignore_non_literal_requires=true

View File

@@ -71,8 +71,8 @@
"electron-to-chromium": "^1.3.2",
"eslint": "^3.17.1",
"eslint-config-babel": "^6.0.0",
"eslint-plugin-flowtype": "^2.29.1",
"flow-bin": "^0.43.0",
"eslint-plugin-flowtype": "^2.33.0",
"flow-bin": "^0.46.0",
"fs-extra": "^2.0.0",
"husky": "^0.13.2",
"lint-staged": "^3.3.1",

View File

@@ -1,3 +1,5 @@
//@flow
import semver from "semver";
import builtInsList from "../data/built-ins.json";
import { logPlugin } from "./debug";
@@ -9,46 +11,41 @@ import useBuiltInsEntryPlugin from "./use-built-ins-entry-plugin";
import addUsedBuiltInsPlugin from "./use-built-ins-plugin";
import getTargets from "./targets-parser";
import { prettifyTargets, semverify } from "./utils";
import type { Plugin, Targets } from "./types";
/**
* Determine if a transformation is required
*
* NOTE: This assumes `supportedEnvironments` has already been parsed by `getTargets`
*
* @param {Object} supportedEnvironments An Object containing environment keys and the lowest
* supported version as a value
* @param {Object} plugin An Object containing environment keys and the lowest
* version the feature was implemented in as a value
* @return {Boolean} Whether or not the transformation is required
*/
export const isPluginRequired = (supportedEnvironments, plugin) => {
const targetEnvironments = Object.keys(supportedEnvironments);
export const isPluginRequired = (
supportedEnvironments: Targets,
plugin: Targets,
): boolean => {
const targetEnvironments: Array<string> = Object.keys(supportedEnvironments);
if (targetEnvironments.length === 0) {
return true;
}
const isRequiredForEnvironments = targetEnvironments.filter(environment => {
// Feature is not implemented in that environment
if (!plugin[environment]) {
return true;
}
const isRequiredForEnvironments: Array<string> = targetEnvironments.filter(
environment => {
// Feature is not implemented in that environment
if (!plugin[environment]) {
return true;
}
const lowestImplementedVersion = plugin[environment];
const lowestTargetedVersion = supportedEnvironments[environment];
const lowestImplementedVersion: string = plugin[environment];
const lowestTargetedVersion: string = supportedEnvironments[environment];
if (!semver.valid(lowestTargetedVersion)) {
throw new Error(
// eslint-disable-next-line max-len
`Invalid version passed for target "${environment}": "${lowestTargetedVersion}". Versions must be in semver format (major.minor.patch)`,
if (!semver.valid(lowestTargetedVersion)) {
throw new Error(
// eslint-disable-next-line max-len
`Invalid version passed for target "${environment}": "${lowestTargetedVersion}". Versions must be in semver format (major.minor.patch)`,
);
}
return semver.gt(
semverify(lowestImplementedVersion),
lowestTargetedVersion,
);
}
return semver.gt(
semverify(lowestImplementedVersion),
lowestTargetedVersion,
);
});
},
);
return isRequiredForEnvironments.length > 0;
};
@@ -63,7 +60,7 @@ const getBuiltInTargets = targets => {
return builtInTargets;
};
export const transformIncludesAndExcludes = opts => {
export const transformIncludesAndExcludes = (opts: Array<string>): Object => {
return opts.reduce(
(result, opt) => {
const target = opt.match(/^(es\d+|web)\./) ? "builtIns" : "plugins";
@@ -78,7 +75,7 @@ export const transformIncludesAndExcludes = opts => {
);
};
const getPlatformSpecificDefaultFor = targets => {
const getPlatformSpecificDefaultFor = (targets: Targets): ?Array<string> => {
const targetNames = Object.keys(targets);
const isAnyTarget = !targetNames.length;
const isWebTarget = targetNames.some(name => name !== "node");
@@ -106,14 +103,17 @@ const filterItems = (list, includes, excludes, targets, defaultItems) => {
return result;
};
export default function buildPreset(context, opts = {}) {
export default function buildPreset(
context: Object,
opts: Object = {},
): { plugins: Array<Plugin> } {
const {
debug,
exclude: optionsExclude,
forceAllTransforms,
include: optionsInclude,
loose,
moduleType,
modules,
spec,
targets: optionsTargets,
useBuiltIns,
@@ -162,9 +162,9 @@ export default function buildPreset(context, opts = {}) {
const plugins = [];
if (moduleType !== false && moduleTransformations[moduleType]) {
if (modules !== false && moduleTransformations[modules]) {
plugins.push([
require(`babel-plugin-${moduleTransformations[moduleType]}`),
require(`babel-plugin-${moduleTransformations[modules]}`),
{ loose },
]);
}
@@ -181,7 +181,7 @@ export default function buildPreset(context, opts = {}) {
console.log("babel-preset-env: `DEBUG` option");
console.log("\nUsing targets:");
console.log(JSON.stringify(prettifyTargets(targets), null, 2));
console.log(`\nUsing modules transform: ${moduleType}`);
console.log(`\nUsing modules transform: ${modules.toString()}`);
console.log("\nUsing plugins:");
transformations.forEach(transform => {
logPlugin(transform, targets, pluginList);

View File

@@ -1,8 +1,11 @@
//@flow
import invariant from "invariant";
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";
const validIncludesAndExcludes = new Set([
...Object.keys(pluginFeatures),
@@ -11,7 +14,10 @@ const validIncludesAndExcludes = new Set([
...defaultWebIncludes,
]);
export const validateIncludesAndExcludes = (opts = [], type) => {
export const validateIncludesAndExcludes = (
opts: Array<string> = [],
type: string,
): Array<string> => {
invariant(
Array.isArray(opts),
`Invalid Option: The '${type}' option must be an Array<String> of plugins/built-ins`,
@@ -21,29 +27,39 @@ export const validateIncludesAndExcludes = (opts = [], type) => {
invariant(
unknownOpts.length === 0,
`Invalid Option: The plugins/built-ins '${unknownOpts}' 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;
};
export const normalizePluginName = plugin =>
export const normalizePluginName = (plugin: string): string =>
plugin.replace(/^babel-plugin-/, "");
export const normalizePluginNames = plugins => plugins.map(normalizePluginName);
export const normalizePluginNames = (plugins: Array<string>): Array<string> =>
plugins.map(normalizePluginName);
export const checkDuplicateIncludeExcludes = (include = [], exclude = []) => {
const duplicates = include.filter(opt => exclude.indexOf(opt) >= 0);
export const checkDuplicateIncludeExcludes = (
include: Array<string> = [],
exclude: Array<string> = [],
): void => {
const duplicates: Array<string> = include.filter(
opt => exclude.indexOf(opt) >= 0,
);
invariant(
duplicates.length === 0,
`Invalid Option: The plugins/built-ins '${duplicates}' 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 validateBoolOption = (name, value, defaultValue) => {
export const validateBoolOption = (
name: string,
value: ?boolean,
defaultValue: boolean,
) => {
if (typeof value === "undefined") {
value = defaultValue;
}
@@ -55,16 +71,18 @@ export const validateBoolOption = (name, value, defaultValue) => {
return value;
};
export const validateLooseOption = looseOpt =>
export const validateLooseOption = (looseOpt: boolean) =>
validateBoolOption("loose", looseOpt, false);
export const validateSpecOption = specOpt =>
export const validateSpecOption = (specOpt: boolean) =>
validateBoolOption("spec", specOpt, false);
export const validateForceAllTransformsOption = forceAllTransforms =>
export const validateForceAllTransformsOption = (forceAllTransforms: boolean) =>
validateBoolOption("forceAllTransforms", forceAllTransforms, false);
export const validateModulesOption = (modulesOpt = "commonjs") => {
export const validateModulesOption = (
modulesOpt: ModuleOption = "commonjs",
) => {
invariant(
modulesOpt === false ||
Object.keys(moduleTransformations).indexOf(modulesOpt) > -1,
@@ -75,7 +93,9 @@ export const validateModulesOption = (modulesOpt = "commonjs") => {
return modulesOpt;
};
export const validateUseBuiltInsOption = (builtInsOpt = false) => {
export const validateUseBuiltInsOption = (
builtInsOpt: BuiltInsOption = false,
): BuiltInsOption => {
invariant(
builtInsOpt === "usage" || builtInsOpt === false || builtInsOpt === "entry",
`Invalid Option: The 'useBuiltIns' option must be either
@@ -87,13 +107,13 @@ export const validateUseBuiltInsOption = (builtInsOpt = false) => {
return builtInsOpt;
};
export default function normalizeOptions(opts) {
export default function normalizeOptions(opts: Options) {
if (opts.exclude) {
opts.exclude = normalizePluginNames(opts.exclude);
}
if (opts.whitelist || opts.include) {
opts.include = normalizePluginNames(opts.whitelist || opts.include);
if (opts.include) {
opts.include = normalizePluginNames(opts.include);
}
checkDuplicateIncludeExcludes(opts.include, opts.exclude);
@@ -106,7 +126,7 @@ export default function normalizeOptions(opts) {
),
include: validateIncludesAndExcludes(opts.include, "include"),
loose: validateLooseOption(opts.loose),
moduleType: validateModulesOption(opts.modules),
modules: validateModulesOption(opts.modules),
spec: validateSpecOption(opts.spec),
targets: opts.targets,
useBuiltIns: validateUseBuiltInsOption(opts.useBuiltIns),

View File

@@ -3,10 +3,7 @@
import browserslist from "browserslist";
import semver from "semver";
import { semverify } from "./utils";
export type Targets = {
[target: string]: string,
};
import type { Targets } from "./types";
const browserNameMap = {
android: "android",
@@ -25,9 +22,9 @@ const semverMin = (first: ?string, second: string): string => {
return first && semver.lt(first, second) ? first : second;
};
const getLowestVersions = browsers => {
const getLowestVersions = (browsers: Array<string>): Targets => {
return browsers.reduce(
(all, browser) => {
(all: Object, browser: string): Object => {
const [browserName, browserVersion] = browser.split(" ");
const normalizedBrowserName = browserNameMap[browserName];
@@ -80,14 +77,10 @@ const targetParserMap = {
return [target, parsed];
},
// TODO: Remove in next version.
// Only valid value for Uglify is `true`
uglify: (target, value) => [target, value === true],
};
const getTargets = (targets: Object = {}): Targets => {
let targetOpts = {};
let targetOpts: Targets = {};
// Parse browsers target via browserslist
if (isBrowsersQueryValid(targets.browsers)) {
@@ -95,8 +88,12 @@ const getTargets = (targets: Object = {}): Targets => {
}
// Parse remaining targets
type ParsedResult = {
targets: Targets,
decimalWarnings: Array<Object>,
};
const parsed = Object.keys(targets).reduce(
(results, target) => {
(results: ParsedResult, target: string): ParsedResult => {
if (target !== "browsers") {
const value = targets[target];
@@ -111,16 +108,10 @@ const getTargets = (targets: Object = {}): Targets => {
if (parsedValue) {
// Merge (lowest wins)
if (typeof parsedValue === "string") {
results.targets[parsedTarget] = semverMin(
results.targets[parsedTarget],
parsedValue,
);
} else {
// We can remove this block if/when we replace Uglify target
// with top level option
results.targets[parsedTarget] = parsedValue;
}
results.targets[parsedTarget] = semverMin(
results.targets[parsedTarget],
parsedValue,
);
}
}

View File

@@ -0,0 +1,27 @@
//@flow
// Targets
export type Target = string;
export type Targets = {
[target: string]: Target,
};
// Options
// Use explicit modules to prevent typo errors.
export type ModuleOption = false | "amd" | "commonjs" | "systemjs" | "umd";
export type BuiltInsOption = false | "entry" | "usage";
export type Options = {
debug: boolean,
exclude: Array<string>,
forceAllTransforms: boolean,
include: Array<string>,
loose: boolean,
modules: ModuleOption,
spec: boolean,
targets: Targets,
useBuiltIns: BuiltInsOption,
};
// Babel
export type Plugin = [Object, Object];

View File

@@ -1,23 +1,33 @@
//@flow
import { logEntryPolyfills } from "./debug";
function isPolyfillSource(value) {
type Plugin = {
visitor: Object,
pre: Function,
post: Function,
name: string,
};
type RequireType = "require" | "import";
function isPolyfillSource(value: string): boolean {
return value === "babel-polyfill";
}
export default function({ types: t }) {
function createImportDeclaration(polyfill) {
export default function({ types: t }: { types: Object }): Plugin {
function createImportDeclaration(polyfill: string): Object {
const declar = t.importDeclaration([], t.stringLiteral(polyfill));
declar._blockHoist = 3;
return declar;
}
function createRequireStatement(polyfill) {
function createRequireStatement(polyfill: string): Object {
return t.expressionStatement(
t.callExpression(t.identifier("require"), [t.stringLiteral(polyfill)]),
);
}
function isRequire(path) {
function isRequire(path: Object): boolean {
return t.isExpressionStatement(path.node) &&
t.isCallExpression(path.node.expression) &&
t.isIdentifier(path.node.expression.callee) &&
@@ -27,19 +37,26 @@ export default function({ types: t }) {
isPolyfillSource(path.node.expression.arguments[0].value);
}
function createImport(polyfill, requireType, core) {
function createImport(
polyfill: string,
requireType: RequireType,
core: ?boolean,
): Object {
if (core) {
polyfill = `babel-polyfill/lib/core-js/modules/${polyfill}`;
}
if (requireType === "import") {
return createImportDeclaration(polyfill);
} else {
return createRequireStatement(polyfill);
}
return createRequireStatement(polyfill);
}
function createImports(polyfills, requireType, regenerator) {
function createImports(
polyfills: Array<string>,
requireType: RequireType,
regenerator: boolean,
): Array<Object> {
const items = Array.isArray(polyfills) ? new Set(polyfills) : polyfills;
const imports = [];

View File

@@ -1,7 +1,15 @@
//@flow
import { definitions } from "./built-in-definitions";
import { logUsagePolyfills } from "./debug";
function isPolyfillSource(value) {
type Plugin = {
visitor: Object,
pre: Function,
name: string,
};
function isPolyfillSource(value: string): boolean {
return value === "babel-polyfill";
}
@@ -12,16 +20,17 @@ function warnOnInstanceMethod() {
// );
}
function has(obj, key) {
function has(obj: Object, key: string): boolean {
return Object.prototype.hasOwnProperty.call(obj, key);
}
function getObjectString(node) {
function getObjectString(node: Object): string {
if (node.type === "Identifier") {
return node.name;
} else if (node.type === "MemberExpression") {
return `${getObjectString(node.object)}.${getObjectString(node.property)}`;
}
return node.name;
}
const modulePathMap = {
@@ -33,8 +42,12 @@ const getModulePath = module => {
`babel-polyfill/lib/core-js/modules/${module}`;
};
export default function({ types: t }) {
function addImport(path, builtIn, builtIns) {
export default function({ types: t }: { types: Object }): Plugin {
function addImport(
path: Object,
builtIn: string,
builtIns: Set<string>,
): void {
if (builtIn && !builtIns.has(builtIn)) {
builtIns.add(builtIn);
@@ -49,7 +62,12 @@ export default function({ types: t }) {
}
}
function addUnsupported(path, polyfills, builtIn, builtIns) {
function addUnsupported(
path: Object,
polyfills: Set<string>,
builtIn: Array<string> | string,
builtIns: Set<string>,
): void {
if (Array.isArray(builtIn)) {
for (const i of builtIn) {
if (polyfills.has(i)) {
@@ -63,7 +81,7 @@ export default function({ types: t }) {
}
}
function isRequire(path) {
function isRequire(path: Object): boolean {
return t.isExpressionStatement(path.node) &&
t.isCallExpression(path.node.expression) &&
t.isIdentifier(path.node.expression.callee) &&

View File

@@ -1,11 +1,11 @@
// @flow
import semver from "semver";
import type { Targets } from "./targets-parser";
import type { Targets } from "./types";
// Convert version to a semver value.
// 2.5 -> 2.5.0; 1 -> 1.0.0;
export const semverify = (version: string | number) => {
export const semverify = (version: string | number): string => {
if (typeof version === "string" && semver.valid(version)) {
return version;
}
@@ -19,7 +19,7 @@ export const semverify = (version: string | number) => {
return split.join(".");
};
export const prettifyVersion = (version: string | number) => {
export const prettifyVersion = (version: string): string => {
if (typeof version !== "string") {
return version;
}
@@ -39,7 +39,7 @@ export const prettifyVersion = (version: string | number) => {
return parts.join(".");
};
export const prettifyTargets = (targets: Targets) => {
export const prettifyTargets = (targets: Targets): Object => {
return Object.keys(targets).reduce(
(results, target) => {
let value = targets[target];

View File

@@ -43,4 +43,4 @@ Using polyfills with `entry` option:
web.timers { "chrome":"55" }
web.immediate { "chrome":"55" }
web.dom.iterable { "chrome":"55" }
src/in.js -> lib/in.js
src/in.js -> lib/in.js

View File

@@ -1,12 +1,12 @@
{
"presets": [
["../../../../lib", {
"modules": false,
"targets": {
"chrome": 55
"chrome": 55,
"uglify": true
},
"useBuiltIns": "entry",
"forceAllTransforms": true
"modules": false,
"useBuiltIns": "entry"
}]
]
}

View File

@@ -83,35 +83,4 @@ describe("getTargets", () => {
);
});
});
describe("uglify", () => {
it("should work with `true`", function() {
assert.deepEqual(
getTargets({
uglify: true,
}),
{
uglify: true,
},
);
});
it("should ignore `false`", function() {
assert.deepEqual(
getTargets({
uglify: false,
}),
{},
);
});
it("should ignore `null`", function() {
assert.deepEqual(
getTargets({
uglify: null,
}),
{},
);
});
});
});

View File

@@ -2158,11 +2158,7 @@ dom-serializer@0, dom-serializer@~0.1.0:
domelementtype "~1.1.1"
entities "~1.1.1"
domelementtype@1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2"
domelementtype@~1.1.1:
domelementtype@1, domelementtype@~1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b"
@@ -2350,9 +2346,9 @@ eslint-config-babel@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/eslint-config-babel/-/eslint-config-babel-6.0.0.tgz#66feedf6ce6e04abe585cec1a65b5bcc96bed50a"
eslint-plugin-flowtype@^2.29.1:
version "2.32.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.32.1.tgz#bbee185dedf97e5f63ec975cdcddd199bd2a2501"
eslint-plugin-flowtype@^2.33.0:
version "2.33.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.33.0.tgz#b2783814ed2ddcf729953b8f65ff73c90cabee4b"
dependencies:
lodash "^4.15.0"
@@ -2563,9 +2559,9 @@ flat-cache@^1.2.1:
graceful-fs "^4.1.2"
write "^0.2.1"
flow-bin@^0.43.0:
version "0.43.1"
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.43.1.tgz#0733958b448fb8ad4b1576add7e87c31794c81bc"
flow-bin@^0.46.0:
version "0.46.0"
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.46.0.tgz#06ad7fe19dddb1042264438064a2a32fee12b872"
flow-parser@0.40.0:
version "0.40.0"