🗂

masonを使ってFlutterのコード生成を自動化する

2022/06/26に公開

https://www.youtube.com/watch?v=qjA0JFiPMnQ
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へ。といっても初期値から特に変わるところはないのでdefaultpromptの文言とかを適当に変える。

 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 installnpm -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アプリが生成されるようになってる。趣味アプリ程度ならこれをスタート地点にして育てていくのも良さそう。
https://github.com/YuheiNakasaka/init_riverpod_freezed

この他にもflavor周りの設定やdev/staging/productionの切り替えコード、あとはfirebase周りのコードまでやろうと思えば一発生成できるbrickが作れそう。時間があったらやってみようと思う。

Discussion