テクノロジー
モノレポのためのツールTurborepoを触ってみた
モノレポとは
複数のアプリケーションを同一のリポジトリで運用する構成のことを、Monorepo (以下モノレポ)と言います。
Turborepo
Turborepoはこのモノレポ環境のうち、JavaScript系のモノレポ環境を対象としたビルドツールです。
昨今のJavaScript周りの発展は目覚ましく、フロントサイドとサーバーサイドどちらもJavaScriptなんて構成は珍しくありません。Turborepoもそうした流れで生まれたツールの一つで、JavaScriptのモノレポをよりシンプルに扱えるようにしてくれます。
導入
node.jsがインストールされている環境で、以下のコマンドを実行してください。
npx create-turbo@latest
実行すると、プロジェクト名と使用するパッケージマネージャを聞かれると同時に、次のようなサンプルの環境が作成されます。
├── README.md
├── apps
│ ├── docs
│ │ ├── README.md
│ │ ├── next-env.d.ts
│ │ ├── next.config.js
│ │ ├── package.json
│ │ ├── pages
│ │ └── tsconfig.json
│ └── web
│ ├── README.md
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ └── tsconfig.json
├── package-lock.json
├── package.json
├── packages
│ ├── eslint-config-acme
│ │ ├── index.js
│ │ └── package.json
│ ├── tsconfig
│ │ ├── README.md
│ │ ├── base.json
│ │ ├── nextjs.json
│ │ ├── package.json
│ │ └── react-library.json
│ └── ui
│ ├── Button.tsx
│ ├── index.tsx
│ ├── package.json
│ └── tsconfig.json
└── turbo.json
build cache
Turborepoの利便性を語るうえで欠かせないのが、ビルドにキャッシュを効かせることができる点です。
作成された環境には、appとpackageというディレクトリが存在しており、appの中にはアプリ本体と思われるapp/webとそのドキュメントapp/docsが存在しています。
この状態でビルドコマンドとして以下のコマンドを実行すると、turbo.jsonに記載された内容に従い、Turborepoが配下のディレクトリの中でnpm scriptsとしてbuildを持つものに対し、npm run buildコマンドを実施します。
npm run build
サンプルの場合、app配下にNext.jsで作られたwebとdocsの2つのWebアプリが存在するため、その2つにビルドが走り、ビルド後に以下のような結果が表示されました。
Tasks: 2 successful, 2 total
Cached: 0 cached, 2 total
Time: 23.785s
2つのTaskのうち2つともキャッシュされず、約24sで結果が返されたという内容です。
では、この状況で同じコマンドをもう一度実行してみましょう。
すると、結果が以下のように変わります。
Tasks: 2 successful, 2 total
Cached: 2 cached, 2 total
Time: 16ms >>> FULL TURBO
2つのTaskのうち2つともがキャッシュされ、16msで結果が返されたという内容です。
Turborepoは、コマンド実行時、対象のディレクトリの中に.turbo/を作成し、その中にハッシュを保存、再度同じコマンドが実行された場合にハッシュを比較し、変更がなければ前回の出力内容をそのまま返すことで処理の高速化を行っています。
このサンプルであれば、apps/webとapps/docsの配下に.turbo/を作成する形となります。
これが、Turborepoのビルドキャッシュです。
全体を何度もビルドしなくて済むため、コードが膨大になってもビルド時間の長期化をある程度抑えることができます。
なお、コマンド入力時の操作についてはturbo.jsonに記載することである程度自由に操作できます。
今回はbuildなのでコマンド実行時に必ずキャッシュさせていましたが、させないことも可能です。
また、コマンド実行時に--filterオプションを用いることで、特定のディレクトリのnpm scriptsのみを実行させるということもできます。
ちなみに、現在Beta機能ですが、Vercel経由でリモートキャッシングも可能です。
これを用いることで、チーム全体でキャッシュを使用できます。
npm workspacesを用いたローカルpackageの利用
これは turborepo の機能ではありませんが、turborepoを用いることでこの恩恵にあずかることができるため紹介します。
npmの機能の一環に、npm workspacesというものがあります。
これは簡単に言うとローカルに作成したpackageをnpm管理できる機能で、npmにアップロードしていない自作パッケージをあたかもnpm上にあるパッケージであるかのように使用できる機能です。
モノレポでない場合はあまり使う機会のない機能なのですが、モノレポの場合これは非常に便利な機能となっています。
例えばサンプルの場合、この機能を用いてapps/webとapps/docsのbuttonにpackage/uiのButtonを持ってきています。
フロントエンドであれば、よく使いまわすようなパーツ、アトミックデザインで言うところのatomsに位置するコンポーネントをこのnpm workspacesの管理下に配置しておくことで、より統一感のあるデザインでより高速な開発を実現できます。
設定の共通化
組織全体で何かしらの統一ルールが存在しない場合に起こりがちなのが、設定の非共通化です。
例えばlintの場合、言語が違う中で設定が異なるのは別に問題ないのですが、同じJavaScriptでチームごとにeslintの設定が異なるというのは開発時に不要な問題を起こしてしまうことにつながりかねません。
サンプルのようにnpm workspacesをうまい事活用すれば、同じeslintの設定を複数のディレクトリで採用することができます。
これで、フロントサイドではシングルクォーテーション、サーバーサイドではダブルクォーテーションといった些細なルール違いから逃れ、統一感のあるコーディングが可能となります。
依存関係の可視化
※実行にはGraphvizのインストールが必要です。
モノレポの運用にうまくいかないと、依存関係が複雑化してしまうことがあります。
npm workspaceを使用していると、このパッケージがどのディレクトリで使用されているかわからないので不用意に変更できない、なんてこともあるでしょう。
そんなときのために、Turborepoは依存関係を可視化できます。
例えば、サンプルでbuild時における依存関係を可視化した場合以下のようになります。
npx turbo run build --graph=sample-graph.png
対応する出力形式は、Graphvizの対応する形式なので、pngやjpeg, pdfやhtml, svgやwebpなど有名な形式にはおおむね対応しています。
おわりに
私は個人的にはモノレポ反対派なのですが、Turborepoはかなり便利なツールに感じました。
特に便利なのが、他のパッケージと同じくnpmでインストールできるので、既存のプロジェクトに後から導入する場合でもそれほど苦ではない点です。
jsに限定されるという欠点こそありますが、その場合は別のツールを使えばいいだけですし、フロントエンドだけでも用いるという方針をとっても、それほど大きな問題にはならないように思えます。
もし、管理が大変なことを理由にモノレポを避けているのであれば、ぜひ一度触ってみてください。
※本記事は2022年08月時点の内容です。