Irbisa · Шпаргалка

Кодогенерация и build_runner

build_runner, part-файлы, json_serializable, freezed, build.yaml, source_gen, время сборки

Middle-разработчик · 18 вопросов

  1. 01

    Что такое build_runner и почему Dart генерирует код на этапе сборки, а не использует рефлексию в рантайме?

    build_runner представляет собой инструмент для разработки, который прогоняет генераторы кода (builders) по пакету и записывает обычные Dart-файлы вроде user.g.dart.

  2. 02

    Зачем модели на json_serializable нужны part 'user.g.dart'; и методы-обёртки (forwarders) к _$UserFromJson?

    Сгенерированные функции приватные (начинаются с _), поэтому должны лежать в той же библиотеке, что и ваш класс.

    import 'package:json_annotation/json_annotation.dart';import 'address.dart'; // the part file cannot import anything itself part 'user.g.dart'; // you write this; the generator writes `part of` @JsonSerializable()…
  3. 03

    Чем отличаются build_runner build, watch и clean и что сегодня делает --delete-conflicting-outputs?

    build генерирует один раз и завершается, watch продолжает работать и при каждом сохранении перегенерирует только изменённое, а clean удаляет кэш сборки в .dart_tool/build.

    # one-off generation: after a pull, a dependency upgrade, in CIdart run build_runner build # day-to-day: stays running, rebuilds only what changeddart run build_runner watch…
  4. 04

    Как управлять именами ключей и значениями enum в json_serializable: snake_case, одно переименованное поле, пропущенное поле, неизвестное значение enum?

    Аннотациями: fieldRename в @JsonSerializable переименовывает все ключи, @JsonKey(name: ...) переопределяет одно поле, includeFromJson/includeToJson: false исключает поле, а @JsonValue вместе с unknownEnumValue безопасно маппят значения enum.

    enum Status {  @JsonValue('in_progress')  inProgress,  done,  unknown,}…
  5. 05

    Нужно ли коммитить в git сгенерированные файлы .g.dart и .freezed.dart?

    Для приложения обычно нет: добавьте их в gitignore и генерируйте после каждого клона и в CI.

  6. 06

    Что выведет здесь print(order.toJson()) и почему это может сломать код, который не проходит через jsonEncode?

    @JsonSerializable()class Order {  const Order(this.id, this.address);  final String id;  final Address address; // Address is @JsonSerializable too   Map<String, dynamic> toJson() => _$OrderToJson(this);} void main() {  final order = Order('42', const Address('Almaty'));  print(order.toJson());  print(jsonEncode(order.toJson()));}

    Выведется {id: 42, address: Instance of 'Address'}: по умолчанию сгенерированный toJson кладёт в map сам вложенный объект.

  7. 07

    Что произойдёт, если передать каждый из этих трёх ответов в сгенерированный Profile.fromJson?

    @JsonSerializable()class Profile {  const Profile({required this.name, required this.age, this.bio});  final String name;  final int age;  final String? bio;   factory Profile.fromJson(Map<String, dynamic> json) =>      _$ProfileFromJson(json);} Profile.fromJson({'name': 'Ann', 'age': 30.0}); // 1Profile.fromJson({'name': null, 'age': 30}); // 2Profile.fromJson({'name': 'Ann', 'age': '30'}); // 3

    Первый сработает, два других бросят TypeError.

  8. 08

    Как сериализовать тип, с которым json_serializable не справляется, например, время в epoch-миллисекундах или value-объект Money?

    Напишите JsonConverter<T, Json> с fromJson и toJson и повесьте его на поле, на класс или передайте через converters: в @JsonSerializable.

    class Money {  const Money(this.cents);  final int cents; // minor units, never a double} class MoneyConverter implements JsonConverter<Money, int> {…
  9. 09

    Найдите баг: пользователь меняет на экране профиля только имя, а на сервере пропадают его био и аватар.

    // PATCH /me: the server updates only the keys present in the body@JsonSerializable(fieldRename: FieldRename.snake)class ProfilePatch {  const ProfilePatch({this.name, this.bio, this.avatarUrl});  final String? name;  final String? bio;  final String? avatarUrl;   Map<String, dynamic> toJson() => _$ProfilePatchToJson(this);} // Edit profile screen: the user changed only the nameFuture<void> rename(Dio dio, String name) =>    dio.patch<void>('/me', data: ProfilePatch(name: name).toJson());

    Сгенерированный toJson пишет все поля, включая null, поэтому тело запроса выглядит как {"name":"Ann","bio":null,"avatar_url":null}.

  10. 10

    Что генерирует freezed и когда достаточно обычного sealed-класса или @JsonSerializable?

    freezed генерирует == и hashCode по всем полям, toString, copyWith, умеющий и обнулить поле, и классы за factory-конструкторами.

    import 'package:freezed_annotation/freezed_annotation.dart'; part 'user.freezed.dart'; // ==, hashCode, toString, copyWithpart 'user.g.dart'; // fromJson / toJson, from json_serializable @freezed…
  11. 11

    Эта модель компилировалась с freezed 2, а после обновления до freezed 3 падает с «Missing concrete implementations». Что изменилось?

    import 'package:freezed_annotation/freezed_annotation.dart'; part 'point.freezed.dart'; @freezedclass Point with _$Point {  const factory Point({required double x, required double y}) = _Point;} // error: Missing concrete implementations of// 'getter mixin _$Point on Object.x' and 'getter mixin _$Point on Object.y'.

    Freezed 3 требует, чтобы аннотированный класс был abstract (одиночный data-класс) или sealed (union).

  12. 12

    Найдите баг: round-trip-тесты для этих событий проходят, но в продакшене каждое событие screen_view не декодируется.

    // Server events: {"type": "purchase", "sku": "A1", "cents": 990}//                {"type": "screen_view", "route": "/home"}@Freezed(unionKey: 'type')sealed class Event with _$Event {  const factory Event.purchase({required String sku, required int cents}) =      Purchase;  const factory Event.screenView({required String route}) = ScreenView;   factory Event.fromJson(Map<String, dynamic> json) => _$EventFromJson(json);}

    Freezed использует имя конструктора как значение union, поэтому сгенерированный fromJson ждёт 'screenView', а не 'screen_view'.

  13. 13

    Для чего нужен build.yaml и как ограничить файлы, которые обрабатывает билдер?

    build.yaml в корне пакета настраивает билдеры этого пакета: какие файлы читает каждый из них (generate_for), его опции и запускается ли он вообще (enabled).

    # build.yaml, next to pubspec.yamltargets:  $default:    builders:      json_serializable:        generate_for:…
  14. 14

    Вы добавили строку в ARB-файл и запустили build_runner, а в AppLocalizations так и нет нового геттера. Почему?

    ARB-файлы не входят в пайплайн build_runner.

    # l10n.yaml at the project root: read by `flutter gen-l10n`, not build_runnerarb-dir: lib/l10n/arbtemplate-arb-file: app_en.arboutput-dir: lib/l10n # real source files, imported normallyoutput-localization-file: app_localizations.dartoutput-class: AppLocalizations…
  15. 15

    Полная сборка build_runner занимает несколько минут. Куда уходит время и как его сократить?

    В основном на то, что анализатор разрешает библиотеки для билдеров: каждый аннотированный файл разрешается вместе со всем, что он импортирует.

  16. 16

    Как написать собственный генератор кода на source_gen и как build_runner его подхватывает?

    Унаследуйтесь от GeneratorForAnnotation<T> и верните генерируемый код из generateForAnnotatedElement, оберните генератор в SharedPartBuilder внутри factory-функции верхнего уровня и объявите эту функцию в build.yaml пакета-генератора.

    import 'package:analyzer/dart/element/element.dart';import 'package:build/build.dart';import 'package:source_gen/source_gen.dart';import 'package:my_annotations/my_annotations.dart'; // AnalyticsEvent class EventGenerator extends GeneratorForAnnotation<AnalyticsEvent> {…
  17. 17

    Почему в монорепозитории build_runner ничего не генерирует для feature-пакета и как настроить генерацию по всем пакетам?

    Билдер применяется только к пакетам, которые напрямую зависят от его пакета (auto_apply: dependents), а обычный запуск пишет выходные файлы только в тот пакет, где его запустили.

    # pubspec.yaml at the repo root: a pub workspacename: shop_workspaceenvironment:  sdk: ^3.9.0workspace:  - app…
  18. 18

    Что случилось с макросами Dart и что это значит для кодогенерации в вашем проекте?

    Команда Dart отменила макросы в январе 2025 года, до их выхода: выполнение пользовательского кода внутри компилятора и анализатора на каждое нажатие клавиши слишком дорого обходилось скорости IDE и hot reload.