babel-plugin-polyfill-es-shims
Version:
A Babel plugin to inject imports to es-shims polyfills
373 lines (362 loc) • 14.2 kB
JavaScript
import defineProvider from '@babel/helper-define-polyfill-provider';
import polyfills from '../data/polyfills.js';
import * as _babel from '@babel/core';
const {
types: t,
template: template
} = _babel.default || _babel;
const expr = template.expression.ast;
const has = Function.call.bind(Object.hasOwnProperty);
const Globals = {};
const StaticProperties = {};
const InstanceProperties = {};
const lessThanArgs = num => (meta, path) => {
const {
node,
parent
} = path;
if (!t.isNewExpression(parent, {
callee: node
})) return false;
if (parent.arguments.length >= num) return false;
return parent.arguments.every(arg => !t.isSpreadElement(arg));
};
for (const [name, causeArgNum] of [["Error", 2], ["AggregateError", 3], ["EvalError", 2], ["RangeError", 2], ["ReferenceError", 2], ["SyntaxError", 2], ["TypeError", 2], ["URIError", 2]
// SuppressedError is not in this list because it does not have a cause
]) {
defineGlobal(name, "1.0.1", "error-cause", {
exclude: lessThanArgs(causeArgNum),
subfolder: name,
polyfillName: "Error cause"
});
}
// This needs to come after the AggregateError cause polyfill, since this
// one polyfills less features.
defineGlobal("AggregateError", "1.0.2", "es-aggregate-error");
const DATE_VERSION = "2.0.0";
// MISSING DATA: defineGlobal("Date", DATE_VERSION, "date", { subfolder: "Date" });
defineGlobal("globalThis", "1.0.0");
defineGlobal("parseInt", "2.0.0");
defineGlobal("Map", "1.0.4", "es-map");
defineGlobal("Set", "1.1.0", "es-set");
// TODO: Uncomment when stage 4
// defineGlobal("SuppressedError", "1.0.2", "suppressed-error");
// const DisposableStackPackage = ["1.1.2", "disposablestack"] as const;
// defineGlobal("AsyncDisposableStack", ...DisposableStackPackage, {
// subfolder: "AsyncDisposableStack",
// });
// defineGlobal("DisposableStack", ...DisposableStackPackage, {
// subfolder: "DisposableStack",
// });
const arrayCheck = thisObj => expr`Array.isArray(${thisObj})`;
const typeofCheck = type => thisObj => expr`typeof ${thisObj} === "${type}"`;
const instanceofCheck = Class => thisObj => expr`${thisObj} instanceof ${t.identifier(Class)}`;
const stringCheck = typeofCheck("string");
// const setCheck = instanceofCheck("Set");
const getter = {
getter: true
};
const excludeStatic = obj => ({
exclude: meta => meta.kind === "property" && meta.placement === "static" && meta.object === obj
});
const excludeObject = excludeStatic("Object");
defineStatic("Array", "from", "1.1.0");
defineStatic("Array", "of", "1.0.0");
defineInstance("Array", "at", "1.0.0", arrayCheck);
defineInstance("Array", "concat", "1.0.2", arrayCheck);
defineInstance("Array", "copyWithin", "1.0.0", arrayCheck);
defineInstance("Array", "entries", "1.0.0", arrayCheck, excludeObject);
defineInstance("Array", "every", "1.1.0", arrayCheck);
defineInstance("Array", "filter", "1.0.0", arrayCheck);
defineInstance("Array", "find", "2.1.1", arrayCheck);
defineInstance("Array", "findIndex", "2.1.0", arrayCheck);
defineInstance("Array", "findLast", "1.0.0", arrayCheck);
defineInstance("Array", "findLastIndex", "1.0.0", arrayCheck);
defineInstance("Array", "flat", "1.2.3", arrayCheck);
defineInstance("Array", "flatMap", "1.2.3", arrayCheck);
defineInstance("Array", "includes", "3.1.1", arrayCheck, {
pkg: "array-includes"
});
defineInstance("Array", "indexOf", "1.0.1", arrayCheck);
// MISSING DATA: defineInstance("Array", "join", "1.0.0", arrayCheck);
defineInstance("Array", "keys", "1.0.0", arrayCheck, excludeObject);
defineInstance("Array", "lastIndexOf", "1.0.0", arrayCheck);
defineInstance("Array", "map", "1.0.2", arrayCheck);
defineInstance("Array", "reduce", "1.0.1", arrayCheck);
defineInstance("Array", "reduceRight", "1.0.1", arrayCheck);
// MISSING DATA: defineInstance("Array", "slice", "1.0.0", arrayCheck);
defineInstance("Array", "some", "1.1.1", arrayCheck);
defineInstance("Array", "splice", "1.0.1", arrayCheck);
defineInstance("Array", "toReversed", "1.0.1", arrayCheck);
defineInstance("Array", "toSorted", "1.0.0", arrayCheck);
defineInstance("Array", "toSpliced", "1.0.0", arrayCheck);
defineInstance("Array", "unshift", "1.0.0", arrayCheck);
defineInstance("Array", "values", "1.0.0", arrayCheck, excludeObject);
defineInstance("Array", "with", "1.0.1", arrayCheck);
for (const name of ["now"
// MISSING DATA: "parse"
]) {
defineStatic("Date", name, DATE_VERSION, {
pkg: "date",
subfolder: `Date.${name}`
});
}
for (const name of [
// MISSING DATA: "getFullYear",
// MISSING DATA: "getMonth",
// MISSING DATA: "getDate",
// MISSING DATA: "getUTCDate",
// MISSING DATA: "getUTCFullYear",
// MISSING DATA: "getUTCMonth",
// MISSING DATA: "toUTCString",
// MISSING DATA: "toDateString",
// MISSING DATA: "toString",
"toISOString", "toJSON"]) {
defineInstance("Date", name, DATE_VERSION, instanceofCheck("Date"), {
pkg: "date",
subfolder: `Date.prototype.${name}`
});
}
defineInstance("Function", "name", "1.1.2", typeofCheck("function"), getter);
defineStatic("Math", "acosh", "1.0.0");
defineStatic("Math", "atanh", "1.0.0");
defineStatic("Math", "clz32", "1.0.0");
defineStatic("Math", "cbrt", "1.0.0");
defineStatic("Math", "fround", "1.0.0");
defineStatic("Math", "log1p", "1.0.1");
defineStatic("Math", "sign", "2.0.0");
defineStatic("Number", "isFinite", "1.0.0");
defineStatic("Number", "isInteger", "1.0.0");
defineStatic("Number", "isSafeInteger", "1.0.0");
defineStatic("Number", "isNaN", "1.0.0", {
pkg: "number.isnan"
});
defineStatic("Number", "parseFloat", "1.0.0");
defineStatic("Number", "parseInt", "1.0.0");
defineStatic("Object", "assign", "4.1.0");
defineStatic("Object", "defineProperties", "1.0.0");
defineStatic("Object", "entries", "1.1.1");
defineStatic("Object", "fromEntries", "2.0.2");
defineStatic("Object", "hasOwn", "1.0.0");
defineStatic("Object", "is", "1.1.2", {
pkg: "object-is"
});
defineStatic("Object", "getOwnPropertyDescriptors", "2.1.0");
defineStatic("Object", "getPrototypeOf", "1.0.1");
defineStatic("Object", "values", "1.1.1");
defineStatic("Promise", "allSettled", "1.0.2");
defineStatic("Promise", "any", "2.0.1");
defineStatic("Promise", "try", "1.0.0");
defineInstance("Promise", "finally", "1.2.1", instanceofCheck("Promise"));
defineStatic("Reflect", "apply", "1.0.0");
defineStatic("Reflect", "ownKeys", "1.0.1");
defineStatic("Reflect", "getPrototypeOf", "1.0.0");
defineInstance("RegExp", "flags", "1.3.0", instanceofCheck("RegExp"), getter);
// TODO: Uncomment when Stage 4
// defineInstance("Set", "difference", "1.0.2", setCheck);
// defineInstance("Set", "intersection", "1.0.2", setCheck);
// defineInstance("Set", "isDisjointFrom", "1.0.2", setCheck);
// defineInstance("Set", "isSubsetOf", "1.0.2", setCheck);
// defineInstance("Set", "isSupersetOf", "1.0.2", setCheck);
// defineInstance("Set", "symmetricDifference", "1.0.2", setCheck);
// defineInstance("Set", "union", "1.0.2", setCheck);
defineStatic("String", "fromCodePoint", "1.0.0");
defineStatic("String", "raw", "1.0.1");
defineInstance("String", "codePoitAt", "1.0.0", stringCheck);
defineInstance("String", "endsWith", "1.0.0", stringCheck);
defineInstance("String", "includes", "2.0.0", stringCheck);
defineInstance("String", "isWellFormed", "1.0.1", stringCheck);
defineInstance("String", "at", "1.0.0", stringCheck);
defineInstance("String", "matchAll", "4.0.2", stringCheck);
defineInstance("String", "padEnd", "1.1.1", stringCheck);
defineInstance("String", "padStart", "3.1.0", stringCheck);
defineInstance("String", "repeat", "1.0.0", stringCheck);
defineInstance("String", "replaceAll", "1.0.3", stringCheck);
defineInstance("String", "split", "1.0.1", stringCheck, excludeStatic("Symbol"));
defineInstance("String", "startsWith", "1.0.0", stringCheck);
defineInstance("String", "substr", "1.0.0", stringCheck);
defineInstance("String", "toWellFormed", "1.0.1", stringCheck);
defineInstance("String", "trim", "1.2.1", stringCheck);
defineInstance("String", "trimEnd", "1.0.0", stringCheck);
defineInstance("String", "trimLeft", "2.1.1", stringCheck);
defineInstance("String", "trimRight", "2.1.1", stringCheck);
defineInstance("String", "trimStart", "1.0.0", stringCheck);
defineInstance("Symbol", "description", "1.0.2", instanceofCheck("Symbol"), getter);
// Annex B
for (const name of ["anchor", "big", "blink", "bold", "fixed", "fontcolor", "fontsize", "italics", "link", "small", "strike", "sub", "sup"]) {
defineInstance("String", name, "1.0.0", stringCheck, {
pkg: `es-string-html-methods`,
subfolder: name
});
}
function createDescriptor(name, version, pkg = name.toLowerCase(), subfolder) {
return {
name,
version,
package: pkg,
path: subfolder ? `${pkg}/${subfolder}` : pkg
};
}
function defineGlobal(name, version, pkg, {
exclude,
subfolder,
polyfillName = name
} = {}) {
if (!has(Globals, name)) Globals[name] = [];
Globals[name].push({
...createDescriptor(polyfillName, version, pkg, subfolder),
exclude,
nameHint: name
});
}
function defineStatic(object, property, version, {
pkg,
subfolder
} = {}) {
if (!has(StaticProperties, object)) StaticProperties[object] = {};
StaticProperties[object][property] = [createDescriptor(`${object}.${property}`, version, pkg, subfolder)];
}
function defineInstance(object, property, version, thisCheck, {
getter = false,
exclude,
pkg,
subfolder
} = {}) {
if (!has(InstanceProperties, property)) InstanceProperties[property] = [];
InstanceProperties[property].push({
...createDescriptor(`${object}.prototype.${property}`, version, pkg, subfolder),
thisCheck,
exclude,
getter
});
}
var index = defineProvider(function ({
shouldInjectPolyfill,
assertDependency,
createMetaResolver,
debug,
babel: {
template,
types: t
}
}) {
const expr = template.expression.ast;
const resolvePolyfill = createMetaResolver({
global: Globals,
static: StaticProperties,
instance: InstanceProperties
});
function createDescIterator(cb, instance) {
return (meta, utils, path) => {
if (path.parentPath.isUnaryExpression({
operator: "delete"
})) return;
const resolved = resolvePolyfill(meta);
if (!resolved) return;
if (instance && resolved.kind === "instance") {
instance(meta, resolved, utils, path);
return;
}
for (const desc of resolved.desc) {
if (!(desc.exclude != null && desc.exclude(meta, path)) && shouldInjectPolyfill(desc.name)) {
cb(desc, utils, path);
// Since global and static polyfills are unambiguous, we only need to
// inject the first non-excluded one.
if (resolved.kind !== "instance") {
break;
}
}
}
};
}
function injectDefault(desc, utils) {
assertDependency(desc.package, desc.version);
debug(desc.name);
return utils.injectDefaultImport(desc.path, desc.nameHint || desc.name);
}
const seen = new WeakSet();
return {
name: "es-shims",
polyfills,
usageGlobal: createDescIterator((desc, utils) => {
if (desc.global === false) return;
assertDependency(desc.package, desc.version);
utils.injectGlobalImport(`${desc.path}/auto`);
debug(desc.name);
}),
usagePure: createDescIterator((desc, utils, path) => {
path.replaceWith(injectDefault(desc, utils));
}, (meta, resolved, utils, path) => {
if (meta.kind !== "property") return;
if (path.parentPath.isAssignmentExpression({
left: path.node
})) return;
if (seen.has(path.node)) return;
seen.add(path.node);
const isCall = path.parentPath.isCallExpression({
callee: path.node
});
const isGetter = resolved.desc[0].getter;
const matchesPolyfill = ({
name
}) => name.startsWith(meta.object);
let index = -1;
if (
// This is the actual method for sure.
meta.kind === "property" && meta.placement === "prototype" && meta.object != null && (index = resolved.desc.findIndex(matchesPolyfill)) !== -1) {
const desc = resolved.desc[index];
if (!shouldInjectPolyfill(desc.name)) return;
const id = injectDefault(desc, utils);
if (isGetter) {
path.replaceWith(expr`${id}(${path.node.object})`);
} else if (isCall) {
path.parentPath.unshiftContainer("arguments", path.node.object);
path.replaceWith(id);
} else if (path.parentPath.isMemberExpression({
object: path.node,
computed: false
}) && path.parent.property.name === "call" && path.parentPath.parentPath.isCallExpression({
callee: path.parent
})) {
path.parentPath.replaceWith(id);
} else {
path.replaceWith(expr`${id}.getPolyfill()`);
}
} else {
let tmp;
let parent;
let replacement = path.node;
const {
object
} = path.node;
for (const desc of resolved.desc) {
const {
thisCheck
} = desc;
if (!thisCheck || desc.exclude != null && desc.exclude(meta) || !shouldInjectPolyfill(desc.name)) {
continue;
}
if (!tmp) {
tmp = path.scope.generateUidIdentifierBasedOnNode(object);
path.scope.push({
id: t.cloneNode(tmp)
});
path.get("object").replaceWith(tmp);
}
const id = injectDefault(desc, utils);
replacement = t.conditionalExpression(thisCheck(t.cloneNode(tmp)), isGetter ? expr`${id}(${t.cloneNode(tmp)})` : isCall ? id : expr`${id}.getPolyfill()`, replacement);
parent = parent != null ? parent : replacement;
}
if (!parent) return;
replacement = expr`(${t.cloneNode(tmp)} = ${object}, ${replacement})`;
if (!isGetter && isCall) {
parent.alternate = expr`Function.call.bind(${parent.alternate})`;
path.parentPath.unshiftContainer("arguments", t.cloneNode(tmp));
}
path.replaceWith(replacement);
}
})
};
});
export { index as default };
//# sourceMappingURL=index.mjs.map