Search code examples
node.jstypescriptts-node

Can code files with a .cts extension in Node.js use ESM module syntax for imports and exports?


  1. I created a new Node.js project with Node version 18.15.0. In the package.json file, I added the field "type": "module":
{
  "name": "ts-node-test",
  "version": "1.0.0",
  "description": "",
  "type": "module",
  "scripts": {
    "test": "cross-env NODE_OPTIONS=\"--loader ts-node/esm\" node index.cts"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "cross-env": "^7.0.3",
    "ts-node": "^10.9.2",
    "tslib": "^2.6.2",
    "typescript": "^5.4.5"
  },
  "volta": {
    "node": "18.15.0"
  }
}
  1. The config of tsconfig.json is:
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "ts-node": {
    "transpileOnly": true,
    "files": true,
    "experimentalResolver":true,
    "esm": true,
  },
  "compilerOptions": {
    "outDir": "./dist",
    "allowJs": true,
    "allowSyntheticDefaultImports": true,
    "baseUrl": "./",
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "forceConsistentCasingInFileNames": true,
    "importHelpers": true,
    "lib": [
      "DOM",
      "ESNext"
    ],
    "module": "ESNext",
    "moduleDetection": "force",
    "moduleResolution": "node",
    "noEmitOnError": true,
    "resolveJsonModule": true,
    "skipLibCheck": true,
    "sourceMap": true,
    "strict": true,
    "target": "esnext",
    "typeRoots": [
      "./node_modules/@types",
    ]
  },
  "include": [
    "index.cts",
    "module.cts"
  ]
}
  1. I created a new file named index.cts with the following code:
import test from './module.cjs'

console.log(test(1, 2))
  1. In the same folder, the code of the file module.cts is:
export default (a: number, b: number) => a + b
  1. Afterwards, I executed the script "test", which produced the result without any errors.
3

It seems that .cts extension in Node.js can use ESM module syntax for imports and exports. Why?

  1. The complete project information has been uploaded to GitHub. The GitHub repository can be found at: https://github.com/yaotusa/ts-node-test

Solution

  • files with an .mts extension can only use ESM (ECMAScript Module) syntax for imports and exports, while files with a .cts extension can only use CommonJS syntax for imports and exports.

    That is indeed true but for .mjs and .cjs files respectively, i.e. after TypeScript compilation.

    Within a TypeScript file (including .cts), you can always use ESM import/export syntax. But the TypeScript compiler will transpile the syntax for you, according to the file extension, TSConfig module value and package.json > type value. More details in TS docs > Modules Reference > Module format detection

    In your case, module.cts file extension should make export default syntax being transpiled into exports.default or similar. You can check by inspecting the emitted JS file.

    Similarly, index.cts file extension should make import syntax being transpiled into require() (possibly wrapped by a custom function to handle default import).

    Hence after TS transpilation, your emitted .cjs file correctly use CommonJS require and exports syntax only.