theme/loader.rs walks a themes/ directory and produces fully-populated
ThemeManifest + ThemeAssetPipeline pairs, one pass per .jinja file
that registers the template with MiniJinja, extracts the schema, extracts
stylesheets, and extracts JavaScript.
The walk
walk_themes(themes_dir, env):
- Reads each subdirectory of
themes_dir, the directory name is the slug. - Loads
theme.jsoninto aThemeManifest(theme/manifest.rs) and validates its settings. - Registers every
.jinjafile underlayout/,nodes/,templates/, andsnippets/(recursively) with MiniJinja under"{slug}/{dir}/{relpath}". - Scans
nodes/for{% schema %}blocks, deserializing each into aNodeSchemaand inserting it intomanifest.nodeskeyed by file stem. Invalid schema or settings → engine startup fails with the theme and node named. - Populates
style_ownership(which CSS keys each rendered type pulls in, including itsaccepts/slot child types), then pre-computescombined_css/combined_js.
Stripping metadata blocks
Before MiniJinja compiles a template, strip_metadata_blocks removes
every {% schema %} … {% endschema %}, {% stylesheet %} …
{% endstylesheet %}, and {% javascript %} … {% endjavascript %}
pair, this is what stops MiniJinja’s “unknown statement” rejection.
extract_role_tag separately pulls a leading @editor:section or
@editor:any marker off the source (the role also lands in the schema if
the JSON has one).
The asset pipeline
pub struct ThemeAssetPipeline {
pub section_styles: HashMap<String, String>, // style key -> raw CSS
pub section_scripts: HashMap<String, String>, // style key -> raw JS
pub style_ownership: HashMap<String, Vec<String>>, // type -> CSS keys
pub combined_css: String,
pub combined_js: String,
}Style keys: __layout__ for files in layout/, otherwise the file stem
(nodes/hero.jinja → hero). Because snippets are registered too, a
snippet’s stylesheet block would be keyed by the snippet’s stem.
page_css(rendered_types), per-page subsetting
__layout__CSS first.- For each rendered node type: its own stylesheet, plus the stylesheets
of every
accepts/ slot child type (viastyle_ownership), deduplicated. - Returns the concatenated subset, the page emits it as one
<style>block in<head>.
Combined output
build_combined_css concatenates all styles in sorted-key order;
build_combined_js concatenates every script block wrapped in an IIFE
((function() { … })();). The engine content-hashes these into
combined.{hash}.css / .js for CDN-cached asset serving.
The manifest
theme/manifest.rs, the parsed theme.json:
pub struct ThemeManifest {
pub name: String,
pub version: String,
pub settings: Vec<SettingDefinition>, // global settings schema
pub nodes: HashMap<String, NodeSchema>, // from nodes/*.jinja schemas
pub presets: HashMap<String, PageConfig>, // default page trees
pub section_groups: HashMap<String, SectionGroupSchema>,
}SectionGroupSchema { name, max_sections, allowed_sections, preset }
models the @root:header-group / @root:footer-group entries.
Template names
Every registered template is addressable by namespaced name:
| File | Template name |
|---|---|
themes/default/layout/theme.jinja |
default/layout/theme.jinja |
themes/default/nodes/hero.jinja |
default/nodes/hero.jinja |
themes/default/templates/account/order.jinja |
default/templates/account/order.jinja |
themes/default/snippets/ui/button.jinja |
default/snippets/ui/button.jinja |
Node bodies resolve dynamically as {slug}/nodes/{node_type}.jinja; page
templates via resolve_template_path (see
Template resolution).
Asset processing
asset/process.rs::process_assets(env, theme_dir, output_dir, settings_ctx) renders *.jinja files found in assets/ through MiniJinja
(stripping the .jinja suffix in the output), copies everything else
verbatim, and skips subdirectories, no-op when assets/ is missing. The
CLI build command uses this to pre-render theme assets into dist/.