`.cts` files with `import`/`export` fail differently depending on how they are loaded
Maintainers usually reply within 1 day
A pull request for this has already been merged.
- #815 by @Brooooooklyn — merged
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 35/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- node.js, typescript
Research direction
Start by reading the .cts loading paths and load_commonjs_esm, then inspect the CommonJS transform behavior described in the issue; no file paths or tests are named. Compare entry-point, ESM import, and require() loading against the provided reproduction. Done means these paths handle .cts files with import/export consistently, either by compiling to CommonJS or by applying one consistent alternative.
Written by the indexing model from the issue text.
Description
A .cts file that uses import/export fails in three different ways depending on how it is loaded, and loads fine in one case. TypeScript (module: nodenext) and tsx compile such a file to CommonJS, which is common in TypeScript projects, for example for config files.
Reproduction
// package.json
{ "type": "module" }
// both.cts
class Counter {
count = 1;
}
export function describe(): number {
return new Counter().count;
}
console.log('both.cts evaluated, require:', typeof require);
// import.ts
import { describe } from './both.cts';
console.log('import ->', describe());
// require.ts
import { createRequire } from 'node:module';
console.log('require ->', createRequire(import.meta.url)('./both.cts').describe());
Results on Node.js 24.21 (22.18 and 26.5 behave the same):
| Command | @oxc-node/core 0.1.5 |
tsx 4.23.15 | Node.js type stripping |
|---|---|---|---|
both.cts as the entry point |
ERR_REQUIRE_CYCLE_MODULE |
runs, require: function |
SyntaxError: Unexpected token 'export' |
import.ts |
does not provide an export named 'describe' |
import -> 1 |
does not provide an export named 'describe' |
require.ts |
ReferenceError: require is not defined in ES module scope (from the helper require()) |
require -> 1 |
SyntaxError: Unexpected token 'export' |
require('./both.cts') from a .cjs file in a CommonJS package |
runs, require: undefined |
runs, require: function |
SyntaxError: Unexpected token 'export' |
In the last row oxc-node runs the file as an ES module, so require, module, and __dirname are undefined inside it.
Why
.cts is CommonJS by contract here: load_commonjs_esm only flips ambiguous extensions to ES modules, and the CommonJS transform emits require() for helpers but leaves import/export in place, since oxc has no ESM-to-CommonJS module transform. Node.js then sees ESM syntax in a CommonJS file and takes a different path depending on the caller.
Possible directions
- Match tsc and tsx: compile the module syntax to CommonJS. This needs an ESM-to-CommonJS module transform in oxc.
- Until then, either run such a file as an ES module consistently on every path (named exports, entry points, and
require()would then work, with no CommonJS globals inside), or reject it with one clear error, as Node.js does.
Vite+ currently takes the first half of option 2 in its vendored copy: a .cts with module syntax runs as an ES module on both the synchronous and asynchronous hook paths, and a .cts without module syntax stays CommonJS.
- Dominant language
- TypeScript
- Stars
- 275
- Forks
- 10
- Avg merge
- 5h 31m
- Merged PRs (30d)
- 68
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from oxc-project/oxc-node
-
Clarify module resolution with .ts files using import and package.json type commonjsPossibly taken @darcyrush claimed this 1 day ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
oxc-project/oxc-node#905 ·
Maintainers usually reply within 1 day
-
.ts files whose only imports and exports are type-only run as ES modules, without require or modulePossibly taken @darcyrush claimed this 1 day ago. Open
Difficulty 4/5 3-5 days Newbie friendliness 35/100
oxc-project/oxc-node#909 ·
Maintainers usually reply within 1 day
-
.ts files run as ES modules under tsconfig module: node16/node18/nodenext, ignoring package.json "type"Possibly taken @darcyrush claimed this 1 day ago. Open
Difficulty 3/5 1-2 days Newbie friendliness 25/100
oxc-project/oxc-node#907 ·
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 53/100
oxc-project/oxc-node#906 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
oxc-project/oxc-node#891 · 1 comment ·
Maintainers usually reply within 1 day
All issues in oxc-project/oxc-node
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
langchain-ai/langchainjs#12157 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
activescott/home-infra-k8s-flux#511 ·
Maintainers usually reply within 1 day
-
community first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Difficulty 1/5 Under an hour Newbie friendliness 72/100
lingdojo/kana-dojo#33753 · 1 comment · 5 reactions ·
Maintainers usually reply within 1 day
-
android:player
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
follow-up needs-triage S3: minor
Difficulty 1/5 1-3 hours Newbie friendliness 85/100
manaflow-ai/cmux#38032 · 1 comment ·
Maintainers usually reply within 1 day