fast_build_runner 0.1.4
fast_build_runner: ^0.1.4 copied to clipboard
Faster watch and incremental rebuild workflows for build_runner-based Dart and Flutter projects.
fast_build_runner #
Faster incremental rebuilds for Dart/Flutter projects built on top of
build_runner.
fast_build_runner keeps builders, analyzer, and generated code in Dart. It
currently focuses on speeding up the watch / incremental path while keeping
generated outputs aligned with upstream.
Current best public signal:
- up to
6.66xfaster incremental rebuilds - generated Dart outputs verified to match upstream on a large real Flutter app
- one-shot
buildstays on the upstreambuild_runner buildpath - the current optimization target is the watch / incremental workflow
- the main Dart runtime / hot-path code lives in
packages/fast_build_runner_internal/lib/src/
Start Fast #
Install the package:
dart pub global activate fast_build_runner
Use the fast runtime for watch / incremental rebuilds:
fast_build_runner spike-watch
Optional experimental Rust source engine:
fast_build_runner spike-watch --source-engine=rust
Table Of Contents #
- Start Fast
- Current Headline
- What This Project Is
- What This Project Is Not
- Why It Exists
- Current Default
- Real-World Results: Large Flutter App
- Correctness Status
- Quick Start
- CLI Commands
- Architecture
- Main Technical Direction
- Main Constraints
- Honest Public Positioning
- Repository Health Commands
- Project Documents
Current Headline #
- ✅ Up to
6.66xfaster incremental rebuilds on a large real Flutter app - ✅ Up to
1.44xfaster total watch time on the same app - ✅ Custom bootstrap path with real upstream
BuilderFactories - ✅ Custom child runtime around upstream
BuildPlan/BuildSeries - ✅ Generated Dart outputs match upstream byte-for-byte on a large real Flutter app
- ⚠️ Rust mode is still experimental
What This Project Is #
fast_build_runner currently experiments with three layers:
- Custom bootstrap
- Generates a custom entrypoint instead of handing everything to the stock
ChildProcess.run(...).
- Generates a custom entrypoint instead of handing everything to the stock
- Custom watch runtime
- Reuses upstream planning/build internals while controlling the watch loop, batching, and incremental scheduling.
- Alternative source engines
dartsource engine: current safe defaultrustsource engine: optional experimental acceleratorupstream: baseline for comparison
What This Project Is Not #
- not a full rewrite of
build_runner - not a full invalidation-engine replacement
- not yet a production-ready replacement for every builder topology
- not yet a solved story for workspaces / post-process / lazy phases
Why It Exists #
The practical bottleneck for many teams is not code generation itself, but:
- slow startup for repeated watch sessions
- broad invalidation after a small edit
- repeated rebuild scheduling on tiny changes
- too much work happening outside the actual tracked builder actions
The current strategy is deliberately narrow:
- keep the ecosystem-compatible parts in Dart
- fork only the hottest internal paths when needed
- measure everything on a real Flutter app instead of synthetic claims only
Current Default #
The current recommended default is:
buildproxies to upstreambuild_runner build- Dart source engine by default
- Rust source engine only as an opt-in experimental mode
- fast runtime is currently focused on watch / incremental workflows
If you do not pass --source-engine, fast_build_runner already uses
dart.
Why this is the default:
- it already shows strong incremental wins
- it matches upstream generated Dart outputs
- it avoids the current Rust startup penalty on short sessions
Real-World Results: Large Flutter App #
Benchmarks below were run on a large private Flutter app with three mutation profiles.
Total Wall-Clock vs Upstream build_runner #
| Scenario | Upstream | fast_build_runner (dart) | fast_build_runner (rust) |
|---|---|---|---|
| DTO mutation | 49.74s | 43.52s (1.14x) |
63.45s (0.78x) |
| Freezed mutation | 50.39s | 43.33s (1.16x) |
42.84s (1.18x) |
| Injection mutation | 66.72s | 49.38s (1.35x) |
46.24s (1.44x) |
Incremental Rebuild vs Upstream build_runner #
| Scenario | Upstream | fast_build_runner (dart) | fast_build_runner (rust) |
|---|---|---|---|
| DTO mutation | 4.41s | 0.97s (4.54x) |
0.93s (4.76x) |
| Freezed mutation | 8.75s | 5.02s (1.74x) |
4.99s (1.75x) |
| Injection mutation | 25.10s | 4.37s (5.75x) |
3.77s (6.66x) |
Current Interpretation #
- The main value today is incremental rebuild speed.
- The Dart mode is the current safe public story.
- The Rust mode already wins on some heavy cases, but still has bad total behavior on short DTO-style sessions because its startup cost does not always amortize.
- The strongest result so far is the DI / injection-heavy case.
Correctness Status #
The most important current correctness claim is:
- upstream
build_runner fast_build_runner --source-engine=dart
produce the same generated Dart outputs on the large private Flutter app for the current regression scenario.
There is now a regression test for that in:
- test/real_app_output_compatibility_test.dart
What is expected to match #
Generated code outputs such as:
*.g.dart*.freezed.dart*.config.dart*.gr.dart*.mocks.dart
What may differ without being a correctness bug #
Build metadata and cache/tooling artifacts, for example:
.flutter-plugins-dependenciesbuild/**/outputs.jsonbuild/**/.filecachebuild/**/gen_localizations.*
Those files are not the generated API/code outputs that users review and commit.
Quick Start #
Run commands from this repository root.
Bootstrap seam proof #
/Users/belief/dev/flutter/bin/dart run bin/fast_build_runner.dart spike-bootstrap
Single watch run on the default Dart engine #
/Users/belief/dev/flutter/bin/dart run bin/fast_build_runner.dart spike-watch
Single watch run on the Rust engine #
/Users/belief/dev/flutter/bin/dart run bin/fast_build_runner.dart spike-watch \
--source-engine=rust
One-shot build through the upstream path #
/Users/belief/dev/flutter/bin/dart run bin/fast_build_runner.dart build \
--delete-conflicting-outputs
This intentionally proxies to upstream build_runner build, so single builds
keep upstream cold-start behavior while fast_build_runner stays focused on the
watch / incremental path.
Compare engines #
/Users/belief/dev/flutter/bin/dart run bin/fast_build_runner.dart benchmark-watch \
--include-upstream \
--output=summary
Real project benchmark on a local real app #
/Users/belief/dev/flutter/bin/dart run bin/fast_build_runner.dart benchmark-watch \
--fixture="$FAST_BUILD_RUNNER_REAL_APP_PATH" \
--mutation-profile=profiles/real_app/analytics_service_injection.json \
--include-upstream \
--output=summary
Set FAST_BUILD_RUNNER_REAL_APP_PATH to your local Flutter app path first.
Example:
export FAST_BUILD_RUNNER_REAL_APP_PATH=/absolute/path/to/your/flutter_app
/Users/belief/dev/flutter/bin/dart run bin/fast_build_runner.dart benchmark-watch \
--fixture="$FAST_BUILD_RUNNER_REAL_APP_PATH" \
--mutation-profile=profiles/real_app/analytics_service_injection.json \
--include-upstream \
--output=summary
CLI Commands #
build- proxies to upstream
build_runner buildfor one-shot builds
- proxies to upstream
spike-bootstrap- proves the bootstrap seam with a generated custom entrypoint
spike-watch- runs one watch/incremental scenario with a chosen source engine
benchmark-watch- compares engines, optionally against upstream baseline
Architecture #
target project
|
v
fast_build_runner CLI
|
v
custom generated entrypoint
|
v
child-side runtime
- BuildPlan
- BuildSeries
- watch scheduler
- custom perf probes
|
+--> Dart source engine
|
+--> Rust source engine
Upstream dependency pin #
The current research pin is:
2b1450e313a188a1027f04940e0e4e82372d6530
The local upstream source-of-truth clone lives in:
research/dart-build/
Main Technical Direction #
The current most promising direction is not "rewrite everything in Rust".
The strongest signal so far is:
- keep builders and analyzer in Dart
- use a narrow Dart-side fork for hot internal paths
- use Rust only where it genuinely helps as an optional source/watch engine
In practice that means the next strong wins are expected from:
- analysis/resolver state retention between incremental builds
- less repeated sync into the analyzer-facing filesystem
- tighter watch/update ingestion
Main Constraints #
- analyzer-heavy builders are still bounded by upstream Dart-side costs
- full build-script freshness parity is still narrower than stock upstream
- Rust is currently a source-engine experiment, not a full graph daemon
- some project profiles still show weak or negative Rust total wall-clock gain
- workspace / post-process / lazy-phase coverage is not complete yet
Honest Public Positioning #
Good:
- "experimental companion for
build_runner" - "real wins on a large private Flutter app"
- "generated Dart outputs match upstream in the Dart mode"
- "Rust mode is optional and still experimental"
Bad:
- "universal
build_runnerreplacement" - "Rust makes everything faster"
- "all builder topologies are solved"
Repository Health Commands #
Analyze:
/Users/belief/dev/flutter/bin/dart analyze
Targeted tests:
/Users/belief/dev/flutter/bin/dart test \
test/bootstrap_spike_test.dart \
test/real_app_output_compatibility_test.dart \
test/watch_alpha_test.dart \
test/watch_benchmark_test.dart \
Rust daemon tests:
cd native/daemon && cargo test
Project Documents #
- Public Demo
- Vision
- Roadmap
- Bootstrap Strategy
- Upstream Code Map
- Feasibility Assessment
- Performance Ceiling
- Edge Cases
- Iteration 00: Principles
- Iteration 01: Repository Bootstrap
- Iteration 02: Dart Internal Adapter
- Iteration 03: Rust Daemon and Protocol
- Iteration 04: Fast Watch Pipeline
- Iteration 05: Graph and Invalidation Engine
- Iteration 06: Benchmarks and Compatibility
- Iteration 07: Build Mode and AOT Expansion