ts-node cannot run mixed ESM/CJS project
m-ronchi opened this issue · 2 comments
Search Terms
ESM CJS mixed project
SyntaxError: Named export not found. The requested module is a CommonJS module, which may not support all module.exports as named exports.
Expected Behavior
ts-node works and prints BAR
Actual Behavior
$ npx tsc && node dist/test.mjs
BAR
$ npx ts-node --esm src/test.mts
TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".mts" for /***/ts-node-bug/src/test.mts
at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:160:9)
at defaultGetFormat (node:internal/modules/esm/get_format:203:36)
at defaultLoad (node:internal/modules/esm/load:143:22)
at async nextLoad (node:internal/modules/esm/hooks:865:22)
at async nextLoad (node:internal/modules/esm/hooks:865:22)
at async Hooks.load (node:internal/modules/esm/hooks:448:20)
at async MessagePort.handleMessage (node:internal/modules/esm/worker:196:18) {
code: 'ERR_UNKNOWN_FILE_EXTENSION'
}
$ node --loader ts-node/esm src/test.mjs
(node:65265) ExperimentalWarning: `--experimental-loader` may be removed in the future; instead use `register()`:
--import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("ts-node/esm", pathToFileURL("./"));'
(Use `node --trace-warnings ...` to show where the warning was created)
file:///****/ts-node-bug/src/test.mts:1
import { foo } from "./lib.js";
^^^
SyntaxError: Named export 'foo' not found. The requested module './lib.js' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from './lib.js';
const { foo } = pkg;
at ModuleJob._instantiate (node:internal/modules/esm/module_job:132:21)
at async ModuleJob.run (node:internal/modules/esm/module_job:214:5)
at async ModuleLoader.import (node:internal/modules/esm/loader:329:24)
at async loadESM (node:internal/process/esm_loader:28:7)
at async handleMainPromise (node:internal/modules/run_main:113:12)
Node.js v20.11.1
$ node --import 'data:text/javascript,import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("ts-node/esm", pathToFileURL("./"));' src/test.mts cluster: prod
file:///***/ts-node-bug/src/test.mts:1
import { foo } from "./lib.js";
^^^
SyntaxError: Named export 'foo' not found. The requested module './lib.js' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from './lib.js';
const { foo } = pkg;
at ModuleJob._instantiate (node:internal/modules/esm/module_job:132:21)
at async ModuleJob.run (node:internal/modules/esm/module_job:214:5)
at async ModuleLoader.import (node:internal/modules/esm/loader:329:24)
at async loadESM (node:internal/process/esm_loader:28:7)
at async handleMainPromise (node:internal/modules/run_main:113:12)
Node.js v20.11.1
Steps to reproduce the problem
run this:
ts-node-bug.zip
Minimal reproduction
lib.ts (inferred as CommonJS module)
export const foo = "BAR"
test.mts (ESM)
import { foo } from "./lib.js";
console.log(foo);
Specifications
ts-node v10.9.2
node v20.11.1
compiler v5.4.2
- tsconfig.json, if you're using one:
{
"compilerOptions": {
"lib": [ "es2023" ],
"module": "node16",
"target": "es2022",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node16",
"noEmit": false,
"outDir": "dist/",
"sourceMap": true,
"strictNullChecks": true,
},
"include": [
"src"
]
}
- package.json:
{
"name": "ts-node-bug",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^20.11.25",
"ts-node": "^10.9.2"
}
}
- Operating system and version: macos
- If Windows, are you using WSL or WSL2?:
I wanted to create a new issue targeting the same problem but then I found this one so I am commenting here. See this stackblitz.
// main.cts
(async () => {
const dep = await import('./dep.mjs');
console.log(dep);
})()
// dep.mts
export const dep = 'dependency';
I expected ts-node main.cts
and tsc && node dist/main.cjs
to behave the same but I get this error
Cannot find module '[..]/dep.mjs' imported from [..]/main.cts
What do I need to do to make this run with ts-node?
Edit: ts-node-esm main.cts
works for me.
It appears this broke in Node 18.19 and versions released since have the issue.
There have been multiple issues tracking facets of it, in multiple repositories (node, typescript, ts-node, esbuild, tsx among others), over the last few months, but no resolution. In #2094 the common strategy is to work around the issue - either downgrade Node to 18.18, or use whatever alternative works in your scenario, for example tsx
(in which the same issue is half-fixed). None of those workarounds represent an actual fix.
The scenario is very simple: it happens in mixed CJS/ESM repositories, using TS in development with tooling that isn't always new, and inevitably some dependencies that don't support a global switch to modules in package.json. Node and some tooling (tsx) defaults to CJS in the absence of type=module
in package.json, and so import
-s are now broken, especially in an .mjs file importing .ts, e.g. import { named } from './tsfile'
(treated as CJS). The CJS-compatible import('./file').then(...)
pattern works in the forced CJS context. Or you could rename your .ts
file to .mts
and hope that the ESM system wakes up.
I'd love if ts-node
had updates about this, but I'm already having to test with alternatives. 🤷