// @flow import corejs3Polyfills from "core-js-compat/data"; import corejs3ShippedProposalsList from "./shipped-proposals"; import getModulesListForTargetVersion from "core-js-compat/get-modules-list-for-target-version"; import filterItems from "../../filter-items"; import { BuiltIns, StaticProperties, InstanceProperties, CommonIterators, CommonInstanceDependencies, PromiseDependencies, PossibleGlobalObjects, } from "./built-in-definitions"; import { createImport, getType, has, intersection, isPolyfillSource, getImportSource, getRequireSource, } from "../../utils"; import { logUsagePolyfills } from "../../debug"; import type { InternalPluginOptions } from "../../types"; import type { NodePath } from "@babel/traverse"; const NO_DIRECT_POLYFILL_IMPORT = ` When setting \`useBuiltIns: 'usage'\`, polyfills are automatically imported when needed. Please remove the direct import of \`core-js\` or use \`useBuiltIns: 'entry'\` instead.`; const corejs3PolyfillsWithoutProposals = Object.keys(corejs3Polyfills) .filter(name => !name.startsWith("esnext.")) .reduce((memo, key) => { memo[key] = corejs3Polyfills[key]; return memo; }, {}); const corejs3PolyfillsWithShippedProposals = corejs3ShippedProposalsList.reduce( (memo, key) => { memo[key] = corejs3Polyfills[key]; return memo; }, { ...corejs3PolyfillsWithoutProposals }, ); export default function( _: any, { corejs, include, exclude, polyfillTargets, proposals, shippedProposals, debug, }: InternalPluginOptions, ) { const polyfills = filterItems( proposals ? corejs3Polyfills : shippedProposals ? corejs3PolyfillsWithShippedProposals : corejs3PolyfillsWithoutProposals, include, exclude, polyfillTargets, null, ); const available = new Set(getModulesListForTargetVersion(corejs.version)); function resolveKey(path, computed) { const { node, parent, scope } = path; if (path.isStringLiteral()) return node.value; const { name } = node; const isIdentifier = path.isIdentifier(); if (isIdentifier && !(computed || parent.computed)) return name; if (!isIdentifier || scope.getBindingIdentifier(name)) { const { value } = path.evaluate(); if (typeof value === "string") return value; } } function resolveSource(path) { const { node, scope } = path; let builtIn, instanceType; if (node) { builtIn = node.name; if (!path.isIdentifier() || scope.getBindingIdentifier(builtIn)) { const { deopt, value } = path.evaluate(); if (value !== undefined) { instanceType = getType(value); } else if (deopt && deopt.isIdentifier()) { builtIn = deopt.node.name; } } } return { builtIn, instanceType }; } const addAndRemovePolyfillImports = { // import 'core-js' ImportDeclaration(path: NodePath) { if (isPolyfillSource(getImportSource(path))) { console.warn(NO_DIRECT_POLYFILL_IMPORT); path.remove(); } }, // require('core-js') Program(path: NodePath) { path.get("body").forEach(bodyPath => { if (isPolyfillSource(getRequireSource(bodyPath))) { console.warn(NO_DIRECT_POLYFILL_IMPORT); bodyPath.remove(); } }); }, // import('something').then(...) Import() { this.addUnsupported(PromiseDependencies); }, Function({ node }: NodePath) { // (async function () { }).finally(...) if (node.async) { this.addUnsupported(PromiseDependencies); } }, // for-of, [a, b] = c "ForOfStatement|ArrayPattern"() { this.addUnsupported(CommonIterators); }, // [...spread] SpreadElement({ parentPath }: NodePath) { if (!parentPath.isObjectExpression()) { this.addUnsupported(CommonIterators); } }, // yield* YieldExpression({ node }: NodePath) { if (node.delegate) { this.addUnsupported(CommonIterators); } }, // Symbol(), new Promise ReferencedIdentifier({ node: { name }, scope }: NodePath) { if (scope.getBindingIdentifier(name)) return; this.addBuiltInDependencies(name); }, MemberExpression(path: NodePath) { const source = resolveSource(path.get("object")); const key = resolveKey(path.get("property")); // Object.entries // [1, 2, 3].entries this.addPropertyDependencies(source, key); }, ObjectPattern(path: NodePath) { const { parentPath, parent, key } = path; let source; // const { keys, values } = Object if (parentPath.isVariableDeclarator()) { source = resolveSource(parentPath.get("init")); // ({ keys, values } = Object) } else if (parentPath.isAssignmentExpression()) { source = resolveSource(parentPath.get("right")); // !function ({ keys, values }) {...} (Object) // resolution does not work after properties transform :-( } else if (parentPath.isFunctionExpression()) { const grand = parentPath.parentPath; if (grand.isCallExpression() || grand.isNewExpression()) { if (grand.node.callee === parent) { source = resolveSource(grand.get("arguments")[key]); } } } for (const property of path.get("properties")) { if (property.isObjectProperty()) { const key = resolveKey(property.get("key")); // const { keys, values } = Object // const { keys, values } = [1, 2, 3] this.addPropertyDependencies(source, key); } } }, BinaryExpression(path: NodePath) { if (path.node.operator !== "in") return; const source = resolveSource(path.get("right")); const key = resolveKey(path.get("left"), true); // 'entries' in Object // 'entries' in [1, 2, 3] this.addPropertyDependencies(source, key); }, }; return { name: "corejs3-usage", pre() { this.polyfillsSet = new Set(); this.addUnsupported = function(builtIn) { const modules = Array.isArray(builtIn) ? builtIn : [builtIn]; for (const module of modules) { this.polyfillsSet.add(module); } }; this.addBuiltInDependencies = function(builtIn) { if (has(BuiltIns, builtIn)) { const BuiltInDependencies = BuiltIns[builtIn]; this.addUnsupported(BuiltInDependencies); } }; this.addPropertyDependencies = function(source = {}, key) { const { builtIn, instanceType } = source; if (PossibleGlobalObjects.has(builtIn)) { this.addBuiltInDependencies(key); } else if (has(StaticProperties, builtIn)) { const BuiltInProperties = StaticProperties[builtIn]; if (has(BuiltInProperties, key)) { const StaticPropertyDependencies = BuiltInProperties[key]; return this.addUnsupported(StaticPropertyDependencies); } } if (!has(InstanceProperties, key)) return; let InstancePropertyDependencies = InstanceProperties[key]; if (instanceType) { InstancePropertyDependencies = InstancePropertyDependencies.filter( m => m.includes(instanceType) || CommonInstanceDependencies.has(m), ); } this.addUnsupported(InstancePropertyDependencies); }; }, post({ path }: { path: NodePath }) { const filtered = intersection(polyfills, this.polyfillsSet, available); const reversed = Array.from(filtered).reverse(); for (const module of reversed) { createImport(path, module); } if (debug) { logUsagePolyfills( filtered, this.file.opts.filename, polyfillTargets, corejs3Polyfills, ); } }, visitor: addAndRemovePolyfillImports, }; }