masonを使ってFlutterのコード生成を自動化する
masonというDartのコード生成を自動化するツールについての紹介をする。自分はFlutterのPackage of the Weekの動画で紹介されてて知った。masonにはいくつかパッケージがあるが今回主に使うのはmason_cliの方。
mason_cliはテンプレート生成やビルドなどができるCLI。masonではbrick
というテンプレパッケージみたいなものを作って、それを元にコードを自動生成する仕組みになっている。作ったbrickはBrickHubというdockerhubとかnpmみたいな場所に公開したりもできる。
brickの仕組みを応用するとRailsのscaffoldコマンドみたいなものも作ったりできるようになる。
インストール
自分はhomebrewでinstallした。dart pub global activate mason_cli
とやればdartでもinstallできる。
brew tap felangel/mason
brew install mason
こんな感じのコマンドがずらり。このmasonコマンドを使って以降色々やっていく。それぞれの使い方についてはmason_cli | Dart PackageのREADMEが詳しい。
% mason --help [~/src/github.com/YuheiNakasaka/masons]
🧱 mason • lay the foundation!
Usage: mason <command> [arguments]
Global options:
-h, --help Print this usage information.
--version Print the current version.
Available commands:
add Adds a brick from a local or remote source.
bundle Generates a bundle from a brick template.
cache Interact with mason cache.
get Gets all bricks in the nearest mason.yaml.
init Initialize mason in the current directory.
list Lists installed bricks.
login Log into brickhub.dev.
logout Log out of brickhub.dev.
make Generate code using an existing brick template.
new Creates a new brick template.
publish Publish the current brick to brickhub.dev.
remove Removes a brick.
search Search published bricks on brickhub.dev.
unbundle Generates a brick template from a bundle.
update Update mason.
upgrade Upgrade bricks to their latest versions.
自作brick作成の流れ
brickをどこで作ってどこに置いておくのが良いのかよくわからないのだけど、とりあえずは適当なところ(~/dev/mason
みたいな場所)を作成してその中を作業ディレクトリとするのが良さそう。
mkdir ~/dev/mason
cd ~/dev/mason
そしてその中でmason new <テンプレ名>
とやるとbrickを作るための雛形が生成される。例えば今回は例としてよくあるfreezedを使ったstateクラスのファイルを作成してみる。
mason new state_with_freezed
cd state_with_freezed
tree
.
├── CHANGELOG.md
├── LICENSE
├── README.md
├── __brick__
│ └── HELLO.md
└── brick.yaml
__brick__
というのがテンプレートファイルの実体を置く場所。{{name}}
のようなmason独自のテンプレ記法を使って書いたファイルを__brick__
配下に置くと、生成時にその部分が具体的な値と置換されてファイルが生成されるという感じ。
またbrick.yaml
の方には下記のように置換したい変数を定義することができる。この場合はテンプレ生成時にname
を何にするかWhat is your name?
とpromptで聞かれて、そこに入力した値がname
としてHELLO.md
の{{name}}
と置換されるという感じ。何も入力しなければDash
がデフォルト値になる。
vars:
name:
type: string
description: Your name
default: Dash
prompt: What is your name?
それではこれを踏まえて__brick__
配下に{{name}}.dart
というファイルを作成する。中身はこんな感じ。
// ignore: unused_import
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part '{{name}}.freezed.dart';
class {{name.pascalCase()}} with _${{name.pascalCase()}} {
const factory {{name.pascalCase()}}({
required String id,
required String title,
required bool completed,
}) = _{{name.pascalCase()}};
}
よくあるstateクラスである。似たようなコードのスニペットを使っている人は結構いると思う(実際今回の例ならスニペットで十分ではある)。目を引くところとしてはpascalCase()
というやつ。これはmasonの用意してくれているヘルパ関数。他にもcamelCase
やらlowerCase
やらがあって便利。ファイル名の{{name}}
にもpascalCase()
などのヘルパ関数を使ったりできる。詳細はbuilt-in-lambdas。
次に置換されるname
の方の設定をbrick.yaml
へ。といっても初期値から特に変わるところはないのでdefault
とprompt
の文言とかを適当に変える。
vars:
name:
type: string
description: state name
default: todo
prompt: What is the state name?
あとは生成するだけ。
生成方法は2種類ある。
一つはプロジェクト内でのみ使えるようにするパターン。
// 例) mason add state_with_freezed --path /Users/YuheiNakasaka/dev/mason/state_with_freezed
mason add <テンプレ名> --path <テンプレの位置>
もう一つはグローバルで使えるようにするパターン。
// 例) mason add -g state_with_freezed --path /Users/YuheiNakasaka/dev/mason/state_with_freezed
mason add -g <テンプレ名> --path <テンプレの位置>
npm install
とnpm -g install
の違いと似たような感じ。
ちなみに削除するにはremoveすれば良いだけ。
mason remove -g state_with_freezed
作成したbrickを使う
先ほど作成したstate_with_freezed
はグローバルで使えるようにしたのでこれを適当なプロジェクトで使ってみる。
作成したテンプレパッケージを使うにはmason make <brick名>
とやるだけ。実際に使うと下記のような感じになる。
$ mason make state_with_freezed
> ? What is the state name? (todo) sample
✓ Made brick state_with_freezed (0.0s)
✓ Generated 1 file:
/Users/YuheiNakasaka/tmp-project/sample.dart (new)
$ cat sample.dart
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'sample.freezed.dart';
@freezed
class Sample with _$Sample {
const factory Sample({
required String id,
required String title,
required bool completed,
}) = _Sample;
}
まとめ
テンプレートジェネレータのmasonの簡単な使い方について書いた。
普段よく実行する処理(interfaceとimplementとそのテストの複数ファイルの一括生成等)は地味に多いと思うのでコマンド一発で使えるようになるとありがたい。プロジェクト固有で使う処理を行うbrickを作り、プロジェクト内に含めてgit管理してしまえば、チーム共通で使えるscaffoldコマンドが簡単に得られる。スニペットだと個々人での設定だが、プロジェクト内のコマンドになってしまえば生産性を共通化できる。
ちなみに今回は自分用にriverpodとfreezedと静的解析周りのpackageだけ入れたよく使うディレクトリ構成のFlutterプロジェクトを丸ごと生成するbrickも作ってみた。初回コードとしては小さなtodoアプリが生成されるようになってる。趣味アプリ程度ならこれをスタート地点にして育てていくのも良さそう。
この他にもflavor周りの設定やdev/staging/productionの切り替えコード、あとはfirebase周りのコードまでやろうと思えば一発生成できるbrickが作れそう。時間があったらやってみようと思う。
Discussion