diff --git a/packages/babel-plugin-transform-runtime/src/index.ts b/packages/babel-plugin-transform-runtime/src/index.ts index 89a524a787d3..d1cf63bdd17d 100644 --- a/packages/babel-plugin-transform-runtime/src/index.ts +++ b/packages/babel-plugin-transform-runtime/src/index.ts @@ -1,22 +1,10 @@ import { declare } from "@babel/helper-plugin-utils"; import { addDefault, isModule } from "@babel/helper-module-imports"; -import { types as t } from "@babel/core"; +import { types as t, type CallerMetadata } from "@babel/core"; import { hasMinVersion } from "./helpers"; import getRuntimePath, { resolveFSPath } from "./get-runtime-path"; -import type { PluginAPI, PluginObject, CallerMetadata } from "@babel/core"; - -import _pluginCorejs2 from "babel-plugin-polyfill-corejs2"; -import _pluginCorejs3 from "babel-plugin-polyfill-corejs3"; -import _pluginRegenerator from "babel-plugin-polyfill-regenerator"; -const pluginCorejs2 = (_pluginCorejs2.default || - _pluginCorejs2) as typeof _pluginCorejs2.default; -const pluginCorejs3 = (_pluginCorejs3.default || - _pluginCorejs3) as typeof _pluginCorejs3.default; -const pluginRegenerator = (_pluginRegenerator.default || - _pluginRegenerator) as typeof _pluginRegenerator.default; - -const pluginsCompat = "#__secret_key__@babel/runtime__compatibility"; +import { createBasePolyfillsPlugin } from "./polyfills"; function supportsStaticESM(caller: CallerMetadata | undefined) { // @ts-expect-error TS does not narrow down optional chaining @@ -32,79 +20,16 @@ export interface Options { version?: string; } -interface CoreJS2PluginOptions { - absoluteImports: string | false; - method: "usage-pure"; - [pluginsCompat]: { - runtimeVersion: string; - useBabelRuntime: string | false; - ext: string; - }; -} - -interface RegeneratorPluginOptions { - absoluteImports: string | false; - method: "usage-pure"; - [pluginsCompat]: { - useBabelRuntime: string | false; - }; -} - -interface CoreJS3PluginOptions { - absoluteImports: string | false; - method: "usage-pure"; - proposals: boolean; - version: number; - [pluginsCompat]: { - useBabelRuntime: string | false; - ext: string; - }; -} - export default declare((api, options: Options, dirname) => { api.assertVersion(7); const { - corejs, helpers: useRuntimeHelpers = true, - regenerator: useRuntimeRegenerator = true, useESModules = false, version: runtimeVersion = "7.0.0-beta.0", absoluteRuntime = false, } = options; - let proposals = false; - let rawVersion; - - if (typeof corejs === "object" && corejs !== null) { - rawVersion = corejs.version; - proposals = Boolean(corejs.proposals); - } else { - rawVersion = corejs; - } - - const corejsVersion = rawVersion ? Number(rawVersion) : false; - - if (![false, 2, 3].includes(corejsVersion)) { - throw new Error( - `The \`core-js\` version must be false, 2 or 3, but got ${JSON.stringify( - rawVersion, - )}.`, - ); - } - - if (proposals && (!corejsVersion || corejsVersion < 3)) { - throw new Error( - "The 'proposals' option is only supported when using 'corejs: 3'", - ); - } - - if (typeof useRuntimeRegenerator !== "boolean") { - throw new Error( - "The 'regenerator' option must be undefined, or a boolean.", - ); - } - if (typeof useRuntimeHelpers !== "boolean") { throw new Error("The 'helpers' option must be undefined, or a boolean."); } @@ -183,101 +108,29 @@ export default declare((api, options: Options, dirname) => { const esModules = useESModules === "auto" ? api.caller(supportsStaticESM) : useESModules; - const injectCoreJS2 = corejsVersion === 2; - const injectCoreJS3 = corejsVersion === 3; - - const moduleName = injectCoreJS3 - ? "@babel/runtime-corejs3" - : injectCoreJS2 - ? "@babel/runtime-corejs2" - : "@babel/runtime"; - const HEADER_HELPERS = ["interopRequireWildcard", "interopRequireDefault"]; - const modulePath = getRuntimePath(moduleName, dirname, absoluteRuntime); - - function createCorejsPlugin( - plugin: ( - api: PluginAPI, - options: Options, - filename: string, - ) => PluginObject, - options: Options, - regeneratorPlugin: ( - api: PluginAPI, - options: RegeneratorPluginOptions, - filename: string, - ) => PluginObject, - ): (api: PluginAPI, options: {}, filename: string) => PluginObject { - return (api: PluginAPI, _: {}, filename: string) => { - return { - ...plugin(api, options, filename), - inherits: regeneratorPlugin, - }; - }; - } - - // TODO: Remove this in Babel 8 - function createRegeneratorPlugin( - options: RegeneratorPluginOptions, - ): ( - api: PluginAPI, - options: RegeneratorPluginOptions, - filename: string, - ) => PluginObject { - if (!useRuntimeRegenerator) return undefined; - return (api, _, filename) => { - return pluginRegenerator(api, options, filename); - }; - } - return { name: "transform-runtime", - inherits: injectCoreJS2 - ? createCorejsPlugin( - pluginCorejs2, - { - method: "usage-pure", - absoluteImports: absoluteRuntime ? modulePath : false, - [pluginsCompat]: { - runtimeVersion, - useBabelRuntime: modulePath, - ext: "", - }, - }, - createRegeneratorPlugin({ - method: "usage-pure", - absoluteImports: absoluteRuntime ? modulePath : false, - [pluginsCompat]: { useBabelRuntime: modulePath }, - }), - ) - : injectCoreJS3 - ? createCorejsPlugin( - pluginCorejs3, - { - method: "usage-pure", - version: 3, - proposals, - absoluteImports: absoluteRuntime ? modulePath : false, - [pluginsCompat]: { useBabelRuntime: modulePath, ext: "" }, - }, - createRegeneratorPlugin({ - method: "usage-pure", - absoluteImports: absoluteRuntime ? modulePath : false, - [pluginsCompat]: { useBabelRuntime: modulePath }, - }), - ) - : createRegeneratorPlugin({ - method: "usage-pure", - absoluteImports: absoluteRuntime ? modulePath : false, - [pluginsCompat]: { useBabelRuntime: modulePath }, - }), + inherits: createBasePolyfillsPlugin( + options, + runtimeVersion, + absoluteRuntime, + ), pre(file) { if (!useRuntimeHelpers) return; + let modulePath: string; + file.set("helperGenerator", (name: string) => { + modulePath ??= getRuntimePath( + file.get("runtimeHelpersModuleName") ?? "@babel/runtime", + dirname, + absoluteRuntime, + ); + // If the helper didn't exist yet at the version given, we bail // out and let Babel either insert it directly, or throw an error // so that plugins can handle that case properly. @@ -287,7 +140,7 @@ export default declare((api, options: Options, dirname) => { // For regeneratorRuntime, we can fallback to the old behavior of // relying on the regeneratorRuntime global. If the 'regenerator' // option is not disabled, babel-plugin-polyfill-regenerator will - // then replace it with a @babel/helpers/regeneratorRuntime import. + // then replace it with a @babel/helpers/regenerator import. // // We must wrap it in a function, because built-in Babel helpers // are functions. diff --git a/packages/babel-plugin-transform-runtime/src/polyfills.ts b/packages/babel-plugin-transform-runtime/src/polyfills.ts new file mode 100644 index 000000000000..d53a3e9df1bd --- /dev/null +++ b/packages/babel-plugin-transform-runtime/src/polyfills.ts @@ -0,0 +1,138 @@ +// TODO(Babel 8): Remove at least support for babel-plugin-polyfill-regenerator, +// which isn't needed anymore, and babel-plugin-polyfill-corejs2, since core-js +// 2 isn't maintained anymore. +// Consider also removing babel-plugin-polyfill-corejs3 from here, and ask users +// to explicitly enable it in their Babel configuration files. + +import type { PluginAPI, PluginObject } from "@babel/core"; +import _pluginCorejs2 from "babel-plugin-polyfill-corejs2"; +import _pluginCorejs3 from "babel-plugin-polyfill-corejs3"; +import _pluginRegenerator from "babel-plugin-polyfill-regenerator"; +const pluginCorejs2 = (_pluginCorejs2.default || + _pluginCorejs2) as typeof _pluginCorejs2.default; +const pluginCorejs3 = (_pluginCorejs3.default || + _pluginCorejs3) as typeof _pluginCorejs3.default; +const pluginRegenerator = (_pluginRegenerator.default || + _pluginRegenerator) as typeof _pluginRegenerator.default; + +import type { Options } from "./index"; + +const pluginsCompat = "#__secret_key__@babel/runtime__compatibility"; + +interface CoreJS2PluginOptions { + absoluteImports: boolean; + method: "usage-pure"; + [pluginsCompat]: { + runtimeVersion: string; + useBabelRuntime: boolean; + ext: string; + }; +} + +interface RegeneratorPluginOptions { + absoluteImports: boolean; + method: "usage-pure"; + [pluginsCompat]: { + useBabelRuntime: boolean; + }; +} + +interface CoreJS3PluginOptions { + absoluteImports: boolean; + method: "usage-pure"; + proposals: boolean; + version: number; + [pluginsCompat]: { + useBabelRuntime: boolean; + ext: string; + }; +} + +function createCorejsPlugin( + plugin: (api: PluginAPI, options: Options, filename: string) => PluginObject, + options: Options, + regeneratorPlugin: ( + api: PluginAPI, + options: RegeneratorPluginOptions, + filename: string, + ) => PluginObject, +): (api: PluginAPI, options: {}, filename: string) => PluginObject { + return (api: PluginAPI, _: {}, filename: string) => { + return { + ...plugin(api, options, filename), + inherits: regeneratorPlugin, + }; + }; +} + +function createRegeneratorPlugin( + options: RegeneratorPluginOptions, + useRuntimeRegenerator: boolean, +): ( + api: PluginAPI, + options: RegeneratorPluginOptions, + filename: string, +) => PluginObject { + if (!useRuntimeRegenerator) return undefined; + return (api, _, filename) => { + return pluginRegenerator(api, options, filename); + }; +} + +export function createBasePolyfillsPlugin( + { corejs, regenerator: useRuntimeRegenerator = true }: Options, + runtimeVersion: string, + absoluteImports: boolean, +) { + let proposals = false; + let rawVersion; + + if (typeof corejs === "object" && corejs !== null) { + rawVersion = corejs.version; + proposals = Boolean(corejs.proposals); + } else { + rawVersion = corejs; + } + + const corejsVersion = rawVersion ? Number(rawVersion) : false; + + if (![false, 2, 3].includes(corejsVersion)) { + throw new Error( + `The \`core-js\` version must be false, 2 or 3, but got ${JSON.stringify( + rawVersion, + )}.`, + ); + } + + if (proposals && (!corejsVersion || corejsVersion < 3)) { + throw new Error( + "The 'proposals' option is only supported when using 'corejs: 3'", + ); + } + + if (typeof useRuntimeRegenerator !== "boolean") { + throw new Error( + "The 'regenerator' option must be undefined, or a boolean.", + ); + } + + const polyfillOpts = { + method: "usage-pure", + absoluteImports, + [pluginsCompat]: { useBabelRuntime: true, runtimeVersion, ext: "" }, + } as const; + + return corejsVersion === 2 + ? createCorejsPlugin( + pluginCorejs2, + polyfillOpts, + createRegeneratorPlugin(polyfillOpts, useRuntimeRegenerator), + ) + : corejsVersion === 3 + ? createCorejsPlugin( + pluginCorejs3, + { version: 3, proposals, ...polyfillOpts }, + createRegeneratorPlugin(polyfillOpts, useRuntimeRegenerator), + ) + : createRegeneratorPlugin(polyfillOpts, useRuntimeRegenerator); +}