くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

MelosでFlutter/Dartをモノレポ運用する

Flutter/Dartでもモノレポで運用したいなと思って、
いろいろ調べたときの備忘録(*´ω`*)

DartではMelosというCLIツールを使えばいいっぽい

melos.invertase.dev

FlutterFireFlameなどでも使われている。
FlutterFire CLIなどでおなじみのInvertase社製。

Melosの機能

トップページに書いてある機能はこのあたり

  • ローカルパッケージのリンクとインストール
  • パッケージ間でのコマンドの実行
  • ローカルパッケージやそのdependenciesの表示
  • pub.devへのパッケージ公開
  • versioningとchangelogの生成

Node.jsまわりだと、pnpm/lerna-lite/turborepoといった感じ

ディレクトリ構成

おすすめのディレクトリ構成はこんな感じ。

my_project
├── apps
│   ├── apps_1
│   └── apps_2
├── packages
│   ├── package_1
│   └── package_2
├── melos.yaml   ... melosの設定ファイル
├── pubspec.yaml ... ワークスペースrootのpubspec.yaml
└── README.md

pubspec.yaml

pubspec.yamlはこんな感じ。dartsdk設定のみ。

name: my_project

environment:
  sdk: '>=3.0.0 <4.0.0'

melos.yaml

melos.yamlはこんな感じ。 対象のpackageのパスやコマンドを設定できる。
FVMを使っているので、SDKのパスも設定

name: my_project

# 対象とするpackageの設定
packages:
  - apps/**
  - packages/**
# melos runで実行するコマンドの設定
scripts:
  analyze:
    exec: dart analyze .

# SDKのパス
sdkPath: .fvm/flutter_sdk

設定できる内容はこのあたりに

FVM v3だと.fvm/flutter_sdkがなくなっているらしいけど、
sdkPathがなくても大丈夫っぽい。

初期設定/bootstrap

とりあえず、初期設定コマンドを実行

# global install版(おすすめ)
$ dart pub global activate melos
$ melos bootstrap

# local install版
$ fvm flutter pub add dev:melos
$ fvm flutter pub get
$ fvm dart run melos bootstrap

実行すると、

  • dependenciesのpackageをinstall(pub get)
  • パッケージ内の依存関係を解析してpubspec_overrides.yamlを作成

pubspec_overrides.yamlは開発時はローカルのパスを見るように、
dependenciesを上書きしてくれるやつ

たとえば、こんな感じで書いているのがあると、

# apps/apps_1/pubspec.yaml
dependencies:
  package_1:
    path: ../../packages/package_1

こんな感じのファイルが作成される。

# apps/apps_1/pubspec_overrides.yaml
dependency_overrides:
  package_1:
    path: ../../packages/package_1

このpubspec_overrides.yamlがある状態で、
fvm flutter pub getを実行すると

$ fvm flutter pub get
! package_1 0.0.1 from path ../../packages/package_1 (overridden in ./pubspec_overrides.yaml)

という感じになり、pubspec_overrides.yamlの設定が参照されるようになる。

そのため、apps/apps_1/pubspec.yaml側では、
pathの指定が不要になる。

  # apps/apps_1/pubspec.yaml
  dependencies:
    package_1:
-     path: ../../packages/package_1

shared dependencies

利用するパッケージのバージョンを揃えたいときにも使える。
こんな感じで、Dart/Flutterのバージョンや、
各dependenciesで揃えたいパッケージを記載してから、
fvm dart run melos bootstrapを実行すると、
各パッケージのpubspec.yamlを上書きしてくれる。

# melos.yaml
command:
  bootstrap:
    environment:
      sdk: ">=3.0.0 <4.0.0"
      flutter: ">=3.0.0 <4.0.0"
    dependencies:
      collection: ^1.18.0
    dev_dependencies:
      build_runner: ^2.3.3

各種コマンド

# 長いのでalias
$ alias fvm-melos="fvm dart run melos bootstrap"

list: パッケージの一覧を表示

$ fvm-melos list
$ fvm-melos list -l       # 詳細版(ver/privateなど)
$ fvm-melos list --graph  # 依存関係グラフのjson表記
$ fvm-melos list --cycles # 循環参照のチェック

exec: 各パッケージで同じコマンドを実行

$ fvm-melos exec flutter pub get
$ fvm-melos exec -- "flutter pub get && flutter pub run build_runner build"

run: scriptsのコマンドを実行

melos.yamlのscriptsに定義したコマンドを実行する。
execで定義したコマンドはexecとして実行される。

# melos.yaml
scripts:
  # rootで実行
  hello: echo 'Hello $(dirname $PWD)'
  # 各パッケージで実行
  print:
    exec: echo 'Hello $(dirname $PWD)'

実行はこんな感じ

$ fvm-melos run hello
$ melos run print

ただ、fvm dart run melos run printだと実行できないので、
グローバルインストールしたmelosじゃないとだめっぽい。。

version: version変更/commitlog生成

versionの一括変更などしてくれるコマンド

# 自動バージョン変更
$ fvm-melos version
# 手動での変更
$ fvm-melos version --manual-version=foo:patch
$ fvm-melos version --manual-version=foo:1.0.0

詳しいガイドはこのあたりに。 - Automated Releases

自動でのバージョンアップは、Conventional Commitsが採用されていて、
featfixなどから次のバージョンを選択してくれる。

gitのタグ付け(--git-tag-version)や--preid--prereleaseなどにも対応

melos.yamlcommand/versionの設定もある

publish: pub.devへの公開

$ fvm-melos publish --dry-run
$ fvm-melos publish --no-dry-run

詳しいガイドはこのあたりに - Automated Releases

こちらにも--git-tag-versionのオプションがある

filtering

特定のpackageにのみ対して実行したい場合は、
filteringのオプションを利用すればOK

# マッチしたpackage名のみで実行
$ melos exec --scope="*example*" -- flutter build ios

# マッチしたpackage名以外で実行
$ melos exec --ignore="*internal*" -- flutter build ios

# flutter packageのみ実行
$ melos exec --flutter -- flutter test

# 特定のpackageを利用しているもののみ実行
$ melos exec --depends-on="flutter" --depends-on="firebase_core" -- flutter test

以上!! なんとなくわかった気がする。。(*´ω`*)

参考にしたサイトさま