nodejs(javascript)で書かれたプロジェクトに、typescriptを導入してjavascript(commonjs)とtypescriptを併用して運用できるようにする手順をまとめました。

段階的にtsに移行していくなどの用途で使えるなと思い、記事にしてみました。

プロジェクトはeslintやprettierなどが適用されていて、nodemonを使って開発をしているとします。

まずは必要なライブラリをインストールします。

1. ライブラリのインストール

typescript関係

$ npm i -D typescript ts-node tslib

node-devを使っている人は ts-node-dev もインストールしなきゃいけないかもしれないです。

$ npm i -D ts-node-dev

ts-nodeも、環境ではグローバルでインストールした方がいいかもしれないです。

$ npm install -g ts-node typescript

eslint関係

$ npm i -D eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser

2. .eslint.jsの編集

parserをtypescriptに。 また、rulesでjavascript(commonjs)と併用できるようにする設定を追記します。

  plugins: ["@typescript-eslint"],
  extends: [
    "eslint:recommended",
    "plugin:prettier/recommended",
    "plugin:@typescript-eslint/recommended",
  ],
  parserOptions: {
    parser: "@typescript-eslint/parser",
    ecmaVersion: 2021,
    sourceType: "module",
  },
  rules: {
    // jsとts併用のため設定
    "@typescript-eslint/no-var-requires": "off",
  }

3. nodemon.jsonの編集

ts-nodeで実行するようにします。

{
  "watch": ["src/*", "*.js"],
  "ext": "ts,js",
  "exec": "ts-node"
} 

4. package.jsonのscriptsの編集

nodeで動かしている部分をts-nodeに変更します。 node-devを使っている人は、合わせてts-node-devに変更します。

  "scripts": {
    "start": "ts-node app.js",
    "watch": "ts-node-dev app.js"
  }

5. tsconfig.jsonの編集

こちらにも、javascriptと併用できるようにするための設定を追記します。 一例ですが、このような感じになります。

{
  "compilerOptions": {
    "target": "es6",
    "module": "CommonJS",
    "strict": true,
    "moduleResolution": "node",
    "skipLibCheck": true,
    "sourceMap": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,

    "types": [
      "node"
    ],
    "lib": [
      "es6"
    ],

    // jsと共存するために必要
    "checkJs": false,
    "allowJs": true,
    "declaration": false,

    // 型チェック
    "noImplicitAny": false,
    "noImplicitReturns": false,
    "noImplicitThis": false,
    "noUnusedLocals": true,
    "strictNullChecks": false,
    "strictFunctionTypes": false,
    "strictBindCallApply": false,
    "strictPropertyInitialization": false
  },

  "ts-node": {
    "transpileOnly": true
  },
  "include": [
    "**/*.js",
    "**/*.json",
    "src/**/*.ts",
    "tests/**/*.ts",
  ],
  "exclude": [
    "node_modules"
  ]
}

動作確認

以下のようなファイルを作成します。 js, ts相互に参照して扱うことができるか試していきます。

.
├── js-module.js
├── js-test.js
├── ts-module.ts
└── ts-test.ts

ts-module.ts

ts-module.ts
export default class TsModule {
  constructor() {
    console.log('TsModule constructor()')
  }

  public readTsModule() {
    console.log('TsModule readTsModule()')
    return 'read ts module text'
  }
}

module.exports.TsModule = TsModule

module.exports.TsModule = TsModule の記載は、tsだけなら必要ないのですが、jsで扱えるようにするには必要となります。

js-module.js

js-module.js
module.exports.readTest = () => {
  console.log('execute js-module.js readTest()')
  return 'read text'
}

ts-test.ts

ts-test.ts
import TsModule from './ts-module'

const { readTest } = require('./js-module')

// tsの読み込みテスト
const tsModule = new TsModule()
tsModule.readTsModule()

// jsの読み込みテスト
console.log(readTest())

js-test.js

js-test.js
const { TsModule } = require('./ts-module')
const { readTest } = require('./js-module')

// tsの読み込みテスト
const tsModule = new TsModule()
console.log(tsModule.readTsModule())

// jsの読み込みテスト
console.log(readTest())

では、実際にコマンドを実行して確認します。

$ ts-node js-test.js
TsModule constructor()
TsModule readTsModule()
read ts module text
execute js-module.js readTest()
read text

$ ts-node ts-test.ts
TsModule constructor()
TsModule readTsModule()
execute js-module.js readTest()
read text

js, ts相互に参照しあえることが確認できました。