🌟

Monorepoについて調べてみた

2023/02/21に公開

最近、サービスを跨いで共通利用したいComponentをどう管理運用するかに関心があり、これをモノリポジトリで解決できないかと思いモノリポジトリについて調べ始めました。

Monorepoとは何であるのか、一緒によく話題に上がるツール(NxやTurborepoなど)はどういう役割のものなのか?
これらについて、調べたことをまとめていこうと思います。

Monorepoとは

単一のコードベースの中に複数のappやpackageが存在していること。
(反対にそれぞれのappやpackageが独立してコードベースで存在しているPolyrepoというものがある。)

npm等のPackage Managerのworkspace機能を利用して、dependenciesを全体で共有したり他workspaceで定義されているコードをdependenciesから参照することができる。

Monorepoの話の中で一緒に度々話題に出てくる、NxTurborepoといったツールに関しては、上記Package Managerのworkspace機能と併用して利用し、Monorepoの開発体験を向上させる立ち位置のツールである。
各ツールに共通して見られた機能としては、各task(package.jsonのscriptsにて定義)の出力のキャッシュ機能等がある。

https://nx.dev/
https://turbo.build/repo

基本設定

ここで雰囲気だけでも掴めるように、必要最低限の設定をnpm workspacesの設定を参考にして記述していく。

  1. まずrootとなるディレクトリを作成し、npm initなどを実行しpackage.jsonを作成する。
  2. 下記のようなディレクトリ構造を作成し、apppackage1package2にそれぞれ同じようにしてpackage.jsonを配置していく。(workspaceとなるディレクトリのpackage.jsonのnameは、他workspaceから呼び出す際に指定する名前になる。)
    ※ルール化されたディレクトリ構造があるわけではないので参考程度に。
    monorepo
    ├─ apps
    │  └─ app
    ├─ packages
    │  ├─ package1
    │  └─ package2
    └─ package.json
    
    {
      "name": "ui",
      "version": "1.0.0"
    }
    
  3. rootディレクトリ直下のpackage.jsonにworkspacesを追加し、対象となるworkspaceを追加する。
    {
      "name": "apps",
      "version": "1.0.0",
      "workspaces": [
        "apps/*",
        "packages/*"
      ]
    }
    
  4. 任意のworkspaceのソースを他のworkspaceから呼び出したい時は、下記のようにpackage.jsonのdependenciesに追加しnpm installを行うことで呼び出すことができるようになる。
    {
      "name": "app",
      "version": "1.0.0",
      "dependencies": {
        "ui": " workspace:*"
      }
    }
    

Package ManagerのMonorepoでの役割

設定の例を挙げたところで次に、npm、yarn、pnpmといったPackage Managerが、Monorepo内でどういう役割を担っているのかまとめていく

workspaceのコードのdependenciesへの追加

例でもあったようにworkspaceに追加されたソースコードは他workspaceにてpackage.jsonのdependenciesに追加することができ、利用することができる。dependenciesに追加されたworkspaceはnode_modules以下でシンボリックリンクとして追加され、呼び出して利用することができるようになる。

{
  "dependencies": {
    "ui": "workspace:*",
  },
}
ui -> ../../../../packages/ui

node_modulesのworkspace全体への共有

rootディレクトリ直下のpakcage.jsonでdependenciesに追加されたものは、全体通して他のworkspaceでも利用することができる。
この機能により、リポジトリ全体で統一したバージョンで利用したいnode_modulesを管理することができる。
逆に各workspace内でのみ利用したいものは、各workspaceのpackage.jsonのdependenciesに記述することができる。

monorepo
├─ apps
│  ├─ app1
│  └─ app2
├─ packages
│  ├─ package1
│  └─ package2
└─ package.json ← ここ

taskの操作

Monorepoでは、それぞれのworkspace定義されたタスクの実行をコントロールすることができる。
つまり、workspaceそれぞれで定義された同コマンドを一括実行したり、workspaceを指定して実行することができる。

  • 一括での実行
    npm run test --workspaces
    
  • workspaceを指定しての実行
    npm run test --workspace=ui
    

Monorepoツールの役割

ここではTurborepoを例に記載する。

Taskのキャッシュ

タスクにより出力された出力物をキャッシュする機能である。
標準出力されたログ、標準出力されたエラー、バンドルファイル等をキャッシュすることができる。
https://turbo.build/repo/docs/core-concepts/caching

マルチタスクによる実行

workspace機能におけるタスクの一括実行を並列で実行する等により効率化することができる。当然全て強制的に並列になるのではなく、設定によりビルドを実行する順番等もコントロールすることができる。

https://turbo.build/repo/docs/core-concepts/monorepos/running-tasks

最後に

今回は、Monorepoとはどういうものでどういうツールを使って基本的な機能が達成されているかに着目し、簡単にまとめました。

実際に運用する上でのLint等の設定については、別の記事にてまた改めて書こうと思います。
最後まで見ていただきありがとうございました。

参考

https://turbo.build/repo/docs/handbook/what-is-a-monorepo
https://docs.npmjs.com/cli/v8/using-npm/workspaces

Discussion