Skip to content

Commit

Permalink
Use synchronous import.meta.resolve (#15575)
Browse files Browse the repository at this point in the history
* Use synchronous import.meta.resolve

* Use `import-meta-resolve@3.0.0`

* Fix build error

* Fix old node

* Fix
  • Loading branch information
nicolo-ribaudo committed Apr 24, 2023
1 parent 1c2311a commit eb62275
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 113 deletions.
9 changes: 7 additions & 2 deletions Gulpfile.mjs
Expand Up @@ -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";

Expand All @@ -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, ""),
},
],
});

Expand All @@ -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<string>;`
`export function resolve(specifier: string, parent: string): string;`
);
});

Expand Down
7 changes: 6 additions & 1 deletion babel.config.js
Expand Up @@ -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,

Expand Down Expand Up @@ -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: [
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -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",
Expand Down
65 changes: 23 additions & 42 deletions 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<ImportMeta["resolve"]> =
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<ImportMeta["resolve"]>[0],
parent?: Parameters<ImportMeta["resolve"]>[1],
): ReturnType<ImportMeta["resolve"]> {
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);
}
13 changes: 6 additions & 7 deletions packages/babel-core/src/config/files/index.ts
Expand Up @@ -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";
78 changes: 36 additions & 42 deletions packages/babel-core/src/config/files/plugins.ts
Expand Up @@ -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";
Expand All @@ -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<string> {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
return yield* resolveStandardizedName("plugin", name, dirname);
}

export function* resolvePreset(name: string, dirname: string): Handler<string> {
// 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);
Expand All @@ -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);

Expand Down Expand Up @@ -122,22 +115,26 @@ function* resolveAlternativesHelper(
}

function tryRequireResolve(
id: Parameters<RequireResolve>[0],
{ paths: [dirname] }: Parameters<RequireResolve>[1],
id: string,
dirname: string | undefined,
): Result<string> {
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<ImportMeta["resolve"]>[0],
options: Parameters<ImportMeta["resolve"]>[1],
): Promise<Result<string>> {
): Result<string> {
try {
return { error: null, value: await importMetaResolve(id, options) };
return { error: null, value: importMetaResolve(id, options) };
} catch (error) {
return { error, value: null };
}
Expand All @@ -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,
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/babel-core/src/vendor/import-meta-resolve.d.ts
@@ -1 +1 @@
export function resolve(specifier: string, parent: string): Promise<string>;
export function resolve(specifier: string, parent: string): string;
23 changes: 6 additions & 17 deletions yarn.lock
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit eb62275

Please sign in to comment.