Skip to content

multiarc/Heddle

Heddle

A text template engine for .NET. Heddle compiles templates written in its small, purpose‑built language into reusable, strongly‑typed renderers. Templates can embed real C# expressions, define and inherit reusable named blocks, compose output through extension chains, and control HTML encoding per directive.

@model(){{dynamic}}
<p>Hi @(Name) — you have @int(Count) new comments.</p>
HeddleTemplate.Configure(typeof(Program).Assembly);

var source = "@model(){{dynamic}}\n<p>Hi @(Name) — you have @int(Count) new comments.</p>";
using var template = new HeddleTemplate(source, new CompileContext(new TemplateOptions { AllowCSharp = true }));

string html = template.Generate(new { Name = "Ada", Count = 3 });
// <p>Hi Ada — you have 3 new comments.</p>

Packages

Package Purpose
Heddle Core engine: parser host, compiler, runtime, built‑in extensions.
Heddle.Language ANTLR grammar + generated lexer/parser and editor assets.

Documentation

Full documentation lives in docs/:

How Heddle compares

Heddle sits in a small niche: a compiled, statically‑typed template language whose entire control‑flow vocabulary is library, not grammar. Here is where it lands against the engines people usually weigh it against.

Heddle Razor Liquid / Scriban Handlebars / Mustache Go text/template
Execution Compiled to an exec‑ready document Compiled Interpreted Interpreted Interpreted
Typing Static, per use site Static Dynamic Dynamic Dynamic
Logic in templates Full C# (opt‑in) Full C# Sandboxed filters Logic‑less + helpers Limited + funcs
Control flow Extensions (a library) Keywords Tags Block helpers Keywords
Context model Relative: current / ::root Absolute (Model.X) Mostly global Relative stack Relative (. / $)
Composition Definition inheritance + override Layouts / sections / partials Partials / includes Partials Heddle / blocks
Untrusted templates No (or run in no‑C# mode) No Yes (sandboxed) Yes (logic‑less) Partial
Reach / ecosystem .NET only, small .NET, excellent Large Large, polyglot Large

What's genuinely distinctive (the combination, more than any single trait):

  • Extension‑only core. There are no if/for/include keywords — those are extensions, so the language grows by adding a class, not by changing the grammar. The : chain composes them right‑to‑left, with a single chained channel carrying the loop index, the piped value, and @out() content.
  • Abstract, late‑bound sections. A definition with no :: Type is compiled against the concrete model at each call site — closer to a C++ template (monomorphised, type‑checked per use) than a C# generic (compiled once behind constraints). Reuse one section across many model shapes, each statically checked.
  • Composition without coupling. Definition inheritance/override are declarative extension points: unlike Razor sections (which bind "backwards" and pin the rendered page as the layout's final consumer), a Heddle page can be split into independent templates recombined by a layout with no runtime cost, and any page can serve as a base for another.
  • Compiled to an execution‑ready document. A template becomes an in‑memory tree of extension calls wired to compiled accessors — member paths to expression‑tree delegates, embedded C# to Roslyn delegates — so nothing is reflected or re‑parsed per render. It renders faster than Razor with fewer allocations on a like‑for‑like page.

Best fit: performance‑sensitive, first‑party .NET rendering by a team that values typed templates and component‑style composition. Poor fit: untrusted user‑supplied templates (the security model is all‑or‑nothing on AllowCSharp, not a sandbox), polyglot stacks, or teams wanting a large ecosystem and batteries‑included tooling. The language also trades some ergonomics for its small core — dense sigils and no else/elif (compose @if/@ifnot instead). See the Language Reference for the full picture.

Performance

Heddle compiles each template into an execution‑ready document — an in‑memory tree of extension calls wired to compiled accessors (member paths to expression‑tree delegates, embedded C# to Roslyn delegates). Rendering walks that document, so it does not re‑parse, reflect, or pay per‑call activation, section, or dependency‑injection overhead at run time. The repository includes a BenchmarkDotNet suite (src/Heddle.Performance) that renders a realistic home page — a layout plus several reusable templates and a dozen component/extension invocations — and compares it head‑to‑head against the equivalent ASP.NET Core Razor page (RenderTemplateEngine vs RenderRazor).

On that like‑for‑like workload, Heddle renders the page faster than Razor and allocates less memory ([MemoryDiagnoser] is enabled). The advantage comes from two places:

  • Execution — walking the pre‑compiled document avoids the partial/section/view‑component machinery Razor invokes per component (see Architecture → Performance).
  • Composition — Heddle definitions are declarative, decoupled extension points. Unlike Razor sections, which bind "backwards" and force the rendered page to be the layout's final consumer, a Heddle template can be split into independent reusable templates recombined by a layout with no runtime cost, and any page can itself serve as a base for another. See Language Reference → inheritance.

Run it yourself:

dotnet run -c Release --project src/Heddle.Performance

Building

dotnet build -c Release

Requires the .NET SDK 10.0 (see global.json). See docs/building.md for details.

Contributing

Contributions are welcome. Please read CONTRIBUTING.md first — it covers the development setup, the pull-request flow, the required DCO sign-off (git commit -s), and the policy on AI-assisted contributions. By contributing you agree your work is licensed under Apache-2.0.

License / authorship

Copyright © Aliaksandr Kukrash and the Heddle contributors.

Licensed under the Apache License, Version 2.0 — see LICENSE, NOTICE, and THIRD-PARTY-NOTICES.md.