スタディサプリ Product Team Blog

株式会社リクルートが開発するスタディサプリのプロダクトチームのブログです

Flutterアプリ開発に欠かせないbuild_runner速度アップTips

Dartチームがmacrosの開発をストップしたことを受けて、緊急でブログとビルドを回しております。 ForSCHOOL開発グループの @koji-1009 です。

medium.com

macrosの開発停止に伴い、Dartチームはbuild_runnerのパフォーマンス向上とdata classの実現に注力する旨の表明をしています。 これらの機能は、macrosにより代替される想定でした。このため、この発表は単純な開発の停止ではなく、よりDartを安定化させつつ要望の多い機能を実現する発表であると感じています。

とはいえ、build_runnerの改善に期待したいのですが、改善がリリースされるまでには少々時間がかかります。 また、筆者の知る限り、build.yamlの記述には工夫の余地が多くあります。

本稿ではbuild.yamlの効果的な工夫について紹介し、build_runnerを利用した環境の開発体験を向上することを目指します。

検証プロジェクト

本稿では、筆者が開発中のプロダクトでビルド時間短縮の検証を行いました。なおmelosを利用したマルチモジュール構成になっています。 記事執筆時点では、melospub workspaceをサポートしたバージョンが正式リリースされていません。このため、melos.yamlにコマンドを定義しています。

開発中のプロジェクトでは、melos generateコマンドでbuild_runnerが実行されるようにしています。-cオプションをブログ用に1に設定すると、次のスクリプトになります。 モジュール分割の結果、Flutterに依存しないモジュールが作られた際、先行してビルドされるように--no-flutter--flutterを組み合わせたコマンドにしてあります。

  generate:
    steps:
      - melos run generate:dart
      - melos run generate:flutter

  generate:dart:
    run: melos exec \
      -c 1 \
      --order-dependents \
      --depends-on="build_runner" --no-flutter -- \
      dart run build_runner build --delete-conflicting-outputs

  generate:flutter:
    run: melos exec \
      -c 1 \
      --order-dependents \
      --depends-on="build_runner" --flutter -- \
      dart run build_runner build --delete-conflicting-outputs

--order-dependentsは、モジュール間の関係を踏まえた上で、処理を実行するオプションです。複数のモジュールが共通して依存するモジュール、たとえば通信処理をまとめるモジュールなどがある場合には、便利な設定になります。

melos.invertase.dev

パッケージごとの対応

プロジェクトで利用している、主なbuild_runnerを利用するパッケージは、次の通りです。

build_runnerにより、生成されるファイル数は次のとおりです。

  • *.g.dart : 303ファイル
  • *.freezed.dart : 161ファイル
  • *.gen.dart : 4ファイル

多くのプロジェクトで工夫が可能なのは、freezedのオプションと生成ファイルの絞り込み、ビルドの実行順だと思われます。

freezedのオプションを指定する

freezedのオプションによるビルド時間の短縮は、それまで不必要に生成されていたコードを削減することで実現されます。 このため、短縮されるビルド時間は、どれだけfreezedでコードを生成しているかの量に大きく依存します。

このため、本稿では生成されるクラス数生成元のコード行数に依存する問題は扱いません。 生成されるクラスの数が多すぎるケース、またクラスの行数が長すぎる場合には、ファイルの書き込み処理そのものに時間がかかっていることがあります。


freezedには複数のオプションがあります。オプションの一覧ドキュメントページはありませんが、@Freezedに指定できるオプションが設定できます。

pub.dev

まず確認するべきは、map/whenの生成オプションです。

pub.dev

pub.dev

このオプションを駆使することで、map/mapOrNull/maybeMap/when/whenOrNull/maybeWhenメソッドの生成を抑制することができます。 検証プロジェクトでは、Dart 3のswitch式によるパターンマッチングを利用しているため、これらのメソッドの生成を次のオプション指定で抑制しています。

targets:
  $default:
    builders:
      freezed:
        options:
          map: false  # map関連のメソッドを生成しない
          when: false # when関連のメソッドを生成しない

mapwhenは必要だがmapOrNullmaybeWhenなどは不要である、というケースであれば、次のように設定するのが良いでしょう。 生成されるコード量を抑えることができるため、ビルド時間の短縮につながります。

targets:
  $default:
    builders:
      freezed:
        options:
          map:
            map: true
            maybeMap: false
            mapOrNull: false
          when:
            when: true
            maybeWhen: false
            whenOrNull: false

検証プロジェクトにおいては、map/whenの生成を抑制することで数万行規模でコードの生成量を抑えることができました。 ビルド時間も大幅に短縮されたので、ぜひおすすめしたいオプションになります。


@Freezedに含まれない設定として、v2.5.0から追加されたformatがあります。

pub.dev

formatオプションは、生成されたコードにフォーマットをかけるかどうかを指定します。 大抵の場合、生成されたファイルの可読性は問題になりません。そのため、formatオプションをfalseに設定することでビルド時間を短縮できることがあります。

targets:
  $default:
    builders:
      freezed:
        options:
          format: false

検証プロジェクトでformatオプションをfalseに設定してみましたが、ビルド時間の短縮にはほとんど効果が見られませんでした。これはマルチモジュール化していることや、すでにmap/whenの生成を抑制していることが影響している可能性があります。

対象プロジェクトごとに影響度合いが異なると思われるため、ぜひ一度計測してみてはいかがでしょうか。formatオプションを追加するだけなので、簡単に試すことができます。

generate_forで生成対象を絞る

生成対象のファイルを絞ることで、ビルド時間が短縮されます。

github.com

Slow builds are often the result of builders that run on all Dart files in your package, and analyze them. In this case you can speed up your builds by telling those builders exactly which files they should run on.

たとえば、freezedの生成対象がmodelディレクトリに限定されている場合、次のように設定することでビルド時間を短縮できます。

targets:
  $default:
    builders:
      freezed:
        generate_for:
          include:
            - lib/**/model/*.dart

この設定は、次のように広い範囲で設定したとしても、ビルド時間の短縮につながります。 検証プロジェクトでは、各モジュールに次のように広い範囲の設定を追加することで、10秒程度のビルド時間短縮につながりました。

targets:
  $default:
    builders:
      freezed:
        generate_for:
          include:
            - lib/**/*.dart
      json_serializable:
        generate_for:
          include:
            - lib/**/*.dart
      riverpod_generator:
        generate_for:
          include:
            - lib/**/*.dart

まとめ

build_runnerのビルド時間はbuild.yamlの設定によって大きく変わります。 どの設定がどれ程の効果があるかは、プロジェクトごとに異なります。このため、この機会に改めて計測しながら、ビルド時間を短縮する設定を探してみてはいかがでしょうか。

この記事がいいなと思った方は、はてなブックマークと読者登録をお願いします!