Skip to content

Commit d9fa47d

Browse files
Do not try to parse .ts configs as JSON if natively supported (babel#17052)
1 parent 6ad70a2 commit d9fa47d

File tree

10 files changed

+247
-97
lines changed

10 files changed

+247
-97
lines changed

eslint.config.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ export default [
284284
"nodeGte22_12",
285285
"nodeLt22_12",
286286
"nodeLt23_6",
287+
"nodeGte23_6",
287288
"nodeGte12NoESM",
288289
"testFn",
289290
],

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

+2
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,9 @@ function readConfig(
340340
case ".js":
341341
case ".cjs":
342342
case ".mjs":
343+
case ".ts":
343344
case ".cts":
345+
case ".mts":
344346
return readConfigCode(filepath, { envName, caller });
345347
default:
346348
return readConfigJSON5(filepath);

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

+99-74
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,14 @@ const loadMjsFromPath = endHiddenCallStack(async function loadMjsFromPath(
8888
}
8989
});
9090

91-
const SUPPORTED_EXTENSIONS = new Set([".js", ".mjs", ".cjs", ".cts"] as const);
92-
type SetValue<T extends Set<unknown>> = T extends Set<infer U> ? U : never;
91+
const SUPPORTED_EXTENSIONS = {
92+
".js": "unknown",
93+
".mjs": "esm",
94+
".cjs": "cjs",
95+
".ts": "unknown",
96+
".mts": "esm",
97+
".cts": "cjs",
98+
} as const;
9399

94100
const asyncModules = new Set();
95101

@@ -101,15 +107,23 @@ export default function* loadCodeDefault(
101107
): Handler<unknown> {
102108
let async;
103109

104-
let ext = path.extname(filepath);
105-
if (!SUPPORTED_EXTENSIONS.has(ext as any)) ext = ".js";
110+
const ext = path.extname(filepath);
111+
const isTS = ext === ".ts" || ext === ".cts" || ext === ".mts";
106112

107-
const pattern =
108-
`${loader} ${ext}` as `${typeof loader} ${SetValue<typeof SUPPORTED_EXTENSIONS>}`;
113+
const type =
114+
SUPPORTED_EXTENSIONS[
115+
Object.hasOwn(SUPPORTED_EXTENSIONS, ext)
116+
? (ext as keyof typeof SUPPORTED_EXTENSIONS)
117+
: (".js" as const)
118+
];
119+
120+
const pattern = `${loader} ${type}` as const;
109121
switch (pattern) {
110-
case "require .cjs":
111-
case "auto .cjs":
112-
if (process.env.BABEL_8_BREAKING) {
122+
case "require cjs":
123+
case "auto cjs":
124+
if (isTS) {
125+
return ensureTsSupport(filepath, ext, () => loadCjsDefault(filepath));
126+
} else if (process.env.BABEL_8_BREAKING) {
113127
return loadCjsDefault(filepath);
114128
} else {
115129
return loadCjsDefault(
@@ -118,14 +132,13 @@ export default function* loadCodeDefault(
118132
/* fallbackToTranspiledModule */ arguments[2],
119133
);
120134
}
121-
case "require .cts":
122-
case "auto .cts":
123-
return loadCtsDefault(filepath);
124-
case "auto .js":
125-
case "require .js":
126-
case "require .mjs": // Some versions of Node.js support require(esm):
135+
case "auto unknown":
136+
case "require unknown":
137+
case "require esm":
127138
try {
128-
if (process.env.BABEL_8_BREAKING) {
139+
if (isTS) {
140+
return ensureTsSupport(filepath, ext, () => loadCjsDefault(filepath));
141+
} else if (process.env.BABEL_8_BREAKING) {
129142
return loadCjsDefault(filepath);
130143
} else {
131144
return loadCjsDefault(
@@ -151,92 +164,104 @@ export default function* loadCodeDefault(
151164
// fall through: require() failed due to TLA
152165
} else if (
153166
e.code === "ERR_REQUIRE_ESM" ||
154-
(!process.env.BABEL_8_BREAKING && ext === ".mjs")
167+
(!process.env.BABEL_8_BREAKING && type === "esm")
155168
) {
156169
// fall through: require() failed due to ESM
157170
} else {
158171
throw e;
159172
}
160173
}
161174
// fall through: require() failed due to ESM or TLA, try import()
162-
case "auto .mjs":
175+
case "auto esm":
163176
if ((async ??= yield* isAsync())) {
164-
return (yield* waitFor(loadMjsFromPath(filepath))).default;
177+
const promise = isTS
178+
? ensureTsSupport(filepath, ext, () => loadMjsFromPath(filepath))
179+
: loadMjsFromPath(filepath);
180+
181+
return (yield* waitFor(promise)).default;
165182
}
166183
throw new ConfigError(esmError, filepath);
167184
default:
168185
throw new Error("Internal Babel error: unreachable code.");
169186
}
170187
}
171188

172-
function loadCtsDefault(filepath: string) {
173-
const ext = ".cts";
174-
const hasTsSupport = !!(
189+
function ensureTsSupport<T>(
190+
filepath: string,
191+
ext: string,
192+
callback: () => T,
193+
): T {
194+
if (
175195
require.extensions[".ts"] ||
176196
require.extensions[".cts"] ||
177197
require.extensions[".mts"]
178-
);
198+
) {
199+
return callback();
200+
}
179201

180-
let handler: NodeJS.RequireExtensions[""];
202+
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+
);
212+
}
181213

182-
if (!hasTsSupport) {
183-
const opts: InputOptions = {
184-
babelrc: false,
185-
configFile: false,
186-
sourceType: "unambiguous",
187-
sourceMaps: "inline",
188-
sourceFileName: path.basename(filepath),
189-
presets: [
190-
[
191-
getTSPreset(filepath),
192-
{
193-
onlyRemoveTypeImports: true,
194-
optimizeConstEnums: true,
195-
...(process.env.BABEL_8_BREAKING
196-
? {}
197-
: { allowDeclareFields: true }),
198-
},
199-
],
214+
const opts: InputOptions = {
215+
babelrc: false,
216+
configFile: false,
217+
sourceType: "unambiguous",
218+
sourceMaps: "inline",
219+
sourceFileName: path.basename(filepath),
220+
presets: [
221+
[
222+
getTSPreset(filepath),
223+
{
224+
onlyRemoveTypeImports: true,
225+
optimizeConstEnums: true,
226+
...(process.env.BABEL_8_BREAKING ? {} : { allowDeclareFields: true }),
227+
},
200228
],
201-
};
229+
],
230+
};
202231

203-
handler = function (m, filename) {
204-
// If we want to support `.ts`, `.d.ts` must be handled specially.
205-
if (handler && filename.endsWith(ext)) {
206-
try {
207-
// @ts-expect-error Undocumented API
208-
return m._compile(
209-
transformFileSync(filename, {
210-
...opts,
211-
filename,
212-
}).code,
232+
let handler: NodeJS.RequireExtensions[""] = function (m, filename) {
233+
// If we want to support `.ts`, `.d.ts` must be handled specially.
234+
if (handler && filename.endsWith(".cts")) {
235+
try {
236+
// @ts-expect-error Undocumented API
237+
return m._compile(
238+
transformFileSync(filename, {
239+
...opts,
213240
filename,
241+
}).code,
242+
filename,
243+
);
244+
} catch (error) {
245+
// TODO(Babel 8): Add this as an optional peer dependency
246+
// eslint-disable-next-line import/no-extraneous-dependencies
247+
const packageJson = require("@babel/preset-typescript/package.json");
248+
if (semver.lt(packageJson.version, "7.21.4")) {
249+
console.error(
250+
"`.cts` configuration file failed to load, please try to update `@babel/preset-typescript`.",
214251
);
215-
} catch (error) {
216-
if (!hasTsSupport) {
217-
// TODO(Babel 8): Add this as an optional peer dependency
218-
// eslint-disable-next-line import/no-extraneous-dependencies
219-
const packageJson = require("@babel/preset-typescript/package.json");
220-
if (semver.lt(packageJson.version, "7.21.4")) {
221-
console.error(
222-
"`.cts` configuration file failed to load, please try to update `@babel/preset-typescript`.",
223-
);
224-
}
225-
}
226-
throw error;
227252
}
253+
throw error;
228254
}
229-
return require.extensions[".js"](m, filename);
230-
};
231-
require.extensions[ext] = handler;
232-
}
255+
}
256+
return require.extensions[".js"](m, filename);
257+
};
258+
require.extensions[ext] = handler;
259+
233260
try {
234-
return loadCjsDefault(filepath);
261+
return callback();
235262
} finally {
236-
if (!hasTsSupport) {
237-
if (require.extensions[ext] === handler) delete require.extensions[ext];
238-
handler = undefined;
239-
}
263+
if (require.extensions[ext] === handler) delete require.extensions[ext];
264+
handler = undefined;
240265
}
241266
}
242267

0 commit comments

Comments
 (0)