Skip to content

Commit 7428811

Browse files
authored
Improve TS babel config loading (#17392)
* fix: search babel.config.ts and babel.config.mts * fix: improve error message when .mts is loaded * add more test cases * add tsx integration test Also disabled the ts-node integration test because in Babel 8, we removed the fallbackToTranspiledModule flag in readConfig. However, when the `ts-node` is registered and then disabled, the `require.extensions[.mts]` hook is still alive, which complicated our ts-support detection * test: extract ts-node & tsx integration tests * disable tsconfig loading * provide dummy TSX_TSCONFIG_PATH * check node strip-types feature
1 parent ce963ce commit 7428811

10 files changed

Lines changed: 521 additions & 46 deletions

File tree

packages/babel-core/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@
7676
"@types/resolve": "^1.3.2",
7777
"@types/semver": "^5.4.0",
7878
"rimraf": "^3.0.0",
79-
"ts-node": "^11.0.0-beta.1"
79+
"ts-node": "^11.0.0-beta.1",
80+
"tsx": "^4.20.3"
8081
},
8182
"conditions": {
8283
"BABEL_8_BREAKING": [

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export const ROOT_CONFIG_FILENAMES = [
3030
"babel.config.mjs",
3131
"babel.config.json",
3232
"babel.config.cts",
33+
"babel.config.ts",
34+
"babel.config.mts",
3335
];
3436
const RELATIVE_CONFIG_FILENAMES = [
3537
".babelrc",

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

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ const loadMjsFromPath = endHiddenCallStack(async function loadMjsFromPath(
8888
}
8989
});
9090

91+
const tsNotSupportedError = (ext: string) => `\
92+
You are using a ${ext} config file, but Babel only supports transpiling .cts configs. Either:
93+
- Use a .cts config file
94+
- Update to Node.js 23.6.0, which has native TypeScript support
95+
- Install tsx to transpile ${ext} files on the fly\
96+
`;
97+
9198
const SUPPORTED_EXTENSIONS = {
9299
".js": "unknown",
93100
".mjs": "esm",
@@ -180,7 +187,11 @@ export default function* loadCodeDefault(
180187

181188
return (yield* waitFor(promise)).default;
182189
}
183-
throw new ConfigError(esmError, filepath);
190+
if (isTS) {
191+
throw new ConfigError(tsNotSupportedError(ext), filepath);
192+
} else {
193+
throw new ConfigError(esmError, filepath);
194+
}
184195
default:
185196
throw new Error("Internal Babel error: unreachable code.");
186197
}
@@ -192,6 +203,7 @@ function ensureTsSupport<T>(
192203
callback: () => T,
193204
): T {
194205
if (
206+
process.features.typescript ||
195207
require.extensions[".ts"] ||
196208
require.extensions[".cts"] ||
197209
require.extensions[".mts"]
@@ -200,15 +212,7 @@ function ensureTsSupport<T>(
200212
}
201213

202214
if (ext !== ".cts") {
203-
throw new ConfigError(
204-
`\
205-
You are using a ${ext} config file, but Babel only supports transpiling .cts configs. Either:
206-
- Use a .cts config file
207-
- Update to Node.js 23.6.0, which has native TypeScript support
208-
- Install ts-node to transpile ${ext} files on the fly\
209-
`,
210-
filepath,
211-
);
215+
throw new ConfigError(tsNotSupportedError(ext), filepath);
212216
}
213217

214218
const opts: InputOptions = {
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { loadPartialConfigSync } from "../lib/index.js";
2+
import path from "node:path";
3+
import semver from "semver";
4+
import { commonJS } from "$repo-utils";
5+
6+
const { __dirname, require } = commonJS(import.meta.url);
7+
8+
// We skip older versions of node testing for two reasons.
9+
// 1. ts-node and ts don't support the old version of node.
10+
// 2. In the old version of node, jest has been registered in `require.extensions`, which will cause babel to disable the transforming as expected.
11+
const shouldSkip = semver.lt(process.version, "14.0.0");
12+
13+
(shouldSkip ? describe : describe.skip)(
14+
"@babel/core config with ts [dummy]",
15+
() => {
16+
it("dummy", () => {
17+
expect(1).toBe(1);
18+
});
19+
},
20+
);
21+
22+
// The integration tests should not be mixed with other tests because the `register` function
23+
// will affect node's built-in ts support.
24+
(shouldSkip ? describe.skip : describe)(
25+
"@babel/core config ts-node integration",
26+
() => {
27+
let service;
28+
beforeAll(() => {
29+
service = require("ts-node").register({
30+
experimentalResolver: true,
31+
compilerOptions: {
32+
module: "CommonJS",
33+
},
34+
});
35+
service.enabled(true);
36+
});
37+
afterAll(() => {
38+
service.enabled(false);
39+
});
40+
it("should work with ts-node", async () => {
41+
require(
42+
path.join(
43+
__dirname,
44+
"fixtures/config-ts/simple-cts-with-ts-node/babel.config.cts",
45+
),
46+
);
47+
48+
const config = loadPartialConfigSync({
49+
configFile: path.join(
50+
__dirname,
51+
"fixtures/config-ts/simple-cts-with-ts-node/babel.config.cts",
52+
),
53+
});
54+
55+
expect(config.options.targets).toMatchInlineSnapshot(`
56+
Object {
57+
"node": "12.0.0",
58+
}
59+
`);
60+
61+
expect(config.options.sourceRoot).toMatchInlineSnapshot(`"/a/b"`);
62+
});
63+
},
64+
);
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { loadPartialConfigSync } from "../lib/index.js";
2+
import path from "node:path";
3+
import semver from "semver";
4+
import { commonJS } from "$repo-utils";
5+
6+
const { __dirname, require } = commonJS(import.meta.url);
7+
8+
// We skip older versions of node testing for two reasons.
9+
// 1. tsx and ts don't support the old version of node.
10+
// 2. In the old version of node, jest has been registered in `require.extensions`, which will cause babel to disable the transforming as expected.
11+
const shouldSkip = semver.lt(process.version, "18.0.0");
12+
13+
(shouldSkip ? describe : describe.skip)(
14+
"@babel/core config with ts [dummy]",
15+
() => {
16+
it("dummy", () => {
17+
expect(1).toBe(1);
18+
});
19+
},
20+
);
21+
22+
// The integration tests should not be mixed with other tests because the `unregister` function
23+
// will affect node's built-in ts support.
24+
(shouldSkip ? describe.skip : describe)(
25+
"@babel/core config tsx integration",
26+
() => {
27+
let unregister;
28+
beforeAll(async () => {
29+
// tsx/cjs/api is a defined sub-export
30+
// eslint-disable-next-line import/no-unresolved, import/extensions
31+
const tsx = require("tsx/cjs/api");
32+
// Provide a dummy tsconfig.json to avoid tsx resolving @babel/* from the `src` directory, where
33+
// undeclared globals such as the PACKAGE_JSON macro will break
34+
process.env.TSX_TSCONFIG_PATH = path.join(
35+
__dirname,
36+
"fixtures/config-ts/simple-cts-with-tsx/tsconfig.json",
37+
);
38+
unregister = tsx.register();
39+
});
40+
afterAll(() => {
41+
unregister();
42+
delete process.env.TSX_TSCONFIG_PATH;
43+
});
44+
45+
it("should work with tsx", () => {
46+
require(
47+
path.join(
48+
__dirname,
49+
"fixtures/config-ts/simple-cts-with-tsx/babel.config.cts",
50+
),
51+
);
52+
53+
const config = loadPartialConfigSync({
54+
configFile: path.join(
55+
__dirname,
56+
"fixtures/config-ts/simple-cts-with-tsx/babel.config.cts",
57+
),
58+
});
59+
60+
expect(config.options.targets).toMatchInlineSnapshot(`
61+
Object {
62+
"node": "12.0.0",
63+
}
64+
`);
65+
66+
expect(config.options.sourceRoot).toMatchInlineSnapshot(`"/a/b"`);
67+
});
68+
},
69+
);

packages/babel-core/test/config-ts.js

Lines changed: 72 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { commonJS, itGte, itLt, USE_ESM } from "$repo-utils";
66
const { __dirname, require } = commonJS(import.meta.url);
77

88
// We skip older versions of node testing for two reasons.
9-
// 1. ts-node and ts don't support the old version of node.
9+
// 1. ts don't support the old version of node.
1010
// 2. In the old version of node, jest has been registered in `require.extensions`, which will cause babel to disable the transforming as expected.
1111
const shouldSkip = semver.lt(process.version, "14.0.0");
1212

@@ -77,7 +77,7 @@ const nodeLt23_6_andRequireBabelPackages =
7777
// This isn't by design, but reflects the status quo when running in Node.js
7878
// versions that don't have native support for .ts files.
7979
// It can be changed if needed.
80-
nodeLt23_6("show not support .ts config file", () => {
80+
nodeLt23_6("should not support .ts config file", () => {
8181
expect(() => {
8282
loadPartialConfigSync({
8383
configFile: path.join(
@@ -90,40 +90,28 @@ const nodeLt23_6_andRequireBabelPackages =
9090
);
9191
});
9292

93-
it("should work with ts-node", async () => {
94-
const service = require("ts-node").register({
95-
experimentalResolver: true,
96-
compilerOptions: {
97-
module: "CommonJS",
98-
},
99-
});
100-
service.enabled(true);
101-
102-
try {
103-
require(
104-
path.join(
105-
__dirname,
106-
"fixtures/config-ts/simple-cts-with-ts-node/babel.config.cts",
107-
),
108-
);
109-
110-
const config = loadPartialConfigSync({
111-
configFile: path.join(
112-
__dirname,
113-
"fixtures/config-ts/simple-cts-with-ts-node/babel.config.cts",
114-
),
93+
nodeLt23_6("should search .ts config file and throw", () => {
94+
expect(() => {
95+
loadPartialConfigSync({
96+
root: path.join(__dirname, "fixtures/config-ts/simple-ts-cjs"),
11597
});
98+
}).toThrow(
99+
/You are using a .ts config file, but Babel only supports transpiling .cts configs/,
100+
);
101+
});
116102

117-
expect(config.options.targets).toMatchInlineSnapshot(`
103+
nodeGte23_6("should search for .cts config files", () => {
104+
const config = loadPartialConfigSync({
105+
root: path.join(__dirname, "fixtures/config-ts/simple-cts-no-modules"),
106+
});
107+
108+
expect(config.options.targets).toMatchInlineSnapshot(`
118109
Object {
119110
"node": "12.0.0",
120111
}
121112
`);
122113

123-
expect(config.options.sourceRoot).toMatchInlineSnapshot(`"/a/b"`);
124-
} finally {
125-
service.enabled(false);
126-
}
114+
expect(config.options.sourceRoot).toMatchInlineSnapshot(`"/a/b"`);
127115
});
128116

129117
nodeGte23_6("should support .cts when available natively", () => {
@@ -154,6 +142,20 @@ const nodeLt23_6_andRequireBabelPackages =
154142
}).toThrow(/import equals declaration is not supported in strip-only mode/);
155143
});
156144

145+
nodeGte23_6("should search for .ts config files", () => {
146+
const config = loadPartialConfigSync({
147+
root: path.join(__dirname, "fixtures/config-ts/simple-ts-cjs"),
148+
});
149+
150+
expect(config.options.targets).toMatchInlineSnapshot(`
151+
Object {
152+
"node": "12.0.0",
153+
}
154+
`);
155+
156+
expect(config.options.sourceRoot).toMatchInlineSnapshot(`"/a/b"`);
157+
});
158+
157159
nodeGte23_6(
158160
"should use native TS support for .ts (cjs) when available",
159161
() => {
@@ -193,4 +195,45 @@ const nodeLt23_6_andRequireBabelPackages =
193195
expect(config.options.sourceRoot).toMatchInlineSnapshot(`"/a/b"`);
194196
},
195197
);
198+
199+
nodeLt23_6("should search .mts config file and throw", () => {
200+
expect(() => {
201+
loadPartialConfigSync({
202+
root: path.join(__dirname, "fixtures/config-ts/simple-mts-modules"),
203+
});
204+
}).toThrow(
205+
/You are using a .mts config file, but Babel only supports transpiling .cts configs/,
206+
);
207+
});
208+
209+
nodeGte23_6("should use native TS support for .mts when available", () => {
210+
const config = loadPartialConfigSync({
211+
configFile: path.join(
212+
__dirname,
213+
"fixtures/config-ts/simple-mts-modules/babel.config.mts",
214+
),
215+
});
216+
217+
expect(config.options.targets).toMatchInlineSnapshot(`
218+
Object {
219+
"node": "12.0.0",
220+
}
221+
`);
222+
223+
expect(config.options.sourceRoot).toMatchInlineSnapshot(`"/a/b"`);
224+
});
225+
226+
nodeGte23_6("should search for .mts config files", () => {
227+
const config = loadPartialConfigSync({
228+
root: path.join(__dirname, "fixtures/config-ts/simple-mts-modules"),
229+
});
230+
231+
expect(config.options.targets).toMatchInlineSnapshot(`
232+
Object {
233+
"node": "12.0.0",
234+
}
235+
`);
236+
237+
expect(config.options.sourceRoot).toMatchInlineSnapshot(`"/a/b"`);
238+
});
196239
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const path = require("path");
2+
type config = any;
3+
module.exports = {
4+
targets: "node 12.0.0",
5+
sourceRoot: path.posix.join("/a", "b"),
6+
} as config;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import path from "node:path";
2+
type config = any;
3+
4+
export default {
5+
targets: "node 12.0.0",
6+
sourceRoot: path.posix.join("/a", "b"),
7+
} as config;

0 commit comments

Comments
 (0)