#ts-nodeでESModulesを使う方法

NodeJSのバージョンが20で正常に動作しない場合はバージョン18を使う。 また、ts-nodeの代わりにswc-nodeを使うことができます。

#設定

下記の設定をします。

{
  ...
  "ts-node": {
    "esm": true,
    "require": ["tsconfig-paths/register"]
  },
  ...
}
{
  ...
  "scripts": {
    "execute": "ts-node scripts/index.ts"
  },
  ...
}

Native ECMAScript modules
paths and baseUrl

#tsconfig.jsonの例

{
    "compilerOptions": {
      "target": "ESNext",
      "module": "NodeNext",
      "allowJs": true,
      "strict": true,
      "moduleResolution": "NodeNext",
      "esModuleInterop": true,
      "baseUrl": "./",
      "skipLibCheck": true,
      "typeRoots": ["node_modules/@types"],
      "sourceMap": false
    },
    "ts-node": {
      "esm": true,
      "require": ["tsconfig-paths/register"]
    },
    "include": [
      "./scripts/**/*"
    ]
}

#ESLint

typescript-eslintを使います。設定ファイルの拡張子を.cjsにします。(例: .eslintrc.cjs) これをしない場合、Error [ERR_REQUIRE_ESM]が発生します。

#Jest

@swc/jest、もしくはts-jestを使います。

package.jsonのscripts以下のようにします。

{
    ...
    "scripts": {
        "test": "NODE_OPTIONS=--experimental-vm-modules jest",
    },
    ...
}

モックでのimport処理を以下のように変更します。

変更前

import { readFile } from 'node:fs/promises'
jest.mock('node:fs/promises')
import { createTitle }  from './utils.js'

変更後

import { jest } from '@jest/globals'
jest.unstable_mockModule('node:fs/promises', () => ({ readFile: jest.fn() }))
const { readFile } = await import ('node:fs/promises')
const { createTitle } = await import('./utils.js')

#@swc/jest

jest、@types/jest、@swc/core、@swc/jestをインストールします。 設定ファイルの拡張子を.cjsにします。(例: jest.config.cjs)

#jest.config.cjsの例

module.exports = {
  roots: ['<rootDir>/scripts'],
  testMatch: ['**/*.test.ts'],
  extensionsToTreatAsEsm: ['.ts', '.tsx'],
  moduleNameMapper: {
    '^(\\.{1,2}/.*)\\.js$': '$1',
  },
  transform: {
    '^.+\\.ts$': [
      '@swc/jest',
    ],
  },
}

#ts-jest

jest、@types/jest、ts-jestをインストールします。 設定ファイルはこれを参考にします。 設定ファイルの拡張子を.cjsにします。(例: jest.config.cjs)

#jest.config.cjsの例

module.exports = {
  roots: ['<rootDir>/scripts'],
  testMatch: ['**/*.test.ts'],
  extensionsToTreatAsEsm: ['.ts', '.tsx'],
  moduleNameMapper: {
    '^(\\.{1,2}/.*)\\.js$': '$1',
  },
  transform: {
    '^.+\\.tsx?$': [
      'ts-jest',
      {
        useESM: true,
      },
    ],
  },
}

#VSCode

VSCodeでdebugするには.vscode/launch.jsonruntimeExecutable${workspaceFolder}/node_modules/.bin/ts-nodeをセットします。

#.vscode/launch.jsonの例

{
    "version": "2.0.0",
    "configurations": [
        {
            "name": "ts-node",
            "type": "node",
            "request": "launch",
            "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/ts-node",
            "args": [
                "${workspaceFolder}/scripts/index.ts"
            ]
        }
    ]
}