Skip to content

ringroad-renderer

The Rust rendering engine, architecture, module map, and design constraints.

Updated View as Markdown

ringroad-renderer (crates/ringroad-renderer/) is the rendering engine for the Ringroad storefront. It is extracted from the monolithic API binary so it can be tested, reused, and iterated on independently, without a database connection.

Design constraints

  • Zero database dependency. All data access flows through the DataSources trait. The API binary (crates/ringroad) implements it with SeaORM queries against PostgreSQL; tests use a stub implementation with fixed fixtures.
  • One engine per API process. Engine is built once at startup from a themes/ directory, holding all loaded themes, the MiniJinja environment with every template pre-compiled, and the shared DataSources handle.
  • Lenient by default. MiniJinja runs with UndefinedBehavior::Lenient, so merchant-edited templates that reference missing variables render as empty strings instead of crashing.
  • Lazy data graph. The old two-pass metafield collector is replaced by lazy Object wrappers: data loads on first property access, inside tokio::task::spawn_blocking, cached per request.

Module map

Module Purpose
engine/ MiniJinja environment, filters, page rendering orchestration, template resolution, HTML/script emission
section/ Node schemas, page configs, recursive node resolution and rendering, node objects
schema/ The 36-type settings system, validation, defaults, color cascade
theme/ theme.json manifest loading and the theme-directory walk (schema/stylesheet/javascript extraction)
data/ Domain types, the DataSources trait, lazy Object wrappers, storefront context
asset/ CSS variable generation from color settings, .jinja asset rendering
cli/ serve (dev server), build (static assets), init (scaffold a theme)
keys.rs Reserved config keys: @root:settings, @root:header-group, @root:footer-group
error.rs Crate-wide Error enum and Result alias

The Engine lifecycle

flowchart LR
  D[themes/ dir] --> W[theme::loader::walk_themes]
  W -->|per .jinja file| R[register template in MiniJinja]
  W -->|"{% schema %}"| M[ThemeManifest.nodes]
  W -->|stylesheet/js blocks| P[ThemeAssetPipeline]
  W --> E[Engine]
  DS[DataSources impl] --> E
  E --> PG[render_page]
  E --> FR[render_node_fragment]
  E --> BA[build_assets]

Engine::new_from_dir(ds, themes_dir, global_scripts):

  1. creates the MiniJinja environment and sets UndefinedBehavior::Lenient,
  2. registers the four custom filters (money, pluralize, newlines_to_br, render_text) and the render_node function,
  3. walks every theme subdirectory: registers all .jinja templates under "{slug}/{dir}/{relpath}", extracts {% schema %} blocks into node schemas, and extracts {% stylesheet %} / {% javascript %} blocks into a per-theme asset pipeline,
  4. validates every setting in every schema, a bad theme fails startup,
  5. stores manifests, pipelines, and the DataSources handle.

The loader strips the extension blocks before MiniJinja compiles anything (see Theme loading).

What the crate is used for

  • Storefront pages, Engine::render_page() renders the full page (header-group + page nodes + footer-group + layout shell).
  • Preview / hot-swap, render_node_fragment() renders one node’s subtree in isolation for the editor’s setting-change swap.
  • Static asset builds, build_assets() emits content-hashed combined.{hash}.css / .js for CDN caching.
  • CLI development, ringroad-renderer serve boots a stub-context preview server that hot-reloads on theme edits.

The test suite (tests/) defends the contracts end to end: schema parsing, page rendering, section trees, CSS subsetting, asset builds, data sources, and a full smoke test over the real themes/default.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close