monorepo で共通型定義パッケージを楽に利用する
概要
所属しているプロジェクトではフロントエンドもバックエンドも TypeScript で開発しているのですが、とある理由から最近 yarn workspaces
を使った monorepo へ移行しました。フロントエンドとバックエンドに共通するロジックはなかったのですが、共通する型はあったので型定義場所を作りたくなり、その際に調べた知識と設定の仕方を共有したいと思います。
プロジェクトでは yarn workspaces
を利用していますが、検証時は npm workspaces
でも試したのでどちらの monorepo tool でも問題なく動作するはずです。また、サンプルコード等は説明に必要な最低限のものに省略したのですが、もし必要なコードが抜けている等あれば教えて頂けると助かります。
前提知識
npm パッケージの扱い
- パッケージ名は
package.json
のname
に指定したものが使われる- name に
hoge
と指定したらnode_modules/hoge
にインストールされる - name に
hoge/fuga
と指定したらnode_modules/hoge/fuga
にインストールされる
- name に
- 公開ライブラリの @types 系のパッケージは
DefinitelyTyped
というリポジトリで行われていることが多い- ここ: https://github.com/DefinitelyTyped/DefinitelyTyped
- このリポジトリ自体が特別扱いされているわけではない
-
@types/xxxx
という名前でパッケージを公開する仕組みが整っている感じ
つまり DefinitelyTyped を使わなくても package.json
の name
に @types/hoge
という名前をつけると node_modules/@types/hoge
にインストールされる。
npm/yarn workspaces の仕組み
- ルートとなるディレクトリにある
package.json
の workspaces に指定したものがサブプロジェクトとして認識される - ルートで
npm install
かyarn install
を実行すると workspaces に指定されたものがnode_modules
にインストールされる- 実態はただのシンボリックリンク
- この時のインストール先は前述の npm パッケージのインストール方法に従って行われる
つまり root/types/package.json
の name
に @types/hoge
と指定されていたら root/node_modules/@types/hoge
に root/types
へのシンボリックリンクが作られる。
@types の認識範囲
TypeScript: TSConfig Reference - #typeRoots
TypeScript: Documentation - Module Resolution
上記 2 つのドキュメントに書かれた具体例だけ見ると node_modules/@types
や ../node_modules/@types
などのようにディレクトリを遡りながら node_modules
の中にある @types
だけを認識するっぽく解釈してしまいそうになるけど実際は違う。
By default all visible ”@types” packages are included in your compilation.
tsconfig.json の typeRoots の説明にあるこの最初の一文が正しくて結構広い範囲にある @types
ディレクトリは勝手に認識してくれる。
api-server
├── @types
│ └── index.d.ts
├── src
│ ├── @types
│ │ └── index.d.ts
│ ├── models
│ │ └── index.ts
│ └── index.ts
├── package.json
└── tsconfig.json
試した範囲だと上記のような構成で tsconfig.json
の設定を "include": "**"
にすると api-server/@types
も api-server/src/@types
も認識くれました。
設定方法
ここまでの前提知識を踏まえ、以下のようなよくありそうな client/server の monorepo 構成で types に共通の型定義を行いたい場合の例です。
project-root
├── api-server
│ ├── src
│ │ ├── @types
│ │ │ └── index.d.ts
│ │ └── index.ts
│ ├── package.json
│ └── tsconfig.json
│
├── web-client
│ ├── src
│ │ ├── @types
│ │ │ └── index.d.ts
│ │ └── index.ts
│ ├── package.json
│ └── tsconfig.json
│
├── types
│ ├── src
│ │ └── index.d.ts
│ ├── package.json
│ └── tsconfig.json
│
└── package.json
リポジトリルートの package.json
に workspaces の設定を行う
// repository-root/package.json
{
"name": "hoge",
"private": true,
"workspaces": [
"types",
"api-server",
"web-client"
]
}
types/package.json のパッケージ名を @types/xxxxx
にする
// repository-root/types/package.json
{
"name": "@types/hoge",
"types": "src/index.d.ts",
"private": true
}
リポジトリルートで npm install
または yarn install
すれば、リポジトリルートの node_modules/@types/hoge
にシンボリックリンクが作られて認識してくれるようになります。
これだけで types に共通の型定義、api-server と web-client の src/@types にそれぞれ必要な型定義をすれば import なしで認識してくれます。
おまけ
// repository-root/types/tsconfig.json
{
"compilerOptions": {
"target": "es2021",
"module": "commonjs",
"rootDir": "./src",
"noEmit": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noUnusedLocals": false, //未使用チェックをしない
"noFallthroughCasesInSwitch": true,
"skipLibCheck": false //.d.tsの型チェックを有効化
},
"include": [ "src" ]
}
最後におまけ程度ですが types の tsconfig.json です。
プロダクトコード側だと tsc --init
で生成されるデフォルトの skipLibCheck: true
にする場合が多いと思いますが、ここでは型定義しかしないので .d.ts
ファイルでも型チェックが行われるようにしています。
Discussion