#Can .ts files use CJS (not an interoperability question)?

16 messages · Page 1 of 1 (latest)

mint cloak
#

Hello friends, I have a vanilla Node.js project using cjs. I'm trying to gradually and slowly migrate the project to ts file by file. Since everything is currently in CJS, and ts compiles to CJS, I'm hoping I can just write CJS in my .ts files.
I tried doing that, and it compiles correctly to CJS, but I'm not getting any type check nor type hints (I'm using VS Code) when I require one ts file from another ts file.

Here's an example:

// myError.ts
module.exports = class MyError extends Error {
  // ...
}

// main.ts
const MyError = require("./myError"); 

My expected behavior: MyError would have its constructor properly typed when invoked from main.ts
My observed behavior: MyError has type any and its constructor is not type checked.

const GetStoreError: any

Just to be super super clear:

  1. This is not a ESM <> CJS interoperability question - I'm doing everything in CJS.
  2. This is not a requiring ts code from js (or vice versa) question - although I'm running ts complication in a mixed repo of ts & js file, my question is specifically about requiring ts file from another ts file.

Some more info about my stack:

  • Node version 20.13.1
  • typescript version 5.4.5

Some potentially relevant tsconfig settings:

"target": "ES2022",
"lib": ["ES2023"],
"module": "Node16",
"moduleResolution": "Node16",
"allowJs": true,
"checkJs": false,
"noEmit": false,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,

I'm not using any build tool aside from tsc. My package.json does not define type property, and so node infers everything as cjs.
tsc output files are in cjs (as expected) and look correct.

Now, if I change the require and module.exports to ESM syntax though, the problem goes away. But I want to avoid mixing ESM with CJS in my repo.
Am I crazy and CJS just doesn't work with type in ts? Or am I missing something obvious here. Bangging my head against a wall here so any help is super appreciated 🙇‍♂️

candid yoke
#

import in TypeScript are ESM only

#

however, you can tell TS to emit JS code using CJS imports

main ridge
#

you always use esm-like imports and exports

candid yoke
#

note you can still use the require function in TS code, to have dynamic imports
tho it requires to have @types/node installed, so TS is aware of the function

main ridge
#

there is specific syntax that aligns more with cjs' model, but you would still be using import and export

#

you could use import MyError = require("./myError"), for example, iirc

candid yoke
#

there is also that old import foo = require("") syntax availabel in some rare cases iic

candid yoke
main ridge
#

afaik, import = is only recommended if you have verbatimModuleSyntax enabled.

main ridge
candid yoke
#

rule of thumb would be to always use ESM-like import in TS
except when needing to import CJS-only modules, or needing dynamic CJS imports

mint cloak
#

Wow appreciate the quick response guys.

Super helpful. Thanks for saving my ass here. I was going crazy cuz I thought ts being a superset of js, means it should support CJS syntax just fine. Apparently not!

candid yoke
#

problem comes from require being dynamic
also CJS makes it hard to guess what's exactly exported
so opted to use ESM in TS
it was a councious decision

main ridge