monorepo (yarn workspace) で tsconfig や .eslintrc をいい感じに管理する
背景
monorepo で TypeScript のプロジェクトを構成するとき、設定を共通化する方法として root ディレクトリに tsconfig.base.json
ファイルを置き "extends": "../../tsconfig.base.json"
のように指定する方法がよく使われます。
ところが先日 Turborepo を導入したついでに Examples (以降、元ネタ) を眺めていたところ tsconfig
や eslint-config-custom
という専用のパッケージを切って管理していたのを見つけました。
この方法をもとに、よりいい感じに管理するために少しアレンジしてみたので紹介します。
tsconfig をいい感じに管理する
tsconfig 専用のパッケージ追加
元ネタでは単に tsconfig
というパッケージ名にしていますが、 npm パッケージではなくプロジェクト内参照であることがわかりやすいように @monorepo/tsconfig
というように prefix をつけました。
この @monorepo
の部分はプロジェクト名などに適宜読み変えてください。
今回は説明を簡単にするために tsconfig/bases を使用します。
tsconfig/bases
の README には "extends": "@tsconfig/node16-strictest/tsconfig.json"
のように書いていますが /tsconfig.json
は省略可能なようです。
{
"name": "@monorepo/tsconfig",
"private": true,
"version": "0.0.0",
"devDependencies": {
"@tsconfig/next": "^1.0.2",
"@tsconfig/node16-strictest": "^1.0.3"
}
}
{
"extends": "@tsconfig/node16-strictest"
}
他のパッケージから参照する
共通の tsconfig を使用したいパッケージ(例えば、backend)の devDependencies
に、作成した @monorepo/tsconfig
への参照を追加します。
{
"name": "backend",
"private": true,
"version": "0.0.0",
"dependencies": {
// ...
},
"devDependencies": {
"@monorepo/tsconfig": "workspace:^",
// ...
}
}
{
"extends": "@monorepo/tsconfig"
}
複数の設定を共通化する
例えば Next.js 用の tsconfig を作り frontend 用のパッケージに適用したい場合は @monorepo/tsconfig
パッケージ内に nextjs/tsconfig.json
を作り、それぞれ以下のように設定します。
{
"extends": "@tsconfig/next"
}
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"dependencies": {
// ...
},
"devDependencies": {
"@monorepo/tsconfig": "workspace:^",
// ...
}
}
{
"extends": "@monorepo/tsconfig/nextjs"
}
.eslintrc もいい感じに管理する
考え方は tsconfig の場合とほぼ同じなので詳しい説明は割愛しますが ESLint の Shareable Configs のドキュメントに従い、パッケージ名は @monorepo/eslint-config
のようにすると良さそうです。
{
"name": "@monorepo/eslint-config",
"private": true,
"version": "0.0.0",
"devDependencies": {
"eslint-config-next": "^12.2.5",
"eslint-config-prettier": "^8.5.0"
}
}
module.exports = {
extends: ["eslint:recommended", "prettier"],
}
{
"name": "backend",
"private": true,
"version": "0.0.0",
"dependencies": {
// ...
},
"devDependencies": {
"@monorepo/eslint-config": "workspace:^",
"@monorepo/tsconfig": "workspace:^",
// ...
}
}
module.exports = {
"extends": ["@monorepo/eslint-config"]
}
この方法のメリット
../../
のような相対パスの指定がなくなりスッキリする
この方法を採用した最初のモチベーションはこれでした。
単に見た目がスッキリするだけではなく、ディレクトリ構造の階層が変わった場合でも書き換える必要がなくなります。
複数の共通設定が管理しやすい
root ディレクトリに tsconfig.base.json
などを置く方法だと、今回のように Next.js 用の設定を作りたいとなった場合に root ディレクトリにファイルがどんどん増えるか、新たにディレクトリを切っていく必要が出てきます。
最初から専用のパッケージを切っておけば、複数の設定があってもきれいに管理できます。
eslint-config-*
などの依存を root の package.json
に追加する必要がなくなる
上でみた例のように tsconfig/bases
や eslint-config-*
の依存は各 @monorepo/tsconfig
や @monorepo/eslint-config
側に追加していくため root の package.json
が肥大化していくのを防ぐことができます。
まとめ
monorepo の設定方法としてバックエンドとフロントエンドを一つのリポジトリにまとめるようなものはたくさん情報があるのですが、私たちのプロジェクトプロジェクトでは複数の Next.js や React Native のアプリケーションを一つのリポジトリにまとめておりそこそこ規模の大きいコードベースになってきたため、ディレクトリ構成なども考え直す必要が出てきました。
そんな中で Turborepo の Examples は参考にすることが多く、例えば今回のコード例でも apps
と packages
を分けているディレクトリ構成も真似してみました。
monorepo で開発している場合は Turborepo を導入していなくても一度目を通してみると発見があるかもしれません。
Discussion