Proposal B — templates
Status: under discussion · Complementary to Proposal A rather than competing with it · See the section overview for the question all five answer
The idea: stop assembling output with push_str and move the shape of
generated source into template files, one set per language. The generator
becomes "build a context, render a template."
Shape
comline-typescript/codegen/
src/generator.rs builds the render context from frozen IR
templates/
schema.ts.hbs interfaces, enums, error classes
protocol.ts.hbs client, dispatcher, serve helper
package.json.hbs the lib-mode manifest
tsconfig.json.hbs
Two credible engines, both already viable here:
- Handlebars — already a dependency of
comline-core(utils/templating.rs, used forlayout), so it is already proven under wasm. Runtime-loaded orinclude_str!-embedded; logic-light by design. - Askama / Rinja — compile-time templates that become Rust code. Type-checked
against their context struct, zero runtime, smallest wasm footprint, and a
broken template fails
cargo buildrather than a generation run.
The case for it
- It removes the real ugliness. The TypeScript generator is littered with
\x20escapes and\-continued string literals to keep indentation readable inside Rust source. That is a tax on every edit, and templates remove it entirely — what the output looks like becomes visible in a file shaped like the output. - Non-Rust contributors can help. Someone who knows idiomatic TypeScript but not Rust can fix emitted TypeScript. Today that requires reading 1,083 lines of Rust that pushes strings. This is the single biggest contributor-accessibility win available short of D.
- Diffs become reviewable. A change to emitted syntax shows up as a diff of the thing being emitted, not as a diff of the code that assembles it.
- Askama keeps every current property. Compile-time, no runtime dependency, wasm-safe, and strictly more checking than today.
The case against
- It fixes none of the defects. This is the decisive point. Not one entry in the overview's defect table would have been prevented: templates change how output is spelled, not what the generator knows about a name's origin. Adopting B instead of A would leave the entire demonstrated bug class in place while feeling like progress.
- Logic migrates into templates. Every template system that reaches real codegen accumulates conditionals and loops until the template is a program in a worse language — OpenAPI Generator's Mustache templates are the cautionary case. Resisting that needs the context to be pre-computed, which is A's job: the context struct is the resolved model.
- Two places to debug. A wrong character could be in the template or in the context. Handlebars errors are especially poor at saying which.
- Order of operations matters. Templating first would mean writing templates against today's unresolved context, then rewriting them when the context becomes a resolved model. Doing it after A means writing them once.
Example projects
| Project | Templates? | Outcome |
|---|---|---|
| OpenAPI Generator | Mustache, ~100 generators | reached enormous language coverage; widely criticised for template-embedded logic and inconsistent output quality between targets |
| swagger-codegen | Mustache | same lineage, same trade-off |
| sqlc, ent | Go text/template |
works well — context is fully resolved in Go before rendering, so templates stay dumb |
| prost / tonic | no | string building and quote! respectively; both stayed small enough not to need it |
| smithy-rs | no | an AbstractCodeWriter with inline formatting instead, so dependency tracking can be a side effect of writing |
The projects where templates work well are the ones that resolve everything before rendering. The projects where templates went wrong are the ones that asked templates to do the resolving.
When this would be the right call
Immediately after A, when there is a resolved context to render and templates can stay logic-free. Askama is the better fit of the two engines for Comline, since it preserves the compile-time, no-runtime, wasm-safe properties the current design depends on — Handlebars' only advantage is already being in the dependency graph.
It would be the wrong call as a substitute for A, or before it.