Skip to content
Switch branches/tags
Go to file
Cannot retrieve contributors at this time

Transforming code with build and build_runner

Unlike the old barback and pub asset systems, it's not permitted to overwrite or otherwise transform existing on-disk files as part of the build process, and our newer build tools and packages will throw exceptions if this is attempted.


Class members

In Dart Classic and Pub, you might write something like the following:

class Calculator {
  int doExpensiveCalculation() => ...

... and writing a pub transformer to understand @memoize and re-write the class:

class Calculator {
  // Code-generated field.
  int _memoize1;

  // New code-generated function.
  int doExpensiveCalculation() => _memoize1 ??= _doExpensiveCalculation();

  // Original user-authored function
  int _doExpensiveCalculation() => ...

As mentioned above, this is not permitted in the newer build systems.

You can emulate this functionality using part files. For example, the following:

// this file = calculator.dart
part 'calculator.g.dart';

abstract class Calculator {
  // Redirect to a generated class.
  factory Calculator() = _$Calculator;

  // The generated class needs a super constructor to call.
  // Make it private to prevent other libraries from using it.

  int doExpensiveCalculation() => ...

And generate the following:

// this file = calculator.g.dart
part of 'calculator.dart';

class _$Calculator extends Calculator {
  int _memoize1;

  _$Calculator() : super._();

  int doExpensiveCalculation() => _memoize1 ??= super.doExpensiveCalculation();

To the user of Calculator, the class works exactly the same:

main() {
  var calculator = new Calculator();

Top-level or static members

Classes are a little easier (and cleaner), because we can use factory functions.

For top-level (or static class members), it's a bit more work, but the same idea holds.

Previously if you wrote something like:

String appVersion;

... and expected it to be transformed into ...

String get appVersion => 'v1.0.0';

You'll need to do a little more work. Again, this time, we will use part files:

// this file = version.dart
part 'version.g.dart';

String get appVersion => _$ReplaceWith$appVersion;

And generate the following:

// this file = version.g.dart
part of 'version.dart';

const _$ReplaceWith$appVersion = 'v1.0.0';


But, why?

While it's tempting to want to use code generation as a form of "macros" or implementing your own higher-order language features, it unfortunately makes most of the optimizations around consistent, fast, and incremental builds nearly impossible. It's possible that we could emulate this in the future with either (a) new language features or (b) changes to our compilers, but this isn't something planned at this time.