跪拜 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.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗