From eb622755bee2dca62d2f1f792a4e918b26c58529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Tue, 25 Apr 2023 01:59:27 +0200 Subject: [PATCH] Use synchronous `import.meta.resolve` (#15575) * Use synchronous import.meta.resolve * Use `import-meta-resolve@3.0.0` * Fix build error * Fix old node * Fix --- Gulpfile.mjs | 9 ++- babel.config.js | 7 +- package.json | 2 +- .../src/config/files/import-meta-resolve.ts | 65 ++++++---------- packages/babel-core/src/config/files/index.ts | 13 ++-- .../babel-core/src/config/files/plugins.ts | 78 +++++++++---------- .../src/vendor/import-meta-resolve.d.ts | 2 +- yarn.lock | 23 ++---- 8 files changed, 86 insertions(+), 113 deletions(-) diff --git a/Gulpfile.mjs b/Gulpfile.mjs index fb6f08de7b30..6b5988e5c442 100644 --- a/Gulpfile.mjs +++ b/Gulpfile.mjs @@ -763,7 +763,7 @@ gulp.task("build-babel", () => buildBabel(true, /* exclude */ libBundles)); gulp.task("build-vendor", async () => { const input = fileURLToPath( - await importMetaResolve("import-meta-resolve", import.meta.url) + importMetaResolve("import-meta-resolve", import.meta.url) ); const output = "./packages/babel-core/src/vendor/import-meta-resolve.js"; @@ -779,6 +779,11 @@ gulp.task("build-vendor", async () => { extensions: [".js", ".mjs", ".cjs", ".json"], preferBuiltins: true, }), + { + // Remove the node: prefix from imports, so that it works in old Node.js version + // TODO(Babel 8): This can be removed. + transform: code => code.replace(/(?<=from ["'"])node:/g, ""), + }, ], }); @@ -801,7 +806,7 @@ ${fs.readFileSync(path.join(path.dirname(input), "license"), "utf8")}*/ fs.writeFileSync( output.replace(".js", ".d.ts"), - `export function resolve(specifier: string, parent: string): Promise;` + `export function resolve(specifier: string, parent: string): string;` ); }); diff --git a/babel.config.js b/babel.config.js index 27ad7dc555b3..d9fa64d372e8 100644 --- a/babel.config.js +++ b/babel.config.js @@ -182,7 +182,6 @@ module.exports = function (api) { ["@babel/proposal-object-rest-spread", { useBuiltIns: true }], convertESM ? "@babel/proposal-export-namespace-from" : null, - convertESM ? pluginImportMetaUrl : null, pluginPackageJsonMacro, @@ -276,6 +275,12 @@ module.exports = function (api) { ], ], }, + convertESM && { + exclude: [ + "./packages/babel-core/src/config/files/import-meta-resolve.ts", + ].map(normalize), + plugins: [pluginImportMetaUrl], + }, { test: sources.map(source => normalize(source.replace("/src", "/test"))), plugins: [ diff --git a/package.json b/package.json index 23e66f627d2d..206c8d6b126d 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "gulp-filter": "^7.0.0", "gulp-plumber": "^1.2.1", "husky": "^7.0.4", - "import-meta-resolve": "^1.1.1", + "import-meta-resolve": "^3.0.0", "jest": "^29.0.1", "jest-light-runner": "^0.4.0", "jest-worker": "^29.0.1", diff --git a/packages/babel-core/src/config/files/import-meta-resolve.ts b/packages/babel-core/src/config/files/import-meta-resolve.ts index 68622f500134..2340e9ef9ed8 100644 --- a/packages/babel-core/src/config/files/import-meta-resolve.ts +++ b/packages/babel-core/src/config/files/import-meta-resolve.ts @@ -1,48 +1,29 @@ -import { createRequire } from "module"; import { resolve as polyfill } from "../../vendor/import-meta-resolve"; -const require = createRequire(import.meta.url); +declare const USE_ESM: boolean; -let import_; -try { - // Node < 13.3 doesn't support import() syntax. - import_ = require("./import.cjs"); -} catch {} +let importMetaResolve: (specifier: string, parent: string) => string; -// import.meta.resolve is only available in ESM, but this file is compiled to CJS. -// We can extract it using dynamic import. -const importMetaResolveP: Promise = - import_ && - // Due to a Node.js/V8 bug (https://github.com/nodejs/node/issues/35889), we cannot - // use always dynamic import because it segfaults when running in a Node.js `vm` context, - // which is used by the default Jest environment and by webpack-cli. - // - // However, import.meta.resolve is experimental and only enabled when Node.js is run - // with the `--experimental-import-meta-resolve` flag: we can avoid calling import() - // when that flag is not enabled, so that the default behavior never segfaults. - // - // Hopefully, before Node.js unflags import.meta.resolve, either: - // - we will move to ESM, so that we have direct access to import.meta.resolve, or - // - the V8 bug will be fixed so that we can safely use dynamic import by default. - // - // I (@nicolo-ribaudo) am really annoyed by this bug, because there is no known - // work-around other than "don't use dynamic import if you are running in a `vm` context", - // but there is no reliable way to detect it (you cannot try/catch segfaults). - // - // This is the only place where we *need* to use dynamic import because we need to access - // an ES module. All the other places will first try using require() and *then*, if - // it throws because it's a module, will fallback to import(). - process.execArgv.includes("--experimental-import-meta-resolve") - ? import_("data:text/javascript,export default import.meta.resolve").then( - (m: { default: ImportMeta["resolve"] | undefined }) => - m.default || polyfill, - () => polyfill, - ) - : Promise.resolve(polyfill); +if (USE_ESM) { + // Node.js < 20, when using the `--experimental-import-meta-resolve` flag, + // have an asynchronous implementation of import.meta.resolve. + if ( + typeof import.meta.resolve === "function" && + typeof import.meta.resolve(import.meta.url) === "string" + ) { + // @ts-expect-error: TS defines import.meta as returning a promise + importMetaResolve = import.meta.resolve; + } else { + importMetaResolve = polyfill; + } +} else { + importMetaResolve = polyfill; +} -export default async function resolve( - specifier: Parameters[0], - parent?: Parameters[1], -): ReturnType { - return (await importMetaResolveP)(specifier, parent); +export default function resolve( + specifier: string, + parent?: string | URL, +): string { + // @ts-expect-error: TS defines import.meta.resolve as returning a promises + return importMetaResolve(specifier, parent); } diff --git a/packages/babel-core/src/config/files/index.ts b/packages/babel-core/src/config/files/index.ts index 5c340eb4b32d..55f68cb73f4e 100644 --- a/packages/babel-core/src/config/files/index.ts +++ b/packages/babel-core/src/config/files/index.ts @@ -21,10 +21,9 @@ export type { RelativeConfig, FilePackageData, } from "./types"; -export { loadPlugin, loadPreset } from "./plugins"; - -import gensync from "gensync"; -import * as plugins from "./plugins"; - -export const resolvePlugin = gensync(plugins.resolvePlugin).sync; -export const resolvePreset = gensync(plugins.resolvePreset).sync; +export { + loadPlugin, + loadPreset, + resolvePlugin, + resolvePreset, +} from "./plugins"; diff --git a/packages/babel-core/src/config/files/plugins.ts b/packages/babel-core/src/config/files/plugins.ts index 9a7b4b64411e..abff0cd21e40 100644 --- a/packages/babel-core/src/config/files/plugins.ts +++ b/packages/babel-core/src/config/files/plugins.ts @@ -4,7 +4,7 @@ import buildDebug from "debug"; import path from "path"; -import gensync, { type Handler } from "gensync"; +import type { Handler } from "gensync"; import { isAsync } from "../../gensync-utils/async"; import loadCodeDefault, { supportsESM } from "./module-types"; import { fileURLToPath, pathToFileURL } from "url"; @@ -27,21 +27,14 @@ const OTHER_PRESET_ORG_RE = /^(@(?!babel\/)[^/]+\/)(?![^/]*babel-preset(?:-|\/|$)|[^/]+\/)/; const OTHER_ORG_DEFAULT_RE = /^(@(?!babel$)[^/]+)$/; -export function* resolvePlugin(name: string, dirname: string): Handler { - // eslint-disable-next-line @typescript-eslint/no-use-before-define - return yield* resolveStandardizedName("plugin", name, dirname); -} - -export function* resolvePreset(name: string, dirname: string): Handler { - // eslint-disable-next-line @typescript-eslint/no-use-before-define - return yield* resolveStandardizedName("preset", name, dirname); -} +export const resolvePlugin = resolveStandardizedName.bind(null, "plugin"); +export const resolvePreset = resolveStandardizedName.bind(null, "preset"); export function* loadPlugin( name: string, dirname: string, ): Handler<{ filepath: string; value: unknown }> { - const filepath = yield* resolvePlugin(name, dirname); + const filepath = resolvePlugin(name, dirname, yield* isAsync()); const value = yield* requireModule("plugin", filepath); debug("Loaded plugin %o from %o.", name, dirname); @@ -53,7 +46,7 @@ export function* loadPreset( name: string, dirname: string, ): Handler<{ filepath: string; value: unknown }> { - const filepath = yield* resolvePreset(name, dirname); + const filepath = resolvePreset(name, dirname, yield* isAsync()); const value = yield* requireModule("preset", filepath); @@ -122,22 +115,26 @@ function* resolveAlternativesHelper( } function tryRequireResolve( - id: Parameters[0], - { paths: [dirname] }: Parameters[1], + id: string, + dirname: string | undefined, ): Result { try { - return { error: null, value: require.resolve(id, { paths: [dirname] }) }; + if (dirname) { + return { error: null, value: require.resolve(id, { paths: [dirname] }) }; + } else { + return { error: null, value: require.resolve(id) }; + } } catch (error) { return { error, value: null }; } } -async function tryImportMetaResolve( +function tryImportMetaResolve( id: Parameters[0], options: Parameters[1], -): Promise> { +): Result { try { - return { error: null, value: await importMetaResolve(id, options) }; + return { error: null, value: importMetaResolve(id, options) }; } catch (error) { return { error, value: null }; } @@ -151,11 +148,11 @@ function resolveStandardizedNameForRequire( const it = resolveAlternativesHelper(type, name); let res = it.next(); while (!res.done) { - res = it.next(tryRequireResolve(res.value, { paths: [dirname] })); + res = it.next(tryRequireResolve(res.value, dirname)); } return res.value; } -async function resolveStandardizedNameForImport( +function resolveStandardizedNameForImport( type: "plugin" | "preset", name: string, dirname: string, @@ -167,36 +164,33 @@ async function resolveStandardizedNameForImport( const it = resolveAlternativesHelper(type, name); let res = it.next(); while (!res.done) { - res = it.next(await tryImportMetaResolve(res.value, parentUrl)); + res = it.next(tryImportMetaResolve(res.value, parentUrl)); } return fileURLToPath(res.value); } -const resolveStandardizedName = gensync< - [type: "plugin" | "preset", name: string, dirname?: string], - string ->({ - sync(type, name, dirname = process.cwd()) { +function resolveStandardizedName( + type: "plugin" | "preset", + name: string, + dirname: string, + resolveESM: boolean, +) { + if (!supportsESM || !resolveESM) { return resolveStandardizedNameForRequire(type, name, dirname); - }, - async async(type, name, dirname = process.cwd()) { - if (!supportsESM) { - return resolveStandardizedNameForRequire(type, name, dirname); - } + } + try { + return resolveStandardizedNameForImport(type, name, dirname); + } catch (e) { try { - return await resolveStandardizedNameForImport(type, name, dirname); - } catch (e) { - try { - return resolveStandardizedNameForRequire(type, name, dirname); - } catch (e2) { - if (e.type === "MODULE_NOT_FOUND") throw e; - if (e2.type === "MODULE_NOT_FOUND") throw e2; - throw e; - } + return resolveStandardizedNameForRequire(type, name, dirname); + } catch (e2) { + if (e.type === "MODULE_NOT_FOUND") throw e; + if (e2.type === "MODULE_NOT_FOUND") throw e2; + throw e; } - }, -}); + } +} if (!process.env.BABEL_8_BREAKING) { // eslint-disable-next-line no-var diff --git a/packages/babel-core/src/vendor/import-meta-resolve.d.ts b/packages/babel-core/src/vendor/import-meta-resolve.d.ts index 497229059004..a4c6e119ef03 100644 --- a/packages/babel-core/src/vendor/import-meta-resolve.d.ts +++ b/packages/babel-core/src/vendor/import-meta-resolve.d.ts @@ -1 +1 @@ -export function resolve(specifier: string, parent: string): Promise; \ No newline at end of file +export function resolve(specifier: string, parent: string): string; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index bc254fd7cfd6..6d750b99505c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6088,7 +6088,7 @@ __metadata: gulp-filter: ^7.0.0 gulp-plumber: ^1.2.1 husky: ^7.0.4 - import-meta-resolve: ^1.1.1 + import-meta-resolve: ^3.0.0 jest: ^29.0.1 jest-light-runner: ^0.4.0 jest-worker: ^29.0.1 @@ -6507,15 +6507,6 @@ __metadata: languageName: node linkType: hard -"builtins@npm:^4.0.0": - version: 4.0.0 - resolution: "builtins@npm:4.0.0" - dependencies: - semver: ^7.0.0 - checksum: 3c8b3b96ed88dd8e21286a3590292862ad62a59085bbcd77a4470848fed0f59fcd67f366afdf9ca8d7e77abce7ccf336bf662c12ead949294aa03bc563a57a1c - languageName: node - linkType: hard - "c8@npm:^7.12.0": version: 7.12.0 resolution: "c8@npm:7.12.0" @@ -9607,12 +9598,10 @@ fsevents@^1.2.7: languageName: node linkType: hard -"import-meta-resolve@npm:^1.1.1": - version: 1.1.1 - resolution: "import-meta-resolve@npm:1.1.1" - dependencies: - builtins: ^4.0.0 - checksum: 2024161e169c45ed25a9f51d984a432a9cc342c35737f9410266bab237ca2f756c1f80c15e2297df83c92f585743d5105291f2ad24094a513f804c6023ea1472 +"import-meta-resolve@npm:^3.0.0": + version: 3.0.0 + resolution: "import-meta-resolve@npm:3.0.0" + checksum: d0428cd14915ee0093b995dc5bbc70bd01cc668822f52b62af98f728e5d6a08724f07e6aa9f5fae002d5eecbf6ec2cdcd379bf4869dd1b353bd080693f91e394 languageName: node linkType: hard @@ -13612,7 +13601,7 @@ fsevents@^1.2.7: languageName: node linkType: hard -"semver@npm:^7.0.0, semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.7": +"semver@npm:^7.3.2, semver@npm:^7.3.5, semver@npm:^7.3.7": version: 7.3.7 resolution: "semver@npm:7.3.7" dependencies: