Skip to content

Commit e7c705a

Browse files
Respect package.json#exports when resolving plugins (#14110)
* Respect `package.json#exports` when resolving plugins * Use native import.meta.resolve when available * Workaround V8 bug that makes Babel-Jest segfault.
1 parent 693f2d2 commit e7c705a

File tree

16 files changed

+282
-67
lines changed

16 files changed

+282
-67
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ packages/babel-preset-env/test/debug-fixtures
2222
packages/babel-standalone/babel.js
2323
packages/babel-standalone/babel.min.js
2424
packages/babel-parser/test/expressions
25+
packages/babel-core/src/vendor
2526

2627
eslint/*/lib
2728
eslint/*/node_modules

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ package-lock.json
2525

2626
/packages/babel-compat-data/build
2727

28+
/packages/babel-core/src/vendor/*.js
29+
/packages/babel-core/src/vendor/*.ts
30+
2831
/packages/babel-runtime/helpers/*.js
2932
!/packages/babel-runtime/helpers/toArray.js
3033
!/packages/babel-runtime/helpers/iterableToArray.js

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package.json
22
packages/babel-preset-env/data
33
packages/babel-compat-data/data
44
packages/babel-compat-data/scripts/data/overlapping-plugins.js
5+
packages/babel-core/src/vendor
56
packages/*/test/fixtures/**/input.*
67
packages/*/test/fixtures/**/exec.*
78
packages/*/test/fixtures/**/output.*

Gulpfile.mjs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import _rollupDts from "rollup-plugin-dts";
2020
const { default: rollupDts } = _rollupDts;
2121
import { Worker as JestWorker } from "jest-worker";
2222
import glob from "glob";
23+
import { resolve as importMetaResolve } from "import-meta-resolve";
2324

2425
import rollupBabelSource from "./scripts/rollup-plugin-babel-source.js";
2526
import formatCode from "./scripts/utils/formatCode.js";
@@ -527,9 +528,54 @@ gulp.task(
527528

528529
gulp.task("build-babel", () => buildBabel(true, /* exclude */ libBundles));
529530

531+
gulp.task("build-vendor", async () => {
532+
const input = fileURLToPath(
533+
await importMetaResolve("import-meta-resolve", import.meta.url)
534+
);
535+
const output = "./packages/babel-core/src/vendor/import-meta-resolve.js";
536+
537+
const bundle = await rollup({
538+
input,
539+
onwarn(warning, warn) {
540+
if (warning.code === "CIRCULAR_DEPENDENCY") return;
541+
warn(warning);
542+
},
543+
plugins: [
544+
rollupCommonJs({ defaultIsModuleExports: true }),
545+
rollupNodeResolve({
546+
extensions: [".js", ".mjs", ".cjs", ".json"],
547+
preferBuiltins: true,
548+
}),
549+
],
550+
});
551+
552+
await bundle.write({
553+
file: output,
554+
format: "es",
555+
sourcemap: false,
556+
exports: "named",
557+
banner: String.raw`
558+
/****************************************************************************\
559+
* NOTE FROM BABEL AUTHORS *
560+
* This file is inlined from https://github.com/wooorm/import-meta-resolve, *
561+
* because we need to compile it to CommonJS. *
562+
\****************************************************************************/
563+
564+
/*
565+
${fs.readFileSync(path.join(path.dirname(input), "license"), "utf8")}*/
566+
`,
567+
});
568+
569+
fs.writeFileSync(
570+
output.replace(".js", ".d.ts"),
571+
`export function resolve(specifier: stirng, parent: string): Promise<string>;`
572+
);
573+
});
574+
530575
gulp.task(
531576
"build",
532577
gulp.series(
578+
"build-vendor",
533579
gulp.parallel("build-rollup", "build-babel", "generate-runtime-helpers"),
534580
gulp.parallel(
535581
"generate-standalone",
@@ -552,6 +598,7 @@ gulp.task("build-no-bundle-watch", () => buildBabel(false));
552598
gulp.task(
553599
"build-dev",
554600
gulp.series(
601+
"build-vendor",
555602
"build-no-bundle",
556603
gulp.parallel(
557604
"generate-standalone",

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"gulp-filter": "^7.0.0",
6363
"gulp-plumber": "^1.2.1",
6464
"husky": "^7.0.4",
65+
"import-meta-resolve": "^1.1.1",
6566
"jest": "^27.4.0",
6667
"jest-worker": "^27.4.0",
6768
"lint-staged": "^9.2.0",
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { createRequire } from "module";
2+
import { resolve as polyfill } from "../../vendor/import-meta-resolve";
3+
4+
const require = createRequire(import.meta.url);
5+
6+
let import_;
7+
try {
8+
// Node < 13.3 doesn't support import() syntax.
9+
import_ = require("./import").default;
10+
} catch {}
11+
12+
// import.meta.resolve is only available in ESM, but this file is compiled to CJS.
13+
// We can extract ir using dynamic import.
14+
const resolveP =
15+
import_ &&
16+
// Due to a Node.js/V8 bug (https://github.com/nodejs/node/issues/35889), we cannot
17+
// use dynamic import when running in the default Jest environment because it
18+
// uses vm.SourceTextModule.
19+
// Jest defines globalThis["jest-symbol-do-not-touch"] in
20+
// https://github.com/facebook/jest/blob/11d79ec096a25851124356095d60352f6ca2824e/packages/jest-util/src/installCommonGlobals.ts#L49
21+
// which is called by
22+
// https://github.com/facebook/jest/blob/11d79ec096a25851124356095d60352f6ca2824e/packages/jest-environment-node/src/index.ts#L85
23+
//
24+
// Note that our Jest runner doesn't have this problem, because it runs ESM in the default
25+
// Node.js context rather than using the `vm` module.
26+
//
27+
// When V8 fixes this bug, we can remove this check. We usually don't have package-specific hacks,
28+
// but Jest is a big Babel consumer widely used in the community and they cannot workaround
29+
// this problem on their side.
30+
!Object.hasOwnProperty.call(global, "jest-symbol-do-not-touch")
31+
? import_("data:text/javascript,export default import.meta.resolve").then(
32+
// Since import.meta.resolve is unstable and only available when
33+
// using the --experimental-import-meta-resolve flag, we almost
34+
// always use the polyfill for now.
35+
m => m.default || polyfill,
36+
() => polyfill,
37+
)
38+
: Promise.resolve(polyfill);
39+
40+
export default function getImportMetaResolve(): Promise<ImportMeta["resolve"]> {
41+
return resolveP;
42+
}

packages/babel-core/src/config/files/index.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ export type {
2121
RelativeConfig,
2222
FilePackageData,
2323
} from "./types";
24-
export {
25-
resolvePlugin,
26-
resolvePreset,
27-
loadPlugin,
28-
loadPreset,
29-
} from "./plugins";
24+
export { loadPlugin, loadPreset } from "./plugins";
25+
26+
import gensync from "gensync";
27+
import * as plugins from "./plugins";
28+
29+
export const resolvePlugin = gensync(plugins.resolvePlugin).sync;
30+
export const resolvePreset = gensync(plugins.resolvePreset).sync;

packages/babel-core/src/config/files/module-types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ try {
1212
import_ = require("./import").default;
1313
} catch {}
1414

15+
export const supportsESM = !!import_;
16+
1517
export default function* loadCjsOrMjsDefault(
1618
filepath: string,
1719
asyncError: string,

packages/babel-core/src/config/files/plugins.ts

Lines changed: 106 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44

55
import buildDebug from "debug";
66
import path from "path";
7-
import type { Handler } from "gensync";
8-
import loadCjsOrMjsDefault from "./module-types";
7+
import gensync, { type Gensync, type Handler } from "gensync";
98
import { isAsync } from "../../gensync-utils/async";
9+
import loadCjsOrMjsDefault, { supportsESM } from "./module-types";
10+
import { fileURLToPath, pathToFileURL } from "url";
11+
12+
import getImportMetaResolve from "./import-meta-resolve";
1013

1114
import { createRequire } from "module";
1215
const require = createRequire(import.meta.url);
@@ -24,22 +27,19 @@ const OTHER_PRESET_ORG_RE =
2427
/^(@(?!babel\/)[^/]+\/)(?![^/]*babel-preset(?:-|\/|$)|[^/]+\/)/;
2528
const OTHER_ORG_DEFAULT_RE = /^(@(?!babel$)[^/]+)$/;
2629

27-
export function resolvePlugin(name: string, dirname: string): string | null {
28-
return resolveStandardizedName("plugin", name, dirname);
30+
export function* resolvePlugin(name: string, dirname: string): Handler<string> {
31+
return yield* resolveStandardizedName("plugin", name, dirname);
2932
}
3033

31-
export function resolvePreset(name: string, dirname: string): string | null {
32-
return resolveStandardizedName("preset", name, dirname);
34+
export function* resolvePreset(name: string, dirname: string): Handler<string> {
35+
return yield* resolveStandardizedName("preset", name, dirname);
3336
}
3437

3538
export function* loadPlugin(
3639
name: string,
3740
dirname: string,
3841
): Handler<{ filepath: string; value: unknown }> {
39-
const filepath = resolvePlugin(name, dirname);
40-
if (!filepath) {
41-
throw new Error(`Plugin ${name} not found relative to ${dirname}`);
42-
}
42+
const filepath = yield* resolvePlugin(name, dirname);
4343

4444
const value = yield* requireModule("plugin", filepath);
4545
debug("Loaded plugin %o from %o.", name, dirname);
@@ -51,10 +51,7 @@ export function* loadPreset(
5151
name: string,
5252
dirname: string,
5353
): Handler<{ filepath: string; value: unknown }> {
54-
const filepath = resolvePreset(name, dirname);
55-
if (!filepath) {
56-
throw new Error(`Preset ${name} not found relative to ${dirname}`);
57-
}
54+
const filepath = yield* resolvePreset(name, dirname);
5855

5956
const value = yield* requireModule("preset", filepath);
6057

@@ -93,62 +90,111 @@ function standardizeName(type: "plugin" | "preset", name: string) {
9390
);
9491
}
9592

96-
function resolveStandardizedName(
93+
type Result<T> = { error: Error; value: null } | { error: null; value: T };
94+
95+
function* resolveAlternativesHelper(
9796
type: "plugin" | "preset",
9897
name: string,
99-
dirname: string = process.cwd(),
100-
) {
98+
): Iterator<string, string, Result<string>> {
10199
const standardizedName = standardizeName(type, name);
100+
const { error, value } = yield standardizedName;
101+
if (!error) return value;
102+
103+
// @ts-ignore
104+
if (error.code !== "MODULE_NOT_FOUND") throw error;
102105

106+
if (standardizedName !== name && !(yield name).error) {
107+
error.message += `\n- If you want to resolve "${name}", use "module:${name}"`;
108+
}
109+
110+
if (!(yield standardizeName(type, "@babel/" + name)).error) {
111+
error.message += `\n- Did you mean "@babel/${name}"?`;
112+
}
113+
114+
const oppositeType = type === "preset" ? "plugin" : "preset";
115+
if (!(yield standardizeName(oppositeType, name)).error) {
116+
error.message += `\n- Did you accidentally pass a ${oppositeType} as a ${type}?`;
117+
}
118+
119+
throw error;
120+
}
121+
122+
function tryRequireResolve(
123+
id: Parameters<RequireResolve>[0],
124+
{ paths: [dirname] }: Parameters<RequireResolve>[1],
125+
): Result<string> {
103126
try {
104-
return require.resolve(standardizedName, {
105-
paths: [dirname],
106-
});
107-
} catch (e) {
108-
if (e.code !== "MODULE_NOT_FOUND") throw e;
109-
110-
if (standardizedName !== name) {
111-
let resolvedOriginal = false;
112-
try {
113-
require.resolve(name, {
114-
paths: [dirname],
115-
});
116-
resolvedOriginal = true;
117-
} catch {}
118-
119-
if (resolvedOriginal) {
120-
e.message += `\n- If you want to resolve "${name}", use "module:${name}"`;
121-
}
122-
}
127+
return { error: null, value: require.resolve(id, { paths: [dirname] }) };
128+
} catch (error) {
129+
return { error, value: null };
130+
}
131+
}
123132

124-
let resolvedBabel = false;
125-
try {
126-
require.resolve(standardizeName(type, "@babel/" + name), {
127-
paths: [dirname],
128-
});
129-
resolvedBabel = true;
130-
} catch {}
131-
132-
if (resolvedBabel) {
133-
e.message += `\n- Did you mean "@babel/${name}"?`;
133+
async function tryImportMetaResolve(
134+
id: Parameters<ImportMeta["resolve"]>[0],
135+
options: Parameters<ImportMeta["resolve"]>[1],
136+
): Promise<Result<string>> {
137+
const importMetaResolve = await getImportMetaResolve();
138+
try {
139+
return { error: null, value: await importMetaResolve(id, options) };
140+
} catch (error) {
141+
return { error, value: null };
142+
}
143+
}
144+
145+
function resolveStandardizedNameForRequrie(
146+
type: "plugin" | "preset",
147+
name: string,
148+
dirname: string,
149+
) {
150+
const it = resolveAlternativesHelper(type, name);
151+
let res = it.next();
152+
while (!res.done) {
153+
res = it.next(tryRequireResolve(res.value, { paths: [dirname] }));
154+
}
155+
return res.value;
156+
}
157+
async function resolveStandardizedNameForImport(
158+
type: "plugin" | "preset",
159+
name: string,
160+
dirname: string,
161+
) {
162+
const parentUrl = pathToFileURL(
163+
path.join(dirname, "./babel-virtual-resolve-base.js"),
164+
).href;
165+
166+
const it = resolveAlternativesHelper(type, name);
167+
let res = it.next();
168+
while (!res.done) {
169+
res = it.next(await tryImportMetaResolve(res.value, parentUrl));
170+
}
171+
return fileURLToPath(res.value);
172+
}
173+
174+
const resolveStandardizedName: Gensync<
175+
(type: "plugin" | "preset", name: string, dirname?: string) => string
176+
> = gensync({
177+
sync(type, name, dirname = process.cwd()) {
178+
return resolveStandardizedNameForRequrie(type, name, dirname);
179+
},
180+
async async(type, name, dirname = process.cwd()) {
181+
if (!supportsESM) {
182+
return resolveStandardizedNameForRequrie(type, name, dirname);
134183
}
135184

136-
let resolvedOppositeType = false;
137-
const oppositeType = type === "preset" ? "plugin" : "preset";
138185
try {
139-
require.resolve(standardizeName(oppositeType, name), {
140-
paths: [dirname],
141-
});
142-
resolvedOppositeType = true;
143-
} catch {}
144-
145-
if (resolvedOppositeType) {
146-
e.message += `\n- Did you accidentally pass a ${oppositeType} as a ${type}?`;
186+
return await resolveStandardizedNameForImport(type, name, dirname);
187+
} catch (e) {
188+
try {
189+
return resolveStandardizedNameForRequrie(type, name, dirname);
190+
} catch (e2) {
191+
if (e.type === "MODULE_NOT_FOUND") throw e;
192+
if (e2.type === "MODULE_NOT_FOUND") throw e2;
193+
throw e;
194+
}
147195
}
148-
149-
throw e;
150-
}
151-
}
196+
},
197+
});
152198

153199
if (!process.env.BABEL_8_BREAKING) {
154200
// eslint-disable-next-line no-var

packages/babel-core/src/vendor/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy