Flutter モバイルとFlutter Webのモノレポ移行
モチベーション
弊社はFlutterでiOSとAndroidのモバイルアプリを開発しています。プロジェクトが進む中で、Flutter Webを使ったモバイルWeb版の開発も始めることになりました。
当初は別々のリポジトリで管理していましたが、以下の課題が発生しました。
- 共通のビジネスロジックやUIコンポーネントの二重管理
- 機能追加や修正を両方のリポジトリに反映する手間
これらの課題を解決するため、モノレポ構成への移行を決定しました。
モノレポにすることで問題が解決し、以下の利点も享受できると考えました。
- 共通コードをcoreパッケージとして切り出し、再利用可能になる
- 変更の一元管理により、保守性が向上する
- モバイルWebも保守できる開発メンバーが増える
特に重要だったのは、既存のGit履歴を保持したまま移行することでした。過去の開発経緯や変更理由を失わずに、モノレポ構成に移行することを目指して移行作業を進めました。
モノレポ移行
実際に行ったモノレポの移行作業について説明します。
導入ツール
モノレポ管理とモノレポ移行にそれぞれツールを導入しました。
melos
モノレポの管理ツールとしてmelosを導入しました。
melosは、dartの複数プロジェクトを管理するCLIツールです。パッケージ間の依存関係を同期して更新できるmelos bootstrap
コマンドが便利です。
tomono
モノレポへの移行ツールとしてtomonoを導入しました。
tomonoは、複数のリポジトリを1つのリポジトリに移行するCLIツールです。
tomonoを使うと、元リポジトリのgit履歴を保持したまま移行できます。
モノレポ移行コマンド
以下は、tomonoを使った移行コマンドです。
事前に、移行先と移行元のリポジトリを用意します。
また、LFSデータは事前に移行する必要があります。LFSデータのダウンロードがタイムアウトしてしまうため、制限を解除しています。
仮置きの名前はtmp_
としています。
# 作業ディレクトリに移動
mkdir tmp_workspace && cd tmp_workspace
# リポジトリ名を設定 (デフォルトはcore)
export MONOREPO_NAME=tmp_app
# LFSを後から取得するように設定
export GIT_LFS_SKIP_SMUDGE=1
# 移行先と移行元のリポジトリをclone
git clone git@tmp_app_monorepo.git
git clone git@tmp_app_mobile.git
git clone git@tmp_app_web.git
# tomonoコマンドをインストール
curl https://raw.githubusercontent.com/hraban/tomono/master/tomono > tomono && chmod 755 tomono
# モバイルアプリをappディレクトリに移行
echo "$PWD/tmp_app_mobile app" | ./tomono --continue
# モバイルウェブをwebディレクトリに移行
echo "$PWD/tmp_app_web web" | ./tomono --continue
# モノレポディレクトリに移行
cd tmp_app_monorepo
# LFSデータコピーのため、一時的にアプリのリポジトリを登録
git remote add origin-flutter git@tmp_app_mobile
# appディレクトリのLFSを取得
git lfs fetch --all origin-flutter
# モバイルアプリのリモートリポジトリを解除
git remote rm origin-flutter
# LFSのタイムアウトを無効にする
git config lfs.activitytimeout 0
# モノレポにしたリポジトリをpush
git push origin main
移行期間に生じた元リポジトリの変更を取り込む
元リポジトリへの更新を移行先リポジトリにも反映したい場合、以下の手順で反映できます。
移行コマンドと同様に、仮置きの名前はtmp_
としています。
# appの変更をローカルに取り込む
cd tmp_app_mobile
git pull
# モノレポで変更を取り込む
cd ../tmp_app_monorepo
git checkout main
# remote:appとして登録したため、"app"の履歴をマージ
git merge -X subtree=app/ app/main
# ブランチを作成して、remote:originにpush
git switch -c update-app
git push
Flutter モノレポ構成
移行したFlutterプロジェクトのモノレポ構成について説明します。
フォルダ構成とワークスペース
モバイルアプリのプロジェクトはapp
、モバイルWebのプロジェクトはweb
、共通するコードはcore
とフォルダを分けました。
.
├── app
├── core
├── web
└── README.md
Visual Studio Codeでは、.code-workspace
ファイルを作ることで、必要なプロジェクトだけを開いて作業できます。
例えば、app
とcore
プロジェクトが必要でweb
プロジェクトが不要な作業をする場合、以下のワークスペースファイルでapp
とcore
プロジェクトだけを開くことができます。
{
"folders": [
{
"name": "Application",
"path": "app"
},
{
"name": "Core",
"path": "core"
}
]
}
プロジェクト構成とCI/CD
core
プロジェクトには、共通するコードをまとめています。例えばAPI呼び出しに関するコードやデザインシステムにあるUIに関するコードなどを共通化しています。
このcore
プロジェクトは、pubspec.yaml
からパッケージとして簡単に追加できます。
dependencies:
core:
path: ../core
app
とweb
の両方がcore
パッケージに依存する構成にしました。coreの変更が両プロジェクトに影響を与えます。そのため、coreパッケージの変更は、CI/CDでappとweb両方のテストを実行することで、影響範囲を早期に検知できるようにしています。
このCI/CDについても、webの変更だけの場合はwebとcoreのジョブだけを実行し、appのジョブを実行しないなど、必要なジョブだけ実行されるよう工夫しています。
パッケージごとのビルド構成
iOS、Android、モバイルWebそれぞれに対応したビルド構成を構築しました。
Visual Studio Codeでは、launch.json
にデバッグビルド構成を設定できます。
起動するディレクトリとファイルをそれぞれ指定することで、パッケージごとのビルド構成を設定しています。
以下は、launch.json
を抜粋したファイルです。ディレクトリはcwd
、起動ファイルはprogram
で指定しています。
...
"configurations": [
{
"name": "iOS",
"request": "launch",
"type": "dart",
"cwd": "app",
"program": "lib/main.dart",
},
...
{
"name": "Web",
"request": "launch",
"type": "dart",
"cwd": "web",
"program": "lib/main.dart",
},
]
...
まとめ
FlutterのモバイルアプリとモバイルWebのプロジェクトを、モノレポ構成に移行して共通コードをcoreパッケージにしました。
tomonoを使うことで、既存のGit履歴を保持したまま移行でき、過去の開発経緯を失わずにモノレポ化を実現できました。
CI/CDやプロジェクト構成を工夫することで、効率を損なわずに開発できるようにしました。
終わりに
モノレポに移行してから1年が経った記念に、移行の手順とプロジェクト構成をまとめました。
モノレポになって開発しやすくなったとの開発メンバーの声も多く、モノレポ移行は成功だったと考えています。
モノレポというと異なる技術スタックを1つのリポジトリにまとめる構成が有名だと思います。
今回のような、同じ技術スタックで複数パッケージをまとめるモノレポもおすすめです。
Discussion