Building & Testing
How to build the engine, run the tests, and produce the NuGet packages.
Prerequisites
.NET SDK 10.0. The repository pins the SDK in global.json:
json{ "sdk": { "version": "10.0.100", "rollForward": "latestMinor" } }Java — only needed if you regenerate the parser from the
.g4grammar; the generation scripts download the ANTLR 4.13.1 jar from antlr.org on first run. See Architecture → grammar. The generated parser is checked in, so a normal build does not need Java.
Building
From the repository root (the commands pick up Heddle.sln):
dotnet restore
dotnet build -c Release
dotnet test # runs all test projects, as CI doesTarget frameworks
| Project | Targets |
|---|---|
Heddle (csproj) | netstandard2.0; net6.0; net8.0; net10.0 |
Heddle.Language (csproj) | netstandard2.0; net6.0; net8.0; net10.0 |
Heddle.Tests | net48 (Windows only); net6.0; net8.0; net10.0 |
All shipping projects use LangVersion=latest and are strong‑name signed with heddle.snk (SignAssembly=true, AssemblyOriginatorKeyFile=..\..\heddle.snk). The current release line is 2.0.0; the published version is set from the release tag (vX.Y.Z) at publish time, so the version in the source tree is just a placeholder.
Key dependencies
Antlr4.Runtime.Standard4.13.1 — runtime for the generated parser.Microsoft.CodeAnalysis.CSharp(Roslyn) — compiles embedded C# expressions; the version is pinned per target framework (4.1.0 on netstandard2.0, 4.9.2 on net6.0, 4.11.0 on net8.0, 5.3.0 on net10.0).Microsoft.Extensions.DependencyModel/Microsoft.Extensions.FileProviders.Embedded— assembly discovery and embedded resources.
Testing
Tests use xUnit. The core engine suite lives in src/Heddle.Tests — its key file is HeddleTemplateTests.cs, with other suites covering the compiler, reflection helpers, and string builders. Four more test projects cover the rest of the toolchain: Heddle.Generator.Tests and Heddle.Generator.IntegrationTests (the source generator), Heddle.Tool.Tests (the heddle CLI), and Heddle.LanguageServices.Tests (the editor language services).
Run the whole solution — this is what CI does (dotnet.yml runs dotnet test -c Debug --no-restore):
dotnet testOr scope to a single project, e.g. dotnet test src/Heddle.Tests.
Many tests are golden‑file comparisons: a .heddle template under TestTemplate/ is rendered and compared against an expected *.html file (e.g. recursion.heddle → test-recursion.html, vc-test.heddle → test-vc.html). The generated-*.html files are the actual output written during a run, for diffing against the test-*.html expectations. These fixtures double as the authoritative examples used throughout this documentation.
Performance benchmarks
src/Heddle.Performance contains a BenchmarkDotNet suite that compares Heddle against four other .NET template engines (Fluid, Scriban, DotLiquid, Handlebars.Net) on byte‑identical parity‑checked output, plus ASP.NET Core Razor on a comparable — but larger and not parity‑checked — page. Run it in Release:
dotnet run -c Release --project src/Heddle.PerformanceWhat it measures (TextRenderBenchmarks.cs, [MemoryDiagnoser] enabled):
RenderHeddle(published in the linked 2026‑07‑11 report under its former name,RenderTemplateEngine) renders the Heddle home page (TestTemplates/home.heddle + layout.heddle) throughHeddleTest.RenderRazorrenders a comparable Razor page (Views/home.cshtml +layout.cshtml) with runtime compilation throughRazorTest. Razor's page is larger and renders different bytes, so — unlike the four Liquid/Handlebars twins — it is not held to the byte‑identical parity assertion; treat its row as indicative rather than apples‑to‑apples.
Both pages are shaped alike: one layout, several reusable templates/sections, and a dozen component invocations — the Heddle components live in TestSuite/Extensions and their Razor counterparts in TestSuite/RazorExtensions. In the published run of 2026‑07‑11 Heddle rendered faster than Razor and allocated less memory (and led the four parity‑checked engines too); for the numbers see the README Performance section, and for why, see Architecture → Performance characteristics.
Benchmark numbers are hardware‑ and workload‑specific — run the suite on your target machine and with a page shaped like your real one to get figures you can quote. The repository benchmark is a representative, component‑heavy page where the compiled document's advantage is most visible.
There are extra runners (compilation cost, memory) in src/Heddle.Performance/Runners.
Packaging
Pack all six shipping packages (Heddle, Heddle.Language, Heddle.Generator, Heddle.LanguageServices, Heddle.LanguageServer, Heddle.Tool) by packing the whole solution, as CI does:
dotnet pack -c Release -o packagesHeddle.Tool and Heddle.LanguageServer are RID‑specific dotnet tool packages, so dotnet pack builds and publishes each RID for them (a RID‑less dotnet build --no-build would miss those outputs).
NuGet feed configuration is in NuGet.Config.
Continuous integration
CI runs on GitHub Actions. All publishing uses Trusted Publishing (OIDC, no stored tokens) and is skipped on fork pull requests.
- .NET build — on every push and pull request to
main, restores, builds, and runs the test suite on Linux and Windows. Internal pull requests also publish a-beta.<run>prerelease to nuget.org. - Ace npm package — builds the custom Ace highlighter bundle; internal pull requests stage a
@multiarc/ace_heddlepre-release on npmjs.org for maintainer review (npm stage publish). - Production releases are tag-driven. Pushing a
vX.Y.Ztag publishes that exact version to nuget.org and npmjs.org (aslatest, with npm provenance) and creates a matching GitHub Release. Merging tomainonly builds and tests — it does not publish. - Documentation — builds this site (including the WebAssembly demo bundle) and deploys it to GitHub Pages. Pull requests build and run the demo smoke suite but do not deploy.
- Integration samples — a
fail-fast: falsematrix, one job persamples/project, running each in capture mode and comparing against its golden.
The integration sample gallery
The repo-root samples/ folder holds ten small, complete, runnable projects — one per supported way to integrate Heddle (SSR, definition libraries, dynamic models, sandboxed user templates, safe output, custom extensions, component libraries, build-time codegen, precompilation, and streaming). They double as the engine's end-to-end test suite: each captures deterministic output that CI compares against a committed golden, so a broken sample is a failed integration test.
Run one interactively, or in the CI capture mode:
dotnet run --project samples/dynamic-models # human mode
dotnet run --project samples/dynamic-models -c Release -- --capture out
bash samples/tools/compare-golden.sh samples/dynamic-models # byte-compare vs golden/UPDATE_GOLDEN=1 bash samples/tools/compare-golden.sh samples/<name> regenerates a golden (a review event, never a silent fix). Adding a sample is one folder + one samples.yml matrix entry + one index row — see the gallery README.
Regenerating the parser
Only needed when you change the grammar. With Java available:
cd src/Heddle.Language
generate_cs.cmd # C# lexer/parser into generated/
generate_js.cmd # JS lexer/parser into js/ (for editor tooling)See Architecture for details.