git submodule + yarn workspace を使ったライブラリ運用

# この記事の対象読者

- モノリポを複数運用することにしたが、一部のコードをライブラリとしてモノリポ間で共有したい（Googleみたいに一つのリポジトリに入れるのは嫌という人）
- 依存しているNPMパッケージにバグがあり、PRを出しつつ直したバージョンを使いたい
- プライベートリポジトリで作っているコードの一部を、ライブラリとしてパブリックなリポジトリで公開したい

と、グダグダ書きましたが要は ↓ のような状況を上手に扱うにはどのようにすればいいかを説明していきます。

![扱いたい状況.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/169861/7b4d3c26-50d9-2e5b-aa4c-fcc71215db3a.png)

## Git Submodule

Git Submoduleは、あるリポジトリの中で別のリポジトリを参照し、管理するためのツールです。

今回なら、モノリポにライブラリのリポジトリを追加し、管理するために使います。

この記事で細かくGit submoduleについて説明するつもりはないので、詳しく知りたい方は[公式ドキュメント](https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E3%83%84%E3%83%BC%E3%83%AB-%E3%82%B5%E3%83%96%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB)を参照してください。

## Yarn Workspace（あるいはnpm workspaces/pnpm workspaces）

Yarn Workspaceは、Yarnを使ってモノリポを運用するためのツールです。

例えば、

- app/package.json
- lib1/package.json
- package.json

というような構造があったときに、appの中のコードで`require('lib1')`などということができるようになります。

こちらも詳しく説明するつもりはないので、[公式ドキュメントへのリンク](https://classic.yarnpkg.com/en/docs/workspaces/)を掲載しておきます。

## 実際にやってみる

今回は、下記のリポジトリを用いて説明していくことにします。

1. submodule （submoduleはライブラリ用のリポジトリで[Rollupのテンプレート](https://github.com/coder-ka/rollup-starter-lib)[^1]を使って用意しました。）
1. monorepo1

[^1]: Rollup公式のTypeScriptのUMDライブラリのテンプレートにava（テストフレームワーク）を私が追加したもの

まずmonorepo1の中でsubmoduleを利用するには、monorepo1のリポジトリで`git submodule add`を実行します。

```bash
git submodule add https://github.com/coder-ka/submodule
```

この操作により、

- submodule フォルダ
- .gitmodules ファイル

が追加されます。

`.gitmodules` の中をのぞくと

```
[submodule "submodule"]
	path = submodule
	url = https://github.com/coder-ka/submodule
```

となっており、"submodule"というリポジトリがsubmoduleとして`submodule/`ディレクトリで管理されているということがわかります。（名前がややこしくてすいません）

ここで、submoduleの中のpackage.jsonの`"name"`キーを`@coder-ka/submodule`に変更しておいてください。（これにより`require('@coder-ka/submodule')`となる）

つぎは、Yarn Workspaceです。

まずリポジトリルートで初期化を行います。

```
yarn init -y
```

そして、生成したpackage.jsonの内容を変更します。

```json
{
  "private": true,
  "workspaces": [
    "app",
    "submodule"
  ]
}
```

このpackage.jsonはWorkspace管理のためのものなので`"private" :true`が必須になっています。（NPMリポジトリで公開しないため）

また、`"workspaces"`にはモノリポ内のリポジトリとして扱うフォルダのパスを配列で指定します。（`packages/*`みたいな指定の仕方もできますが、今回は割愛）

`submodule`は先ほど追加したので存在しますが、`app`フォルダがないので作ります。

```bash
mkdir app

# ついでに初期化
cd app
yarn init -y
```

また、リポジトリ内であればどこでもいいので `yarn` コマンドを実行しておきます。

```bash
yarn
```

これにより、各ワークスペースが依存するパッケージがインストールされ、リポジトリのルートフォルダに`node_modules/`が配置されます。（さらに`node_modules/@coder-ka/submodule/` は `submodule/` へのSymlinkなので、ライブラリへの変更が即座に反映される）

次に `app/index.js` を作成し、コードを書きます。

```javascript:index.js
const howLongTillLunch = require('@coder-ka/submodule')

console.log('it will be lunchtime in ' + howLongTillLunch());
```

そして、今回に限っては使っているテンプレートの都合でRollupによるビルドを行う必要がありますが、これは本質的な内容ではないのであまり気にしないでください。

```bash
# submodule/フォルダで yarn build しているのと同じ
yarn workspace @coder-ka/submodule build
```

これで `app/index.js` が正常に動くようになるので、

```bash
node app/index.js
```

とすれば、`it will be lunchtime in 18 hours` とコンソールに出てきます。

これで無事に別リポジトリで管理しているライブラリを別のモノリポ内で動かすことができました。

# 懸念点と解決策

考えられる懸念点と解決策について書いていきます。

### 複数のモノリポからの依存

複数のモノリポからライブラリのリポジトリが参照される構成になると、ライブラリへの変更によるほかのモノリポに対する影響が気になってきます。

そんな時は、下記のような運用が良いと思われます。

例えば、

- submodule
- monorepo1
- monorepo2

がある場合は、submoduleのリポジトリにmonorepo1/monorepo2のブランチを作成し、それぞれのモジュールの変更はそのブランチに反映させるようにします。

Git Submoduleはデフォルトでは`master`を参照するので、`.gitmodules`に`branch`を追加します。

```
[submodule "submodule"]
	path = submodule
	url = https://github.com/coder-ka/submodule
	branch = monorepo1
```

これで `git submodule update --remote`（サブモジュールの更新） をしたときに`monorepo1`の内容を参照してくれます。

`master` には monorepo1 での運用で動作が安定していることが確認できた後に反映すれば、monorepo2 はそれを取り込むことができます。（もちろん、直接 monorepo1 から取り込むこともできます）

# 終わりに

Git Submodule + Yarn Workspace の組み合わせが有用であることが理解いただけたでしょうか？

今までは、公開や変更の手間などを気にしてライブラリを別リポジトリで作ることを躊躇していた方もいるかもしれませんが、この方法なら無理なくライブラリを運用かつ利用していくことができます。

ライブラリとして公開できる汎用性を持ったコードを作ることは、それにかかわるコミュニティをどんどん便利なものにしていってくれます。

是非この運用法を活用して、生産性向上に役立てていただければと思います。