yarn workspacesのモノレポをHerokuにデプロイする
yarn workspacesのモノレポで、Webアプリ・モバイルアプリ・APIを開発しています。このうちAPIをHerokuにデプロイしたので、調べたことや手順をまとめてみます。
記事の要約
- Herokuにモノレポのサブディレクトリをデプロイする場合は、heroku-buildpack-monorepoを使う
- yarn workspaceはロックファイルがプロジェクトルートにあるため、heroku-buildpack-monorepoを使うとパッケージのバージョンが固定できなくなる。そのため、普通のNode.jsのbuildpackを使う。
- Node.jsのbuildpackはyarn installで全ての依存をインストールするため、インストールに時間がかかったり、ビルドサイズが大きくなる。yarn berryにアップグレードすると、yarn workspaces focusを使って一部の依存のみをインストールできる。
Herokuのbuildpackとは何か
buildpackは、アプリケーションコードをslug(ビルドの成果物)に変換するためのスクリプトです。buildpackでは、依存関係の取得やコンパイルなどを行います。
Herokuへのモノレポのデプロイ方法
Railwayのmonorepoの分類が分かりやすかったので、こちらの定義を使用します。
Isolated Monorepo
A repository that contains components that are completely isolated to the directory they are contained in (eg. JS frontend and Python backend)
Shared Monorepo
A repository that contains components that share code or configuration from the root directory (eg. Yarn workspace or Lerna project)
Isolated Monorepoの場合
heroku-buildpack-monorepoを使います。このbuildpackは、環境変数で指定したディレクトリを持ち上げてビルド対象にします。
# 該当の処理
STAGE="$(mktemp -d)"
(
mv "${BUILD_DIR}/${APP_BASE}" "${STAGE}" &&
rm -rf "${BUILD_DIR}"/* &&
mv "${STAGE}/$(basename "$APP_BASE")"/* "${BUILD_DIR}"
)
Shared Monorepoの場合
heroku-buildpack-multi-procfileを使います。このbuildpackは、指定したProcfile
(とそのディレクトリにあるapp.json
)をビルドディレクトリに移動します。
yarn workspacesを使う場合はこのパターンに該当します。しかし、単一のアプリをデプロイするのであればルートにProcfile
を置くだけで問題ありません。そのため、今回はこちらのbuildpackは使いませんでした。
yarn workspacesのモノレポのデプロイ
それでは実際にデプロイしてみます。サンプルとして、client/
とserver/
があるアプリを使います。コードはこちらにあります。
デプロイの詳細は長くなるため省略しますが、次のような手順で進めました。
- Review Appsを使うためにはパイプラインが必要なため、パイプラインを作成する
- パイプラインにproduction用のアプリを作成する
- パイプラインをGitHubに連携する
- パイプラインのReview Appsを有効化する
- app.jsonを書く。Node.jsのbuildpackの設定だけ書きました。
- PRを作成し、それに対応するレビューアプリがデプロイされることを確認する
- PRをマージして、productionのアプリのデプロイを実行する
ビルドサイズを小さくする
上記の方法で無事にデプロイすることができました。しかし、全てのアプリケーションの依存をインストールしているため、インストールに時間がかかったり、ビルドサイズが肥大化してしまいました。
対策として、デプロイするアプリに必要な依存のみをインストールすることが考えられます。yarn berry(yarn >= v2)では、yarn workspaces focus
で、指定したワークスペースの依存のみをインストールできます。
yarn workspaces focus
| Yarn - Package Manager
そのため、yarn berryにアップグレードしてやってみることにしました。yarn berryへのアップグレードは、次の記事を参考にしました。yarn berryへのアップグレードは、PnPを使わない場合はハマりどころは多くない印象です。
yarn workspaces focusで必要なパッケージのみをインストールする
yarn workspaces focus
は、次のように使います。
# @sample/serverの依存のみをインストールする
yarn workspaces focus @sample/server
# @sample/serverの依存(devDependenciesを除く)をインストールする
yarn workspaces focus --production @sample/server
yarn berryには、パッケージのインストール戦略が複数あります。現在のプロジェクトでは次のようにしています。
- nodeLinker: node_modules(node_modulesを使う)
- nmHoistingLimits: workspace(依存の巻き上げはワークスペースまで)
- zero installは不使用(.yarn/cacheはgitにコミットしない)
Herokuがyarn workspaces focusに対応してない
heroku-buildpack-nodejsは、現時点ではyarn workspaces focus
に対応していません[1]。
該当のIssue: Yarn 2: support focusing on a workspace for monorepo
Issueを見た感じ、yarn workspaces focus
を使うためにはフォークする必要がありました。既にフォークを作成している方がいたので、その方を参考にフォークを作りました。
変更点は以下の3つです。
- インストールするとき、指定したワークスペースに対して
yarn workspaces focus
を実行する - devDependenciesを削除するとき、
yarn workspaces focus --production
を実行する - devDependenciesの削除で再度レジストリからのfetchが実行されないように修正(devDependenciesの削除の前に、レジストリのキャッシュがmvされるのが原因。cpに変更して対応。)[2]
app.json
にフォークしたビルドパックを指定し、YARN2_FOCUS_WORKSPACE
環境設定にフォーカス対象を指定するとデプロイできます。
{
"buildpacks": [
{
- "url": "heroku/nodejs"
+ "url": "https://github.com/tekihei2317/heroku-buildpack-nodejs"
}
]
}
現在取り組んでいるプロジェクトでは、この方法でビルドサイズを330MB→70MBに削減できました。
まとめ
yarn workspacesのモノレポのうち、APIをHerokuにデプロイしました。通常のNode.jsのビルドパックを使うと、他のワークスペースの依存をインストールしてしまうため、デプロイに時間がかかったりビルドサイズが大きくなってしまいました。
この問題をyarn workspaces focus
で改善しました。Herokuのbuildpackが未対応だったため、フォークする必要がありました。
フォークをメンテナンスし続けるのは大変そうです。yarn berryには色々なパターンがあるため、その辺りを整理したりテストを書いてからPRを出してみようと思います。
Discussion