Dart's code generation tooling is one of its killer features. Projects like json_serializable,
freezed, and my own auto_mappr all use it to eliminate boilerplate.
How build_runner Works#
build_runner watches your source files and runs Builder classes against them. A builder reads an input file, transforms it, and writes an output file — typically with a
.g.dart suffix.
dart run build_runner build
# or during development:
dart run build_runner watch
Writing a Simple Builder#
class MyBuilder implements Builder {
@override
Map<String, List<String>> get buildExtensions => {
'.dart': ['.g.dart'],
};
@override
Future<void> build(BuildStep buildStep) async {
final inputId = buildStep.inputId;
final outputId = inputId.changeExtension('.g.dart');
await buildStep.writeAsString(outputId, '// generated');
}
}
auto_mappr Under the Hood#
auto_mappr reads your @AutoMappr annotation, inspects the generic type arguments, and generates a complete mapping implementation — no runtime reflection needed.
The generated code is plain Dart that you can read and debug, which makes it much easier to trust than runtime magic.