Conversation
Loading a `.ts` file at runtime (via `createRequire` or a dynamic `import()` with a runtime path) outside the bundle graph fails when its module style mismatches the package `type` scope: a CommonJS-style `.ts` in a `type: module` scope, or the mirror case in a `type: commonjs` scope. Node's type stripping erases types but never converts module systems, so these load as the wrong format and throw. Add a per-project `runtimeTsTransform` option (default `true`) that installs a Node `module.registerHooks` sync load hook in the test and globalSetup workers. The hook intervenes only on the two mismatch cases, transforming with the SWC that already ships inside Rspack (`rspack.experiments.swc`), so there is no new dependency and no user configuration. The native binding is loaded lazily, only on an actual mismatch hit. All other files keep Node-native semantics, and a third-party `.ts` loader (ts-node/@swc-node/tsx) takes precedence. The feature self-gates on Node versions with the sync-hook reentrancy fix (nodejs/node#59929: >= 22.22.3 / 24.11.1 / 25.1.0 / 26.0.0) and is silently inactive below them, where the documented manual workaround still applies. Node executor only; the browser wire strips it.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 15236a8ed9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| export const looksLikeEsm = (source: string): boolean => { | ||
| const stripped = stripComments(source); | ||
| return ESM_SYNTAX_RE.test(stripped) && !CJS_MARKER_RE.test(stripped); |
There was a problem hiding this comment.
Allow ESM transforms when modules call require()
In a type: commonjs scope, an ESM-style .ts file can still legitimately call a local require created with createRequire. Because looksLikeEsm rejects any source containing require(, the hook leaves that file as commonjs-typescript instead of transforming it, so Node parses it as CommonJS and fails on the import/export declarations. This breaks the documented mirror case for runtime-loaded ESM TypeScript modules that need to load a CJS dependency.
Useful? React with 👍 / 👎.
Summary
Closes #1577.
Loading a
.tsfile at runtime — viacreateRequireor a dynamicimport()with a runtime path, outside the bundle graph — fails when the file's module style mismatches its packagetypescope. A CommonJS-style.ts(module.exports = ...) in a"type": "module"scope loads as ESM and throwsmodule is not defined; the mirror case (an ESM-style.tsin a"type": "commonjs"scope) fails too. Node's type stripping erases types but never converts module systems, so today the docs tell users to manually registerts-node/@swc-node.This PR builds the fix in. A new per-project config option
runtimeTsTransform(defaulttrue) installs a Nodemodule.registerHookssync load hook in the test and globalSetup workers that intervenes only on those two mismatch cases, transforming with the SWC that already ships inside Rspack (rspack.experiments.swc) — no new dependency, no user configuration..tsloader (ts-node/@swc-node/tsx) takes precedence and the hook passes through. Onlyfile:URLs,.tsextension,node_modulesexcluded.>= 22.22.3/24.11.1/25.1.0/26.0.0) and is silently inactive below, where the documented manual workaround still applies. SetruntimeTsTransform: falseto opt out entirely.paths, no decorators metadata, no.tsx/.mts/.cts); hook-loaded files stay invisible to watch-mode re-runs andrstest.mock, same as today.Related Links
Checklist