Skip to content

Commit ca52e08

Browse files
Improve CJS compat with ESM-based @babel/core (#15137)
Co-authored-by: liuxingbaoyu <30521560+liuxingbaoyu@users.noreply.github.com>
1 parent 43dce19 commit ca52e08

16 files changed

Lines changed: 286 additions & 5 deletions

babel.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,11 @@ module.exports = function (api) {
231231
{ name: "USE_ESM", value: outputType === "module" },
232232
"flag-USE_ESM",
233233
],
234+
[
235+
pluginToggleBooleanFlag,
236+
{ name: "IS_STANDALONE", value: env === "standalone" },
237+
"flag-IS_STANDALONE",
238+
],
234239

235240
process.env.STRIP_BABEL_8_FLAG && [
236241
pluginToggleBooleanFlag,

packages/babel-core/cjs-proxy.cjs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
"use strict";
22

33
const babelP = import("./lib/index.js");
4+
let babel = null;
5+
Object.defineProperty(exports, "__ initialize @babel/core cjs proxy __", {
6+
set(val) {
7+
babel = val;
8+
},
9+
});
410

511
const functionNames = [
612
"createConfigItem",
@@ -11,13 +17,9 @@ const functionNames = [
1117
"transformFromAst",
1218
"parse",
1319
];
20+
const propertyNames = ["types", "tokTypes", "traverse", "template", "version"];
1421

1522
for (const name of functionNames) {
16-
exports[`${name}Sync`] = function () {
17-
throw new Error(
18-
`"${name}Sync" is not supported when loading @babel/core using require()`
19-
);
20-
};
2123
exports[name] = function (...args) {
2224
babelP.then(babel => {
2325
babel[name](...args);
@@ -26,4 +28,24 @@ for (const name of functionNames) {
2628
exports[`${name}Async`] = function (...args) {
2729
return babelP.then(babel => babel[`${name}Async`](...args));
2830
};
31+
exports[`${name}Sync`] = function (...args) {
32+
if (!babel) throw notLoadedError(`${name}Sync`, "callable");
33+
return babel[`${name}Sync`](...args);
34+
};
35+
}
36+
37+
for (const name of propertyNames) {
38+
Object.defineProperty(exports, name, {
39+
get() {
40+
if (!babel) throw notLoadedError(name, "accessible");
41+
return babel[name];
42+
},
43+
});
44+
}
45+
46+
function notLoadedError(name, keyword) {
47+
return new Error(
48+
`The \`${name}\` export of @babel/core is only ${keyword}` +
49+
` from the CommonJS version after that the ESM version is loaded.`
50+
);
2951
}

packages/babel-core/src/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
declare const PACKAGE_JSON: { name: string; version: string };
2+
declare const USE_ESM: boolean, IS_STANDALONE: boolean;
3+
24
export const version = PACKAGE_JSON.version;
35

46
export { default as File } from "./transformation/file/file";
@@ -72,15 +74,27 @@ export const DEFAULT_EXTENSIONS = Object.freeze([
7274
] as const);
7375

7476
// For easier backward-compatibility, provide an API like the one we exposed in Babel 6.
77+
// TODO(Babel 8): Remove this.
7578
import { loadOptionsSync } from "./config";
7679
export class OptionManager {
7780
init(opts: {}) {
7881
return loadOptionsSync(opts);
7982
}
8083
}
8184

85+
// TODO(Babel 8): Remove this.
8286
export function Plugin(alias: string) {
8387
throw new Error(
8488
`The (${alias}) Babel 5 plugin is being run with an unsupported Babel version.`,
8589
);
8690
}
91+
92+
import Module from "module";
93+
import * as thisFile from "./index";
94+
if (USE_ESM) {
95+
if (!IS_STANDALONE) {
96+
// Pass this module to the CJS proxy, so that it can be synchronously accessed.
97+
const cjsProxy = Module.createRequire(import.meta.url)("../cjs-proxy.cjs");
98+
cjsProxy["__ initialize @babel/core cjs proxy __"] = thisFile;
99+
}
100+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { execFile } from "child_process";
2+
import { createRequire } from "module";
3+
import { outputType } from "./helpers/esm.js";
4+
5+
const require = createRequire(import.meta.url);
6+
7+
async function run(name) {
8+
return new Promise((res, rej) => {
9+
execFile(
10+
process.execPath,
11+
[require.resolve(`./fixtures/esm-cjs-integration/${name}`)],
12+
{ env: process.env },
13+
(error, stdout, stderr) => {
14+
if (error) rej(error);
15+
res({ stdout: stdout.toString(), stderr: stderr.toString() });
16+
},
17+
);
18+
});
19+
}
20+
21+
(outputType === "module" ? describe : describe.skip)("usage from cjs", () => {
22+
it("lazy plugin required", async () => {
23+
expect(await run("lazy-plugin-required.cjs")).toMatchInlineSnapshot(`
24+
Object {
25+
"stderr": "",
26+
"stdout": "\\"Replaced!\\";
27+
",
28+
}
29+
`);
30+
});
31+
32+
it("lazy plugin as config string", async () => {
33+
expect(await run("lazy-plugin-as-string.cjs")).toMatchInlineSnapshot(`
34+
Object {
35+
"stderr": "",
36+
"stdout": "\\"Replaced!\\";
37+
",
38+
}
39+
`);
40+
});
41+
42+
it("eager plugin required", async () => {
43+
await expect(run("eager-plugin-required.cjs")).rejects.toThrow(
44+
"The `types` export of @babel/core is only accessible from" +
45+
" the CommonJS version after that the ESM version is loaded.",
46+
);
47+
});
48+
49+
it("eager plugin required after dynamic esm import", async () => {
50+
expect(await run("eager-plugin-required-after-dynamic-esm-import.cjs"))
51+
.toMatchInlineSnapshot(`
52+
Object {
53+
"stderr": "",
54+
"stdout": "\\"Replaced!\\";
55+
",
56+
}
57+
`);
58+
});
59+
60+
it("eager plugin required after static esm import", async () => {
61+
expect(await run("eager-plugin-required-after-static-esm-import.mjs"))
62+
.toMatchInlineSnapshot(`
63+
Object {
64+
"stderr": "",
65+
"stdout": "\\"Replaced!\\";
66+
",
67+
}
68+
`);
69+
});
70+
71+
it("eager plugin as config string", async () => {
72+
expect(await run("eager-plugin-as-string.cjs")).toMatchInlineSnapshot(`
73+
Object {
74+
"stderr": "",
75+
"stdout": "\\"Replaced!\\";
76+
",
77+
}
78+
`);
79+
});
80+
81+
it("transformSync", async () => {
82+
await expect(run("transform-sync.cjs")).rejects.toThrow(
83+
"The `transformSync` export of @babel/core is only callable from" +
84+
" the CommonJS version after that the ESM version is loaded.",
85+
);
86+
});
87+
88+
it("transformSync after dynamic esm import", async () => {
89+
expect(await run("transform-sync-after-dynamic-esm-import.cjs"))
90+
.toMatchInlineSnapshot(`
91+
Object {
92+
"stderr": "",
93+
"stdout": "REPLACE_ME;
94+
",
95+
}
96+
`);
97+
});
98+
99+
it("transformSync after static esm import", async () => {
100+
expect(await run("transform-sync-after-static-esm-import.mjs"))
101+
.toMatchInlineSnapshot(`
102+
Object {
103+
"stderr": "",
104+
"stdout": "REPLACE_ME;
105+
",
106+
}
107+
`);
108+
});
109+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const babel = require("../../../cjs-proxy.cjs");
2+
3+
babel
4+
.transformAsync("REPLACE_ME;", {
5+
configFile: false,
6+
plugins: [__dirname + "/plugins/eager.cjs"],
7+
})
8+
.then(out => console.log(out.code), console.error);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import("../../../lib/index.js")
2+
.then(babel =>
3+
babel.transformAsync("REPLACE_ME;", {
4+
configFile: false,
5+
plugins: [require("./plugins/eager.cjs")],
6+
}),
7+
)
8+
.then(out => console.log(out.code), console.error);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as babel from "../../../lib/index.js";
2+
3+
babel
4+
.transformAsync("REPLACE_ME;", {
5+
configFile: false,
6+
plugins: [(await import("./plugins/eager.cjs")).default],
7+
})
8+
.then(out => console.log(out.code), console.error);
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* This test throws an error, because the plugin accesses
3+
* @babel/core's CJS .types export before that the ESM
4+
* version is loaded.
5+
*/
6+
7+
const babel = require("../../../cjs-proxy.cjs");
8+
9+
babel
10+
.transformAsync("REPLACE_ME;", {
11+
configFile: false,
12+
plugins: [require("./plugins/eager.cjs")],
13+
})
14+
.then(out => console.log(out.code), console.error);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const babel = require("../../../cjs-proxy.cjs");
2+
3+
babel
4+
.transformAsync("REPLACE_ME;", {
5+
configFile: false,
6+
plugins: [__dirname + "/plugins/lazy.cjs"],
7+
})
8+
.then(out => console.log(out.code), console.error);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const babel = require("../../../cjs-proxy.cjs");
2+
3+
babel
4+
.transformAsync("REPLACE_ME;", {
5+
configFile: false,
6+
plugins: [require("./plugins/lazy.cjs")],
7+
})
8+
.then(out => console.log(out.code), console.error);

0 commit comments

Comments
 (0)