🍱

Flutter モバイルとFlutter Webのモノレポ移行

に公開

モチベーション

弊社はFlutterでiOSとAndroidのモバイルアプリを開発しています。プロジェクトが進む中で、Flutter Webを使ったモバイルWeb版の開発も始めることになりました。
当初は別々のリポジトリで管理していましたが、以下の課題が発生しました。

  • 共通のビジネスロジックやUIコンポーネントの二重管理
  • 機能追加や修正を両方のリポジトリに反映する手間

これらの課題を解決するため、モノレポ構成への移行を決定しました。
モノレポにすることで問題が解決し、以下の利点も享受できると考えました。

  • 共通コードをcoreパッケージとして切り出し、再利用可能になる
  • 変更の一元管理により、保守性が向上する
  • モバイルWebも保守できる開発メンバーが増える

特に重要だったのは、既存のGit履歴を保持したまま移行することでした。過去の開発経緯や変更理由を失わずに、モノレポ構成に移行することを目指して移行作業を進めました。

モノレポ移行

実際に行ったモノレポの移行作業について説明します。

導入ツール

モノレポ管理とモノレポ移行にそれぞれツールを導入しました。

melos

https://melos.invertase.dev/~melos-latest

モノレポの管理ツールとしてmelosを導入しました。
melosは、dartの複数プロジェクトを管理するCLIツールです。パッケージ間の依存関係を同期して更新できるmelos bootstrapコマンドが便利です。

tomono

https://github.com/hraban/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ファイルを作ることで、必要なプロジェクトだけを開いて作業できます。
例えば、appcoreプロジェクトが必要でwebプロジェクトが不要な作業をする場合、以下のワークスペースファイルでappcoreプロジェクトだけを開くことができます。

{
  "folders": [
    {
      "name": "Application",
      "path": "app"
    },
    {
      "name": "Core",
      "path": "core"
    }
  ]
}

プロジェクト構成とCI/CD

coreプロジェクトには、共通するコードをまとめています。例えばAPI呼び出しに関するコードやデザインシステムにあるUIに関するコードなどを共通化しています。

このcoreプロジェクトは、pubspec.yamlからパッケージとして簡単に追加できます。

dependencies:
  core:
    path: ../core

appwebの両方が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つのリポジトリにまとめる構成が有名だと思います。
今回のような、同じ技術スタックで複数パッケージをまとめるモノレポもおすすめです。

参考文献

jig.jp Engineers' Blog

Discussion