create add-used-built-ins option

This commit is contained in:
Henry Zhu 2017-03-31 15:34:39 -04:00
parent 83b85a3609
commit c10528254b
31 changed files with 599 additions and 17 deletions

View File

@ -213,6 +213,55 @@ This will also work for `core-js` directly (`import "core-js";`)
npm install core-js --save
```
### `addUsedBuiltIns`
`boolean`, defaults to `false`.
Adds imports for polyfills when they are used in each file. You need to have core-js as a dependency (and regeneratorRuntime if necessary).
> This option is different than `useBuiltIns` in that instead of only adding polyfills at the entry point, the plugin adds a specific import for each polyfill that is used in each file. We take advantage of the fact that the bundler will load the same polyfill only once.
```sh
npm install core-js regenerator-runtime --save
```
**In**
a.js
```js
var a = new Promise();
```
b.js
```js
var b = new Map();
```
**Out (if environment doesn't support it)**
```js
import "core-js/modules/es6.promise";
var a = new Promise();
```
```js
import "core-js/modules/es6.map";
var b = new Map();
```
**Out (if environment supports it)**
```js
var a = new Promise();
```
```js
import "core-js/modules/es6.map";
var b = new Map();
```
---
## Examples

View File

@ -0,0 +1,212 @@
import { definitions } from "./built-in-definitions";
function isPolyfillSource(value) {
return value === "babel-polyfill" || value === "core-js";
}
function warnOnInstanceMethod(details) {
console.warn(`Adding a polyfill: An instance method may have been used: ${details}`);
}
function getRuntimeModuleName(opts) {
return opts.moduleName || "babel-runtime";
}
function has(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
function getObjectString(node) {
if (node.type === "Identifier") {
return node.name;
} else if (node.type === "MemberExpression") {
return `${getObjectString(node.object)}.${getObjectString(node.property)}`;
} else {
return "";
}
}
const HELPER_BLACKLIST = ["interopRequireWildcard", "interopRequireDefault"];
export default function ({ types: t }) {
function addImport(path, builtIn) {
if (builtIn) {
const importDec = t.importDeclaration([],
t.stringLiteral(builtIn)
);
importDec._blockHoist = 3;
path.unshiftContainer("body", importDec);
}
}
function isRequire(path) {
return t.isExpressionStatement(path.node) &&
t.isCallExpression(path.node.expression) &&
t.isIdentifier(path.node.expression.callee) &&
path.node.expression.callee.name === "require" &&
path.node.expression.arguments.length === 1 &&
t.isStringLiteral(path.node.expression.arguments[0]) &&
isPolyfillSource(path.node.expression.arguments[0].value);
}
const addAndRemovePolyfillImports = {
ImportDeclaration(path, state) {
if (path.node.specifiers.length === 0 && isPolyfillSource(path.node.source.value)) {
state.opts.addUsedBuiltIns && console.warn(`
Adding "import 'babel-polyfill' (or 'core-js')" isn't necessary with the addUsedBuiltIns option anymore.
Please remove the call.
`);
path.remove();
}
},
Program: {
enter(path, state) {
if (!state.opts.polyfills) {
throw path.buildCodeFrameError(`
There was an issue in "babel-preset-env" such that
the "polyfills" option was not correctly passed
to the "transform-polyfill-require" plugin
`);
}
path.get("body").forEach((bodyPath) => {
if (isRequire(bodyPath)) {
state.opts.addUsedBuiltIns && console.warn(`
Adding "require('babel-polyfill') (or 'core-js')" isn't necessary with the addUsedBuiltIns option anymore.
Please remove the call.
`);
path.remove();
}
});
},
// add polyfills
exit(path, state) {
for (const builtIn of Array.from(this.builtIns.keys()).reverse()) {
if (Array.isArray(builtIn)) {
for (const i of builtIn) {
console.warn(i);
if (state.opts.polyfills.indexOf(i) !== -1) {
addImport(path, `core-js/modules/${i}`);
}
}
} else {
console.warn(builtIn);
if (state.opts.polyfills.indexOf(builtIn) !== -1) {
addImport(path, `core-js/modules/${builtIn}`);
}
}
}
if (state.opts.regenerator && this.usesRegenerator) {
addImport(path, "regenerator-runtime/runtime");
}
}
},
// Symbol() -> _core.Symbol();
// new Promise -> new _core.Promise
ReferencedIdentifier(path) {
const { node, parent, scope } = path;
if (t.isMemberExpression(parent)) return;
if (!has(definitions.builtins, node.name)) return;
if (scope.getBindingIdentifier(node.name)) return;
this.builtIns.add(definitions.builtins[node.name]);
},
// Array.from -> _core.Array.from
MemberExpression: {
enter(path) {
if (!path.isReferenced()) return;
const { node } = path;
const obj = node.object;
const prop = node.property;
if (!t.isReferenced(obj, node)) return;
// doesn't reference the global
if (path.scope.getBindingIdentifier(obj.name)) return;
if (has(definitions.staticMethods, obj.name)) {
const staticMethods = definitions.staticMethods[obj.name];
if (has(staticMethods, prop.name)) {
this.builtIns.add(`${staticMethods[prop.name]}`);
}
}
if (!node.computed && t.isIdentifier(prop) && has(definitions.instanceMethods, prop.name)) {
warnOnInstanceMethod(getObjectString(node));
this.builtIns.add(definitions.instanceMethods[prop.name]);
} else if (node.computed && t.isStringLiteral(prop) && has(definitions.instanceMethods, prop.value)) {
warnOnInstanceMethod(`${obj.name}['${prop.value}']`);
this.builtIns.add(definitions.instanceMethods[prop.value]);
}
},
// Symbol.match
exit(path) {
if (!path.isReferenced()) return;
const { node } = path;
const obj = node.object;
if (!has(definitions.builtins, obj.name)) return;
if (path.scope.getBindingIdentifier(obj.name)) return;
this.builtIns.add(definitions.builtins[obj.name]);
}
},
// var { repeat, startsWith } = String
VariableDeclarator(path) {
if (!path.isReferenced()) return;
const { node } = path;
const obj = node.init;
if (!t.isObjectPattern(node.id)) return;
const props = node.id.properties;
if (!t.isReferenced(obj, node)) return;
// doesn't reference the global
if (path.scope.getBindingIdentifier(obj.name)) return;
for (let prop of props) {
prop = prop.key;
if (!node.computed && t.isIdentifier(prop) && has(definitions.instanceMethods, prop.name)) {
warnOnInstanceMethod(`${path.parentPath.node.kind} { ${prop.name} } = ${obj.name}`);
this.builtIns.add(definitions.instanceMethods[prop.name]);
}
}
},
Function(path) {
if (path.node.generator || path.node.async) {
this.usesRegenerator = true;
}
}
};
return {
name: "add-used-built-ins",
pre(file) {
const moduleName = getRuntimeModuleName(this.opts);
if (this.opts.helpers !== false) {
const baseHelpersDir = this.opts.useBuiltIns ? "helpers/builtin" : "helpers";
const helpersDir = this.opts.useESModules ? `${baseHelpersDir}/es6` : baseHelpersDir;
file.set("helperGenerator", function (name) {
if (HELPER_BLACKLIST.indexOf(name) < 0) {
return file.addImport(`${moduleName}/${helpersDir}/${name}`, "default", name);
}
});
}
this.builtIns = new Set();
this.usesRegenerator = false;
},
visitor: addAndRemovePolyfillImports,
};
}

View File

@ -0,0 +1,108 @@
export const definitions = {
builtins: {
DataView: "es6.typed.data-view",
Int8Array: "es6.typed.int8-array",
Uint8Array: "es6.typed.uint8-array",
Uint8ClampedArray: "es6.typed.uint8-clamped-array",
Int16Array: "es6.typed.int16-array",
Uint16Array: "es6.typed.uint16-array",
Int32Array: "es6.typed.int32-array",
Uint32Array: "es6.typed.uint32-array",
Float32Array: "es6.typed.float32-array",
Float64Array: "es6.typed.float64-array",
Map: "es6.map",
Set: "es6.set",
WeakMap: "es6.weak-map",
WeakSet: "es6.weak-set",
Promise: "es6.promise",
Symbol: "es6.symbol",
},
instanceMethods: {
name: ["es6.function.name"],
fromCodePoint: ["es6.string.from-code-point"],
codePointAt: ["es6.string.code-point-at"],
repeat: ["es6.string.repeat"],
startsWith: ["es6.string.starts-with"],
endsWith: ["es6.string.ends-with"],
includes: ["es6.string.includes", "es7.array.includes"],
flags: ["es6.regexp.flags"],
match: ["es6.regexp.match"],
replace: ["es6.regexp.replace"],
split: ["es6.regexp.split"],
search: ["es6.regexp.search"],
copyWithin: ["es6.array.copy-within"],
find: ["es6.array.find"],
findIndex: ["es6.array.find-index"],
fill: ["es6.array.fill"],
padStart: ["es7.string.pad-start"],
padEnd: ["es7.string.pad-end"],
},
staticMethods: {
Array: {
from: "es6.array.from",
of: "es6.array.of",
},
Object: {
assign: "es6.object.assign",
is: "es6.object.is",
getOwnPropertySymbols: "es6.object.get-own-property-symbols",
setPrototypeOf: "es6.object.set-prototype-of",
values: "es7.object.values",
entries: "es7.object.entries",
getOwnPropertyDescriptors: "es7.object.get-own-property-descriptors",
},
Math: {
acosh: "es6.math.acosh",
asinh: "es6.math.asinh",
atanh: "es6.math.atanh",
cbrt: "es6.math.cbrt",
clz32: "es6.math.clz32",
cosh: "es6.math.cosh",
expm1: "es6.math.expm1",
fround: "es6.math.fround",
hypot: "es6.math.hypot",
imul: "es6.math.imul",
log1p: "es6.math.log1p",
log10: "es6.math.log10",
log2: "es6.math.log2",
sign: "es6.math.sign",
sinh: "es6.math.sinh",
tanh: "es6.math.tanh",
trunc: "es6.math.trunc",
},
String: {
raw: "es6.string.raw",
},
Number: {
isFinite: "es6.number.is-finite",
isInteger: "es6.number.is-integer",
isSafeInteger: "es6.number.is-safe-integer",
isNaN: "es6.number.is-nan",
EPSILON: "es6.number.epsilon",
MIN_SAFE_INTEGER: "es6.number.min-safe-integer",
MAX_SAFE_INTEGER: "es6.number.max-safe-integer",
},
Reflect: {
apply: "es6.reflect.apply",
construct: "es6.reflect.construct",
defineProperty: "es6.reflect.define-property",
deleteProperty: "es6.reflect.delete-property",
get: "es6.reflect.get",
getOwnPropertyDescriptor: "es6.reflect.get-own-property-descriptor",
getPrototypeOf: "es6.reflect.get-prototype-of",
has: "es6.reflect.has",
isExtensible: "es6.reflect.is-extensible",
ownKeys: "es6.reflect.own-keys",
preventExtensions: "es6.reflect.prevent-extensions",
set: "es6.reflect.set",
setPrototypeOf: "es6.reflect.set-prototype-of",
},
},
};

View File

@ -6,6 +6,7 @@ import normalizeOptions from "./normalize-options.js";
import pluginList from "../data/plugins.json";
import transformPolyfillRequirePlugin
from "./transform-polyfill-require-plugin";
import addUsedBuiltInsPlugin from "./add-used-built-ins-plugin";
/**
* Determine if a transformation is required
@ -177,7 +178,7 @@ function getPlatformSpecificDefaultFor(targets) {
export default function buildPreset(context, opts = {}) {
const validatedOptions = normalizeOptions(opts);
const { debug, loose, moduleType, useBuiltIns } = validatedOptions;
const { debug, loose, moduleType, useBuiltIns, addUsedBuiltIns } = validatedOptions;
const targets = getTargets(validatedOptions.targets);
const include = transformIncludesAndExcludes(validatedOptions.include);
@ -195,7 +196,7 @@ export default function buildPreset(context, opts = {}) {
let polyfills;
let polyfillTargets;
if (useBuiltIns) {
if (useBuiltIns || addUsedBuiltIns) {
polyfillTargets = getBuiltInTargets(targets);
const filterBuiltIns = filterItem.bind(
null,
@ -219,7 +220,7 @@ export default function buildPreset(context, opts = {}) {
transformations.forEach(transform => {
logPlugin(transform, targets, pluginList);
});
if (useBuiltIns && polyfills.length) {
if ((useBuiltIns || addUsedBuiltIns) && polyfills.length) {
console.log("\nUsing polyfills:");
polyfills.forEach(polyfill => {
logPlugin(polyfill, polyfillTargets, builtInsList);
@ -242,8 +243,11 @@ export default function buildPreset(context, opts = {}) {
]),
);
useBuiltIns &&
if (useBuiltIns) {
plugins.push([transformPolyfillRequirePlugin, { polyfills, regenerator }]);
} else if (addUsedBuiltIns) {
plugins.push([addUsedBuiltInsPlugin, { polyfills, regenerator }]);
}
return {
plugins,

View File

@ -77,5 +77,6 @@ export default function normalizeOptions(opts) {
moduleType: validateModulesOption(opts.modules),
targets: opts.targets,
useBuiltIns: opts.useBuiltIns,
addUsedBuiltIns: opts.addUsedBuiltIns,
};
}

View File

@ -0,0 +1,26 @@
Array.from; // static method
Map; // built-in
new Promise(); // new builtin
Symbol.match; // as member expression
_arr[Symbol.iterator](); // Symbol.iterator
// no import
Array.asdf;
Array2.from;
Map2;
new Promise2();
Symbol.asdf;
Symbol2.match;
_arr9[Symbol2.iterator]();
_arr9[Symbol.iterator2]();
G.assign; // static method
function H(WeakMap) {
var blah = new WeakMap();
} // shadowed
// not covered by this plugin
var asdf = 'copyWithin';
i[asdf]; // computed with identifier
j[`copyWithin`]; // computed with template
var { [asdf]: _a } = k; // computed

View File

@ -0,0 +1,26 @@
Array.from; // static method
Map; // built-in
new Promise(); // new builtin
Symbol.match; // as member expression
_arr[Symbol.iterator](); // Symbol.iterator
// no import
Array.asdf;
Array2.from;
Map2;
new Promise2();
Symbol.asdf;
Symbol2.match;
_arr9[Symbol2.iterator]();
_arr9[Symbol.iterator2]();
G.assign; // static method
function H(WeakMap) {
var blah = new WeakMap();
} // shadowed
// not covered by this plugin
var asdf = 'copyWithin';
i[asdf]; // computed with identifier
j[`copyWithin`]; // computed with template
var { [asdf]: _a } = k; // computed

View File

@ -0,0 +1,11 @@
{
"presets": [
["../../../../lib", {
"addUsedBuiltIns": true,
"modules": false,
"targets": {
"chrome": 58
}
}]
]
}

View File

@ -0,0 +1,24 @@
Array.from; // static method
Map; // built-in
new Promise(); // new builtin
Symbol.match; // as member expression
_arr[Symbol.iterator](); // Symbol.iterator
// no import
Array.asdf;
Array2.from;
Map2;
new Promise2();
Symbol.asdf;
Symbol2.match;
_arr9[Symbol2.iterator]();
_arr9[Symbol.iterator2]();
G.assign; // static method
function H(WeakMap) { var blah = new WeakMap(); } // shadowed
// not covered by this plugin
var asdf = 'copyWithin';
i[asdf] // computed with identifier
j[`copyWithin`] // computed with template
var { [asdf]: _a } = k; // computed

View File

@ -0,0 +1,32 @@
import 'core-js/modules/es6.array.from';
import 'core-js/modules/es6.map';
import 'core-js/modules/es6.promise';
import 'core-js/modules/es6.regexp.match';
import 'core-js/modules/es6.symbol';
Array.from; // static method
Map; // built-in
new Promise(); // new builtin
Symbol.match; // as member expression
_arr[Symbol.iterator](); // Symbol.iterator
// no import
Array.asdf;
Array2.from;
Map2;
new Promise2();
Symbol.asdf;
Symbol2.match;
_arr9[Symbol2.iterator]();
_arr9[Symbol.iterator2]();
G.assign; // static method
function H(WeakMap) {
var blah = new WeakMap();
} // shadowed
// not covered by this plugin
var asdf = 'copyWithin';
i[asdf]; // computed with identifier
j['copyWithin']; // computed with template
var _k = k,
_a = _k[asdf]; // computed

View File

@ -0,0 +1,9 @@
{
"presets": [
["../../../../lib", {
"addUsedBuiltIns": true,
"modules": false,
"debug": true
}]
]
}

View File

@ -0,0 +1,8 @@
{
"presets": [
["../../../../lib", {
"addUsedBuiltIns": true,
"modules": false
}]
]
}

View File

@ -1,11 +1,11 @@
{
"presets": [
["../../../../lib", {
"addUsedBuiltIns": true,
"modules": false,
"targets": {
"chrome": 55
},
"modules": false,
"useBuiltIns": true
}
}]
]
}

View File

@ -0,0 +1,20 @@
import "regenerator-runtime/runtime";
import _asyncToGenerator from "babel-runtime/helpers/asyncToGenerator";
var a = function () {
var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
case "end":
return _context.stop();
}
}
}, _callee, this);
}));
return function a() {
return _ref.apply(this, arguments);
};
}();

View File

@ -0,0 +1,8 @@
{
"presets": [
["../../../../lib", {
"addUsedBuiltIns": true,
"modules": false
}]
]
}

View File

@ -0,0 +1,11 @@
{
"presets": [
["../../../../lib", {
"addUsedBuiltIns": true,
"modules": false,
"targets": {
"node": 6
}
}]
]
}

View File

@ -0,0 +1,15 @@
import "regenerator-runtime/runtime";
var _marked = [a].map(regeneratorRuntime.mark);
function a() {
return regeneratorRuntime.wrap(function a$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
case "end":
return _context.stop();
}
}
}, _marked[0], this);
}

View File

@ -0,0 +1,8 @@
{
"presets": [
["../../../../lib", {
"addUsedBuiltIns": true,
"modules": false
}]
]
}

View File

@ -0,0 +1,4 @@
"use strict";
import "core-js/modules/es6.promise";
Promise;

View File

@ -0,0 +1,7 @@
{
"presets": [
["../../../../lib", {
"addUsedBuiltIns": true
}]
]
}

View File

@ -1,3 +0,0 @@
import "babel-polyfill";
import "babel-polyfill";
1 ** 2;

View File

@ -1,7 +0,0 @@
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";
1 ** 2;