TypeScript 3.0 の Project reference(プロジェクト参照) やってみた

TypeScript 3.0 がリリースされました。

追加機能のひとつ、Project references は、ちょうど仕事で「どうするのがいいの?」と迷ってたところだったので、さっそくやってみました。

話としてはよくある、 複数のプロジェクトから参照される "共通プロジェクト" の在り方 です。

Project Reference 適用以前

Project Reference 適用前(つまり現状)は、次のような構成になっていました(説明簡略化のため、client -> shared のみを書いてますが serverside からも shared を参照しています)。

root
├── client
│   ├── tsconfig.json
│   └── src
│       └── main.ts
└── shared
    └── src
        └── calc.ts

shared の calc.ts

export function calcAdd(x: number, y: number): number {
    return x + y;
}

client の main.ts

import { calcAdd } from '../../shared/src/calc';

console.log(calcAdd(1, 2)); // = 3

client の tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true
  }
}

shared/ 配下 は ただのファイル置き場 で、client から相対パスで calc.ts を参照しているに過ぎません。

これを tsc -b client/tsconfig.json した結果は次のようになります。

root
├── dist
│   ├── client
│   │   └── src
│   │       └── main.js
│   └── shared
│       └── src
│           └── calc.js
├── client
│    └─-
└── shared
     └─-

これはイケてないと思いつつ開発してきましたが、これを Project reference に変えてみます。

Project reference 適用後

ではプロジェクト参照を使ってみます。TypeScript Version 3.0.1 で試しています。

まず、 shared/ をプロジェクト化するために tsc --inittsconfig.json を作り、内容を次のようにします。

shared の tsconfig.json

{
  "compilerOptions": {
    // tsc --init で既定で設定されてた項目
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,

    // あとから追加した項目
    "outDir": "../dist/shared",
    "rootDir": "./src",
    "composite": true,
    "declaration": true,
  }
}

"composite": true がプロジェクト参照のために必要な項目で、"declaration": true は、型定義ファイルを出力するために必要です(よね?)。outDirrootDir は出力される .js ファイルの場所を調整するために設定しました。

次に client 側の tsconfig.json を修正します。

client の tsconfig.json

{
  "compilerOptions": {
    // tsc --init で既定で設定されてた項目
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,

    // あとから追加した項目
    "outDir": "../dist/client",
    "rootDir": "./src",

    // さらに追加した相対パス地獄を防ぐための項目
    "baseUrl": "./",
    "paths": {
      "shared/*": [
        "../dist/shared/*"
      ]
    }
  },
  // あとから追加した項目
  "references": [
    { "path": "../shared" }
  ]
}

こちらには、 "references" を追加し、shared への参照を設定します。これがプロジェクト参照のメインですね。

baseUrlpaths は、 "relative path hell" を回避するための設定です。

を見てやってみました。

最後に、client の main.tsimport 文を書き換えます。

client の main.ts

import { calcAdd } from 'shared/calc';

console.log(calcAdd(1, 2)); // = 3

import は、shared プロジェクトのビルド結果である ./dist/shared を参照するようにしますが、 先に baseUrlpathsshared/*../dist/shared/* をマッピングさせているので、ここでは from 'shared/calc' だけで済みます。

ではビルドしてみましょう。

tsc -b client/tsconfig.json

を実行します。ポイントは、shared もプロジェクトなのにそれは含めていない、ということです。

ビルド結果を含むディレクトリ全体は次のようになります。

root
├── client
│   ├── tsconfig.json
│   └── src
│       └── main.ts
├── shared
│   ├── tsconfig.json
│   └── src
│       └── calc.ts
└── dist
    ├── client
    │   └── main.js
    └── shared
        ├── calc.js
        └── calc.d.ts

なんだかそれっぽくなった気がします。 tsc -b client/tsconfig.json としたのに、プロジェクト参照に設定されている shared 側も(先に)ビルドされて dist/shared に出力されています。

client の tsconfig.json には "rootDir": "./src" を設定したので、好き勝手に別の親ディレクトリにある .ts ファイルを参照することができなくなり、秩序が守られる気がします。

冒頭の説明には、もっとたくさんのオプションについて説明がありますが、とりあえず以上です。