New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Support .cts
as configuration file
#15283
Changes from 10 commits
350ba10
40b5f2b
25d0fac
0aae8bd
8459ad7
e395515
8a71976
e90dd47
f768926
b76ec12
b34add1
72bbe28
9f79e04
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,9 @@ import semver from "semver"; | |
import { endHiddenCallStack } from "../../errors/rewrite-stack-trace"; | ||
import ConfigError from "../../errors/config-error"; | ||
|
||
import type { InputOptions } from ".."; | ||
import { transformFileSync } from "../../transform-file"; | ||
|
||
const require = createRequire(import.meta.url); | ||
|
||
let import_: ((specifier: string | URL) => any) | undefined; | ||
|
@@ -23,38 +26,96 @@ export const supportsESM = semver.satisfies( | |
"^12.17 || >=13.2", | ||
); | ||
|
||
export default function* loadCjsOrMjsDefault( | ||
export default function* loadCodeDefault( | ||
filepath: string, | ||
asyncError: string, | ||
// TODO(Babel 8): Remove this | ||
fallbackToTranspiledModule: boolean = false, | ||
): Handler<unknown> { | ||
switch (guessJSModuleType(filepath)) { | ||
case "cjs": | ||
switch (path.extname(filepath)) { | ||
case ".cjs": | ||
return loadCjsDefault(filepath, fallbackToTranspiledModule); | ||
case "unknown": | ||
case ".mjs": | ||
break; | ||
case ".cts": | ||
return loadCtsDefault(filepath); | ||
default: | ||
try { | ||
return loadCjsDefault(filepath, fallbackToTranspiledModule); | ||
} catch (e) { | ||
if (e.code !== "ERR_REQUIRE_ESM") throw e; | ||
} | ||
// fall through | ||
case "mjs": | ||
if (yield* isAsync()) { | ||
return yield* waitFor(loadMjsDefault(filepath)); | ||
} | ||
throw new ConfigError(asyncError, filepath); | ||
} | ||
if (yield* isAsync()) { | ||
return yield* waitFor(loadMjsDefault(filepath)); | ||
} | ||
throw new ConfigError(asyncError, filepath); | ||
} | ||
|
||
function guessJSModuleType(filename: string): "cjs" | "mjs" | "unknown" { | ||
switch (path.extname(filename)) { | ||
case ".cjs": | ||
return "cjs"; | ||
case ".mjs": | ||
return "mjs"; | ||
default: | ||
return "unknown"; | ||
function loadCtsDefault(filepath: string) { | ||
const ext = ".cts"; | ||
const hasTsSupport = !!( | ||
require.extensions[".ts"] || | ||
require.extensions[".cts"] || | ||
require.extensions[".mts"] | ||
Comment on lines
+58
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the plan to eventually support There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I want to support this, but it may be difficult, and the current nodejs support for this is not very complete. |
||
); | ||
|
||
let handler: NodeJS.RequireExtensions[""]; | ||
|
||
if (!hasTsSupport) { | ||
const preset = require("@babel/preset-typescript"); | ||
|
||
if (!preset) { | ||
throw new ConfigError( | ||
"You appear to be using a .cts file as Babel configuration, but the `@babel/preset-typescript` package was not found, please install it!", | ||
filepath, | ||
); | ||
} | ||
|
||
const opts: InputOptions = { | ||
babelrc: false, | ||
configFile: false, | ||
sourceType: "script", | ||
sourceMaps: "inline", | ||
presets: [ | ||
[ | ||
preset, | ||
{ | ||
disallowAmbiguousJSXLike: true, | ||
allExtensions: true, | ||
onlyRemoveTypeImports: true, | ||
optimizeConstEnums: true, | ||
...(process.env.BABEL_8_BREAKING && { | ||
allowDeclareFields: true, | ||
}), | ||
liuxingbaoyu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}, | ||
], | ||
], | ||
}; | ||
|
||
handler = function (m, filename) { | ||
// If we want to support `.ts`, `.d.ts` must be handled specially. | ||
if (handler && filename.endsWith(ext)) { | ||
// @ts-expect-error Undocumented API | ||
return m._compile( | ||
transformFileSync(filename, { | ||
...opts, | ||
filename, | ||
}).code, | ||
filename, | ||
); | ||
} | ||
return require.extensions[".js"](m, filename); | ||
}; | ||
require.extensions[ext] = handler; | ||
} | ||
try { | ||
return endHiddenCallStack(require)(filepath); | ||
} finally { | ||
if (!hasTsSupport) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The user config could register a new handler for TS extensions. We should also check if |
||
if (require.extensions[ext] === handler) delete require.extensions[ext]; | ||
handler = undefined; | ||
} | ||
} | ||
} | ||
|
||
|
@@ -69,8 +130,7 @@ function loadCjsDefault(filepath: string, fallbackToTranspiledModule: boolean) { | |
async function loadMjsDefault(filepath: string) { | ||
if (!import_) { | ||
throw new ConfigError( | ||
"Internal error: Native ECMAScript modules aren't supported" + | ||
" by this platform.\n", | ||
"Internal error: Native ECMAScript modules aren't supported by this platform.\n", | ||
filepath, | ||
); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,9 @@ | ||
import { injcectVirtualStackFrame, expectedError } from "./rewrite-stack-trace"; | ||
import { injectVirtualStackFrame, expectedError } from "./rewrite-stack-trace"; | ||
|
||
export default class ConfigError extends Error { | ||
constructor(message: string, filename?: string) { | ||
super(message); | ||
expectedError(this); | ||
if (filename) injcectVirtualStackFrame(this, filename); | ||
if (filename) injectVirtualStackFrame(this, filename); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is exactly how we should do it, but it's also a potential breaking change 🙁
peerDependenciesMeta
is only supported in npm 7+, and Node.js 14 comes with npm 6 preinstalled. There are also other places where we would needpeerDependenciesMeta
, but they are waiting for Babel 8.Maybe we could
try/catch
requiring the preset without listing it as a peer dependency, even if this means that for now it won't work with PnP? Or we could only support loading throughts-node
until Babel 8.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am a little hesitant about this, because even if the npm used by the user does not support
peerDependenciesMeta
, it should only install an additional plugin, and there will be no other side effects except for a very small hard disk occupation. 😕There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, in old npm versions that don't support
peerDependenciesMeta
@babel/preset-typescript
will not be installed, and instead it will be handled like a required peer dependency.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get it, this causes a warning to show up in some versions of npm, which is really less than ideal, thanks!