ts-node icon indicating copy to clipboard operation
ts-node copied to clipboard

ts-node cannot run mixed ESM/CJS project

Open m-ronchi opened this issue 1 year ago • 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?:

m-ronchi avatar Mar 08 '24 15:03 m-ronchi

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.

fardolieri avatar Mar 15 '24 09:03 fardolieri

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. 🤷

gmarinov avatar May 16 '24 08:05 gmarinov