sercrod

*template

Summary

*template marks a subtree as a reusable template. The subtree is registered under a name in the current Sercrod world and is not rendered where it is declared. Later, *include can refer to that name and copy the template’s inner content into a real element. If you want to share templates across files, you usually combine *template with *import: *import loads HTML from another file, and any *template declarations inside that HTML are then available to *include.

Key points:

Basic example

A simple reusable card template and a loop that includes it:

<serc-rod
  id="app"
  data='{
    "users": [
      { "name": "Alice", "bio": "Loves minimal HTML." },
      { "name": "Bob",   "bio": "Enjoys fast renderers." }
    ]
  }'
>
  <!-- Declare a reusable template named "userCard" -->
  <template *template="'userCard'">
    <article class="user-card">
      <h2 *print="user.name"></h2>
      <p *print="user.bio"></p>
    </article>
  </template>

  <!-- Use the template inside a loop -->
  <section *each="user of users">
    <div *include="'userCard'"></div>
  </section>
</serc-rod>

Behavior:

Behavior

Alias:

Name resolution

*template uses the shared helper _resolve_template_name(raw_text, scope, { el, mode: "template" }).

For an attribute like:

<template *template="expr">...</template>

Sercrod resolves the name as follows:

  1. Convert the attribute to a string and trim it.

  2. Try to evaluate it as an expression:

    • this.eval_expr(src, scope, { el, mode: "template", quiet: true }) is called.
    • If the evaluation yields a non-null, non-undefined value, Sercrod turns it into a string, trims it, and if the result is non-empty, that becomes the name.
  3. If evaluation does not produce a usable name, fall back to identifier rules:

    • If the original trimmed text matches /^[A-Za-z_][A-Za-z0-9_-]*$/, it is treated as a name directly.
    • Otherwise, name resolution fails.

If name resolution fails:

Examples:

World-local registration and duplicates

Templates are registered per Sercrod world:

Duplicate names:

Evaluation timing

*template runs early in the element pipeline:

From the rendered document’s point of view:

Execution model

Conceptually, Sercrod does the following for *template:

  1. Detect declaration:

    • If the current node has *template or n-template, treat it as a template definition.
  2. Resolve the name:

    • Use _resolve_template_name with mode: "template" to get a non-empty string.
  3. Handle invalid names:

    • If the name is empty or invalid, optionally warn and return without rendering this node.
  4. Handle duplicates:

    • If the current world’s _template_registry already has that name, optionally warn and return without rendering this node.
  5. Register a prototype:

    • Deep-clone the node (including its attributes and children).
    • Store the clone as the prototype for that template name in _template_registry.
    • Store basic visibility attributes (inert, hidden, aria-hidden) in _template_attr_snapshot for possible future use.
  6. Skip output:

    • Do not append the original node to the DOM of the rendered result.
    • Do not process its children at the declaration location.

Later, when *include uses this name, Sercrod:

Variable creation

*template does not create any new variables by itself.

Variables such as user, item, or config that appear inside the template body must be supplied by the scope at the call site (where *include is used).

Scope layering

Declaration-time scope and usage-time scope are distinct:

Implications:

Parent access

When the template content is rendered (via *include):

*template does not add any extra parent layer over this. It only defines where the content comes from.

Use with conditionals and loops

There are two places where conditionals and loops can appear:

Declaration element:

Template body:

Use with *include and *import

*template is the definition side of the template system. *include and *import are consumers, but in different ways.

In other words:

Unsupported combinations on one element:

For these patterns, *template always runs first and suppresses the other directive for that node. Always separate declaration and usage onto different elements.

Comparison: *include vs *import

From the template system’s point of view:

Typical usage patterns:

The string passed to *import is treated purely as an URL from Sercrod’s perspective. If you use patterns like "/partials/card.html:card", the :card part is just part of the URL and is not parsed specially by Sercrod itself. Any such semantics (for example, serving only one named fragment from a combined file) must be implemented on the server side.

Best practices

Additional examples

Templates for a page shell:

<serc-rod
  id="page"
  data='{"title":"Sercrod Docs","subtitle":"Attribute-first templates"}'
>
  <template *template="'pageShell'">
    <header>
      <h1 *print="title"></h1>
      <p *print="subtitle"></p>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <small>Sercrod example</small>
    </footer>
  </template>

  <section *include="'pageShell'">
    <p>This paragraph is rendered inside the <main> of the pageShell template.</p>
  </section>
</serc-rod>

Templates loaded from another file via *import:

<serc-rod id="root">
  <!-- Load external HTML that may define templates like "card" or "layoutHeader" -->
  <div *import="'/partials/common-templates.html'"></div>

  <!-- After the import, those templates are available in this world -->
  <section *include="'layoutHeader'"></section>
  <div *include="'card'"></div>
</serc-rod>

Notes