super-dev — full-stack

@super-dev.app · Full-stack · ★ open to work
12.8k subscribers·47 videos·Active since 2017

Full-stack .NET / Angular dev with a DevOps streak on Azure Cloud. Also fluent in Flutter + Firebase. I build products, ship them, and keep them healthy.

Download CV
Back to articles
{{ }}
.NET
.NET

Interpret, don't compile: a template engine for .NET

.NET template engines compile. Razor goes through Roslyn, Handlebars emits IL, and the first render foots the bill: tens of milliseconds of code generation before the first byte.

In a long-lived process, that cost gets amortized. In a serverless function or an on-demand container, you pay it again on every wake-up.

NgSharp does the opposite: the template is parsed into an AST then interpreted . Nothing to compile, zero third-party dependencies, and v3 holds its own on warm performance too, benchmarks to back it up.

The compilation cliff

Same 96-product catalog, same HTML output. RazorLight takes ~29 ms on first render, the time for Roslyn to compile. Handlebars, ~10 ms of IL emission. NgSharp, 32 µs .

The gap doesn't come from the rendering itself but from everything that doesn't happen: no compilation, no generated code to load.

Code generation also has an access cost: Native AOT and *trimming* exclude Roslyn and Reflection.Emit . An interpreted engine simply doesn't have that constraint.

Angular-style templates, no dependency

The syntax mirrors Angular's: {{ }} interpolation, pipes, [attr.x] / [class.x] bindings, server components, @if / @for / @switch control flow.

C#
1var builder = HtmlBuilder.Create(); // pre-loaded with the built-in pipes
2
3var html = builder.BuildFromTemplate(
4 "<ul>@for (u of Users) {<li>{{ u.Name | upper }}</li>}</ul>",
5 new { Users = new[] { new { Name = "ada" }, new { Name = "linus" } } });
6
7// → <ul><li>ADA</li><li>LINUS</li></ul>

The HTML parser is purpose-built, without AngleSharp. It does exactly one thing: produce structurally correct, escaped output.

Since v3, the engine also accepts things other than HTML: TemplateMode.Text runs plain text, JSON, or CSV through the same pipeline, for text emails and exports.

The documentation walks through every directive, pipe, and binding with runnable examples.

What v3 changes

The core was rewritten: single-pass parser, lazy and copy-free model reads, inline caches on property access, allocation-free Span -formatted pipes. The AST is immutable and the renderer is stateless, so a template compiled once renders in parallel, lock-free.

C#
1builder.BuildFromTemplate(template, model, new TemplateOptions
2{
3 Strict = true, // fail loud: silent misses become NgSharpException
4 Culture = new CultureInfo("fr-FR"), // per-render pipe formatting
5 Limits = new RenderLimits(), // resource caps
6});

Strict mode turns silent failures into NgSharpException , and catches an always-false condition or a division by a literal zero right at template compilation.

Byte-identical output

Template engine comparisons rarely compare the same thing, since each engine renders slightly different HTML. Here, the six engines measured (NgSharp, RazorLight, Handlebars, Fluid, Scriban, Stubble) produce byte-identical output, and a CI gate verifies this before measuring anything at all.

On the catalog, NgSharp renders warm in 25 µs for 33 KB allocated, ahead of every engine measured, both in time and in allocations. The whole thing is covered by 704 tests .

Where it runs

netstandard2.1 and net8.0 , IsAotCompatible , a single NuGet dependency ( System.Text.Json ). The TextWriter and RenderAsync sinks write atomically: a failed render writes nothing at all. The package is on NuGet : dotnet add package NgSharp .

Compiling templates buys warm-path speed at the price of a cold-start cliff. v3 shows the trade-off isn't mandatory: interpret, and still stay ahead on the warm path too. The benchmarks can be re-run with a single command from the repo.
super-dev — portfolio.app
// More in .NET
{ }
Strangle a .NET 8 monolith without breaking everything
9 min • 3.2k reads
ƒ()
Minimal APIs + EF Core: a clean .NET 8 API
8 min • 3.1k reads
CQRS and vertical slices without the ceremony
7 min • 2.3k reads