microsoft-fast-build
A server-side renderer for FAST declarative HTML templates, written in pure Rust with no external dependencies.
It takes FAST declarative HTML template syntax and a JSON state object, resolves bindings, evaluates conditionals, iterates repeats, and renders custom elements — producing static HTML ready to be served.
Installation
Add the crate to your Cargo.toml:
[]
= "0.1"
Or use cargo add:
Usage
Render with a JSON string
use render_template;
let html = render_template?;
// html == "<h1>Hello, World!</h1>"
Render with a parsed JsonValue
use ;
use HashMap;
let mut map = new;
map.insert;
let state = Object;
let html = render?;
// html == "<p>Alice</p>"
Both functions return Result<String, RenderError>. See Error Handling.
Template Syntax
Content Bindings
| Syntax | Description |
|---|---|
{{expr}} |
HTML-escaped value from state |
{{{expr}}} |
Raw (unescaped) HTML value from state |
{{title}}
{{{richContent}}}
Single-Brace Passthrough
Single-brace expressions ({expr}) are FAST client-side-only bindings (event handlers, attribute directives). They are never interpreted by the server renderer and pass through verbatim.
<!-- Passes through unchanged -->
{{label}}
Conditional Rendering — <f-when>
Admin panel
Member content
Active
Has items
Either
Both
Supported operators: ==, !=, >=, >, <=, <, ||, &&, !
Operators can be chained — && binds tighter than ||, matching standard precedence:
Admin
Any of the above
Visible
Right-hand operands can be string literals ('foo'), boolean literals (true/false), number literals (42), or other state references.
Array Iteration — <f-repeat>
{{item.name}} — {{title}}
Inside <f-repeat>, {{item}} resolves to the current loop variable. Other bindings fall back to the root state (e.g. {{title}} above).
Nesting
<f-when> and <f-repeat> can be nested arbitrarily:
{{cell}}
Property Access and Array Indexing
Dot-notation and numeric indices are both supported:
{{user.address.city}}
{{list.0.name}}
Custom Elements
When given a Locator, the renderer detects custom elements (any tag whose name contains a hyphen, excluding f-when/f-repeat) and renders their corresponding HTML template files.
Setting up a Locator
use Locator;
// From glob patterns — scans the filesystem
let locator = from_patterns?;
// Or build manually (useful in tests)
let mut locator = from_templates;
locator.add_template;
Glob patterns support * (any characters within one path segment), ** (any number of segments), and ? (any single character).
Rendering with a Locator
use ;
let locator = from_patterns?;
let html = render_template_with_locator?;
Attribute → State Mapping
Attributes on a custom element become the state passed to its template:
| Attribute form | State entry |
|---|---|
disabled (boolean, no value) |
{"disabled": true} |
label="Click me" |
{"label": "Click me"} |
count="42" |
{"count": 42} |
foo="{{bar}}" |
{"foo": <value of bar from parent state>} |
The last form is a property binding with renaming: foo="{{bar}}" resolves bar from the parent state and passes it into the child template under the key foo.
Output Format
The renderer wraps the rendered template in Declarative Shadow DOM:
<!-- Input -->
light DOM
<!-- Output -->
Submit
light DOM
Custom elements that have no matching template in the locator are passed through verbatim.
Error Handling
All render functions return Result<String, RenderError>. RenderError is an enum:
| Variant | Triggered by |
|---|---|
UnclosedBinding |
{{ with no closing }} |
UnclosedUnescapedBinding |
{{{ with no closing }}} |
EmptyBinding |
{{}} — blank expression |
MissingState |
{{key}} where key is absent from state |
UnclosedDirective |
<f-when> / <f-repeat> with no matching close tag |
MissingValueAttribute |
Directive missing value="{{…}}" attribute |
InvalidRepeatExpression |
Repeat value not in item in list format |
NotAnArray |
<f-repeat> binding resolves to a non-array value |
DuplicateTemplate |
Two template files resolve to the same element name |
TemplateReadError |
A matched template file could not be read |
JsonParse |
Invalid JSON passed to render_template |
Every error message includes a description of the problem and a snippet of the template near the error site to aid debugging:
missing state: '{{title}}' has no matching key in the provided state — template: "…<h1>{{title}}</h1>…"
unclosed binding '{{name': no closing '}}' found to end the expression — template: "Hello {{name"
duplicate template: element '<my-button>' is defined in multiple files: ./a/my-button.html, ./b/my-button.html