跪拜 Guibai
← All articles
Frontend · Flutter · Android

dmx Injects Generated Dart Code Directly Into Your Source Files on Save

By 恋猫de小郭 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Inline code generation removes the friction of managing separate part files and running a persistent build watcher. For developers who find the `build_runner` workflow heavy, dmx offers a save-to-update cycle that keeps generated code visible and version-controlled inside the source file itself.

Summary

The dmx package takes a fundamentally different approach to Dart code generation. Instead of producing companion `.g.dart` part files like `json_serializable` or Freezed, it rewrites the source file itself, inserting generated methods and properties between `//#region` markers inside the target class. A Rust-based VS Code extension watches for file changes and triggers regeneration on save, using tree-sitter to build a lossless concrete syntax tree rather than relying on regex.

Under the hood, Rust parses the Dart class into structured data — field names, types, nullability — and pre-computes expressions for equality, hashing, encoding, and decoding. Mustache templates then render the final Dart code. The project ships with 11 built-in generators covering models, unions, enums, SQL table bindings, REST clients, and more.

Custom generators are written as Dart classes extending `DmxMacro`, making it a general-purpose codegen framework. A macro can read external sources like a SQLite schema or an OpenAPI spec and produce corresponding Dart classes and clients. A standalone CLI also supports headless use in CI or AI-assisted workflows.

Takeaways
dmx writes generated code directly into the annotated `.dart` file, not into a separate `.g.dart` part file.
Code generation runs on file save via a Rust-powered VS Code extension that watches the workspace.
Parsing uses tree-sitter-dart to build a lossless concrete syntax tree, avoiding fragile regex-based extraction.
Rust pre-computes expressions for equality, hashing, JSON encode/decode, and copy logic; Mustache templates handle formatting only.
The package ships with 11 generators: model, union, enum, diff, lerp, validate, table, route, cli, fake, and restClient.
Custom macros are written as Dart classes extending `DmxMacro` and can read external data sources like SQLite schemas or OpenAPI specs.
A standalone CLI (`dmx build` / `dmx watch`) supports headless and CI usage without the VS Code extension.
Conclusions

Moving codegen logic into Rust and tree-sitter sidesteps the performance and correctness issues that plague Dart-based builders operating on raw text or the analyzer AST.

Writing generated code back into the source file is a deliberate trade-off: it simplifies the project structure but means generated regions must be carefully delimited to avoid merge conflicts and accidental edits.

The project's timing is awkward — the rise of AI coding assistants has reduced the pain of hand-writing boilerplate, which was the original motivation for tools like this.

Concepts & terms
Inline Code Generation
A code generation strategy where the tool writes generated code directly into the original source file (between marked regions) rather than emitting a separate companion file. This keeps all code for a class in one place but requires careful region management to avoid overwriting manual edits.
Lossless Concrete Syntax Tree
A parse tree produced by tree-sitter that preserves every token, including whitespace and comments, and records exact byte offsets. This allows a code generator to modify source files without destroying formatting or comments in unrelated parts of the file.
From the discussion

The core objection is that dmx merely shifts boilerplate generation from a separate part file into the source file, which one view considers a regression in separation of concerns. The counter-position, implicit in the tool's design, treats in-file generation as a convenience that removes the build_runner watch dependency. A secondary point dismisses the tool's value because AI can already produce similar boilerplate.

dmx is fundamentally still a code generator, not a novel approach.
Generating code directly into source files mixes implementation details with authored code, which is seen as worse than keeping generated code in separate .g.dart files via build_runner watch.
In-file generation is compared unfavorably to IDE automation features like Java's getter/setter generation, where manual control is preferred.
AI tools can already generate serialization and equality boilerplate, reducing the need for a dedicated generator like dmx.
Featured comments
c1yde3

Feels inferior to build_runner watch. Implementation details like these should be hidden away. Writing them into the source file is like automating the auto getter/setter feature for Java in IDEA — I'd rather do some of that manually. Besides, AI can pretty much auto-generate this kind of code too.

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗