Stencil is a lightweight Mustache-style template engine for MoonBit. It focuses on a practical, documented core: variable interpolation, sections, inverted sections, partials, comments, dotted-path lookup, changing delimiters, and safe HTML escaping by default.
Why this project
Small API surface: render, compile, and partial-aware rendering helpers.
Safe-by-default output: {{name}} escapes HTML automatically.
Practical Mustache coverage: sections, inverted sections, lists, object contexts, comments, raw variables, and partials.
Predictable edge behavior: standalone control lines, CRLF input, bounded nesting, and deterministic errors.
MoonBit-first maintenance: tests, CI, changelog, and repository self-checks are part of the project itself.
Feature Summary
Escaped variables: {{name}}
Raw variables: {{{html}}} and {{&html}}
Sections: {{#items}}...{{/items}}
Inverted sections: {{^items}}...{{/items}}
Dotted-path lookup: {{user.profile.name}}
Implicit iterator for lists: {{.}}
Partials with standalone indentation propagation
Comment tags: {{! ignored }}
Standalone comments and section control lines
Delimiter changes: {{=<% %>=}}<%name%>
Bounded parser and partial expansion depth (256 levels)
Configurable render limits, missing-partial policies, and cycle detection
PartialStore for reusable partial registries, validation, dependency inspection, and startup compilation
Diagnostics and structural metrics for editors, CI, and acceptance review
Compatibility corpus and reproducible HTML catalog benchmark
Render a precompiled template with named partial sources.
RenderOptions and checked rendering
render_with_options and render_with_options_and_partials expose explicit
policies for HTML escaping, nesting/partial/output limits, missing partials,
and recursive partial detection. MissingPartialPolicy::Empty preserves the
legacy behavior; Error is recommended for production validation and
Preserve is useful for multi-pass pipelines.
let options = @stencil.RenderOptions::default()
.with_max_output_length(1_000_000)
.with_missing_partial(@stencil.MissingPartialPolicy::Error)
let html = @stencil.render_with_options_and_partials(template, data, partials, options)
PartialStore
PartialStore centralizes named sources and supports set, remove, names,
validate, dependencies, compile_all, and option-aware rendering. This
provides a deterministic application boundary without introducing filesystem
or network access into the library.
TemplateCatalog and batch rendering
TemplateCatalog is the application-level boundary for a group of named
templates. It owns source text, lazily compiled templates, revision numbers,
dependency edges, health reports, and cache invalidation when a source is
replaced. This is useful for page bundles, email layouts, documentation builds,
and configuration generators that must validate a manifest before serving it.
render_jobs preserves successful outputs and failure details instead of
discarding the whole batch. dependency_report catches missing partials and
cycles, while health_report combines compile, syntax, and dependency checks.
See catalog usage and operational notes.
Diagnostics and analysis
diagnose(source) returns stable severity, message, byte index, line, and
column fields without raising. compile(source).stats() reports node counts,
variables, sections, partials, depth, delimiter use, and a complexity score.
The CLI exposes the same evidence:
moon run cli -- analyze
moon run cli -- compatibility
moon run cli -- benchmark
Mustache Compatibility Notes
Stencil intentionally supports a practical core instead of every corner of the full Mustache spec.
Standalone comments and section control lines using the default delimiters
Delimiter changes such as {{=<% %>=}}<%name%>
Current behavior boundaries:
Missing keys render as empty strings
Arrays stringify as [Array] outside section iteration
Objects stringify as [Object] outside section traversal
Missing partials render as empty strings only through legacy APIs; configured APIs can error or preserve the tag
Invalid partial source is ignored by legacy render_with_partials / Template::render_with, while configured APIs report it
Parser sections and partial expansion default to 256 nested levels; RenderOptions can lower or remove the render-time limits
Mustache lambdas, expression evaluation, filesystem partial loading, and dynamic partial names are outside the current scope
These boundaries are documented so callers can rely on stable behavior instead of guessing from implementation details.
Examples
HTML-safe output
let tpl = "<p>{{content}}</p>"
let result = @stencil.render(tpl, { "content": "<script>alert(1)</script>" })
// <p><script>alert(1)</script></p>
let template = "items:\n {{>item}}\ndone"
let partials = {
"item": "- {{name}}\n- ready",
}
let data : Json = { "name": "Stencil" }
let result = @stencil.render_with_partials(template, data, partials)
This repository includes a small runnable CLI example:
moon run cli
Development
Recommended local verification loop:
moon fmt --check
moon check --deny-warn --target all
moon build --target wasm,wasm-gc,js
moon info --target all
git diff --ignore-blank-lines --exit-code
moon test --deny-warn --target wasm,wasm-gc,js
If a system C compiler is available, also run:
moon test --deny-warn --target native
moon build --target native
CI and Toolchain Notes
The official OSC2026 feedback asked for strict formatting, interface generation, type checking, and tests under the latest MoonBit toolchain.
With MoonBit CLI moonc v0.10.3 or newer, strict warning mode is available on
moon check and moon test, but not exposed on moon fmt or moon info.
Hosted CI runs moon fmt --check src because the 0.10.3 formatter accepts the
executable package metadata in cli/moon.pkg, while newer formatters may
rewrite that metadata. The complete CLI package remains covered by check,
build, interface generation, and multi-target tests:
moon run cli -- benchmark (deterministic benchmark smoke check)
The local acceptance script additionally runs full moon fmt --check under the
pinned 0.10.3 toolchain.
The executable package metadata intentionally remains compatible with the
competition-pinned 0.10.3 toolchain. With newer formatters, CI and the local
acceptance script check src formatting while moon check, moon build,
moon info, and all target tests validate the complete CLI package.
The reproducible benchmark uses one compiled-template workload: the CLI compiles
the four-item catalog template once and renders it 200 times. The PowerShell
wrapper additionally records end-to-end moon run startup cost. It is documented in
docs/performance.md and can be run with:
The CLI reports the workload, iteration count, output length, checksum, and a
consistency flag so correctness regressions are distinguishable from timing noise.
License
This project is licensed under the MIT License. See LICENSE
and NOTICE for the project attribution and compliance note.
关于
一个用 MoonBit 编写的轻量级模板引擎库,支持变量插值、条件渲染、列表迭代、模板组合等能力,基于 MoonBit 内置 Json 类型实现数据绑定,适用于 Web 渲染、代码生成、文档输出等场景。
Stencil
Stencil is a lightweight Mustache-style template engine for MoonBit. It focuses on a practical, documented core: variable interpolation, sections, inverted sections, partials, comments, dotted-path lookup, changing delimiters, and safe HTML escaping by default.
Why this project
render,compile, and partial-aware rendering helpers.{{name}}escapes HTML automatically.Feature Summary
{{name}}{{{html}}}and{{&html}}{{#items}}...{{/items}}{{^items}}...{{/items}}{{user.profile.name}}{{.}}{{! ignored }}{{=<% %>=}}<%name%>PartialStorefor reusable partial registries, validation, dependency inspection, and startup compilationInstallation
Add the package to your MoonBit module:
Package page: LL124-Arch/stencil on Mooncakes
Or import it directly in code:
Quick Start
Output:
API
render(template : String, data : Json) -> String raise TemplateErrorCompile and render a template in one step.
compile(source : String) -> Template raise TemplateErrorCompile a template once and reuse it with different JSON inputs.
Template::render(self : Template, data : Json) -> StringRender a precompiled template with the provided context.
render_with_partials(template : String, data : Json, partials : Map[String, String]) -> String raise TemplateErrorRender a template while supplying partial sources by name.
Template::render_with(self : Template, data : Json, partials : Map[String, String]) -> StringRender a precompiled template with named partial sources.
RenderOptionsand checked renderingrender_with_optionsandrender_with_options_and_partialsexpose explicit policies for HTML escaping, nesting/partial/output limits, missing partials, and recursive partial detection.MissingPartialPolicy::Emptypreserves the legacy behavior;Erroris recommended for production validation andPreserveis useful for multi-pass pipelines.PartialStorePartialStorecentralizes named sources and supportsset,remove,names,validate,dependencies,compile_all, and option-aware rendering. This provides a deterministic application boundary without introducing filesystem or network access into the library.TemplateCatalogand batch renderingTemplateCatalogis the application-level boundary for a group of named templates. It owns source text, lazily compiled templates, revision numbers, dependency edges, health reports, and cache invalidation when a source is replaced. This is useful for page bundles, email layouts, documentation builds, and configuration generators that must validate a manifest before serving it.render_jobspreserves successful outputs and failure details instead of discarding the whole batch.dependency_reportcatches missing partials and cycles, whilehealth_reportcombines compile, syntax, and dependency checks. See catalog usage and operational notes.Diagnostics and analysis
diagnose(source)returns stable severity, message, byte index, line, and column fields without raising.compile(source).stats()reports node counts, variables, sections, partials, depth, delimiter use, and a complexity score. The CLI exposes the same evidence:Mustache Compatibility Notes
Stencil intentionally supports a practical core instead of every corner of the full Mustache spec.
The compatibility target is the official Mustache manual. The exact implemented subset, intentional deviations, and unsupported host-dependent extensions are recorded in docs/mustache-compatibility.md.
Supported behavior:
{{.}}{{=<% %>=}}<%name%>Current behavior boundaries:
[Array]outside section iteration[Object]outside section traversalrender_with_partials/Template::render_with, while configured APIs report itRenderOptionscan lower or remove the render-time limitsThese boundaries are documented so callers can rely on stable behavior instead of guessing from implementation details.
Examples
HTML-safe output
Reusing a compiled template
Partials with indentation
Output:
Production-style email snippet
CLI Demo
This repository includes a small runnable CLI example:
Development
Recommended local verification loop:
If a system C compiler is available, also run:
CI and Toolchain Notes
The official OSC2026 feedback asked for strict formatting, interface generation, type checking, and tests under the latest MoonBit toolchain.
With MoonBit CLI
moonc v0.10.3or newer, strict warning mode is available onmoon checkandmoon test, but not exposed onmoon fmtormoon info. Hosted CI runsmoon fmt --check srcbecause the 0.10.3 formatter accepts the executable package metadata incli/moon.pkg, while newer formatters may rewrite that metadata. The complete CLI package remains covered by check, build, interface generation, and multi-target tests:moon fmt --check srcmoon check --deny-warn --target allmoon build --target wasm,wasm-gc,jsmoon info --target allgit diff --ignore-blank-lines --exit-code(interface drift check; ignores toolchain-only blank-line churn)moon test --deny-warn --target ...moon run cli -- benchmark(deterministic benchmark smoke check)The local acceptance script additionally runs full
moon fmt --checkunder the pinned 0.10.3 toolchain.The executable package metadata intentionally remains compatible with the competition-pinned 0.10.3 toolchain. With newer formatters, CI and the local acceptance script check
srcformatting whilemoon check,moon build,moon info, and all target tests validate the complete CLI package.Both GitHub Actions and GitLink CI are included:
OSC2026 Self-Check
For competition maintenance, run:
The script checks:
See docs/acceptance-checklist.md for the requirement-to-evidence mapping used in this repository.
Performance Baseline
The reproducible benchmark uses one compiled-template workload: the CLI compiles the four-item catalog template once and renders it 200 times. The PowerShell wrapper additionally records end-to-end
moon runstartup cost. It is documented in docs/performance.md and can be run with:The CLI reports the workload, iteration count, output length, checksum, and a consistency flag so correctness regressions are distinguishable from timing noise.
License
This project is licensed under the MIT License. See LICENSE and NOTICE for the project attribution and compliance note.