sercrod

*compose

Summary

*compose composes the contents of a single element from the result of an expression and writes it into the element as HTML via the html filter. It is the structural counterpart of *innerHTML and has an alias n-compose.

Key ideas:

Alias:

Basic example

A basic composition from a precomputed HTML string:

<serc-rod id="app" data='{
  "cardHtml": "<div class=&quot;card&quot><h2>Title</h2><p>Body</p></div>"
}'>
  <section class="wrapper">
    <div class="slot" *compose="cardHtml"></div>
  </section>
</serc-rod>

Behavior:

Behavior

At a high level, *compose:

Details:

Evaluation timing

Within _renderElement, *compose takes part in the following order:

After *compose:

Implications:

Execution model

Conceptually, the engine behaves as follows when it hits *compose:

  1. Decide which attribute to read:

    • If the element has any of *compose, n-compose, *innerHTML, or n-innerHTML, one of those names is chosen as srcAttr.
    • Only one attribute is used; others are ignored for this step.
  2. Read the expression:

    • expr = node.getAttribute(srcAttr).
  3. Evaluate the expression:

    • v = eval_expr(expr, scope, { el: node, mode: "compose" or "innerHTML" }).
    • Any variables in the expression are resolved from the merged scope and special helpers.
  4. Normalize the result:

    • If v is null or false, treat it as "".
    • Otherwise, use it as raw.
  5. Call the html filter:

    • ctx = { el, expr, scope }.
    • htmlString = Sercrod._filters.html(raw, ctx).
  6. Write into innerHTML:

    • el.innerHTML = htmlString.
  7. On error:

    • If evaluation or filtering throws, Sercrod logs a [Sercrod warn] html filter: message (when error.warn is enabled).
    • el.innerHTML is set to an empty string.

After that, the standard rendering pipeline continues, including child rendering and post-apply actions.

Integration with the html filter

*compose is tightly coupled with the html filter:

Security considerations

*compose ultimately writes to innerHTML and does not perform any built-in escaping. This is a standard XSS risk surface: if the value you compose contains active HTML (such as script tags or event handlers) and that content comes from untrusted input, it may execute in the browser.

This manual’s role is to clearly document that behavior, not to claim that a particular pattern is always safe. Even when you apply sanitization in the html filter, no single step can guarantee that all XSS vectors are eliminated. In practice, XSS protection is a multi-layer task that includes server-side validation, output encoding, and careful template design.

Within that broader context, Sercrod expects at least the following minimum precautions when using *compose:

These notes should be read as a “speed limit sign”: they describe how the engine behaves and where the risks lie, and they indicate a minimum level of care. They do not replace application-level security measures, especially on the server side.

Variable creation and scope layering

*compose does not create any new local variables.

Scope behavior:

Parent access

Because *compose is purely expression-driven and does not alter scope:

Use with conditionals and loops

*compose often appears together with conditionals or in contexts controlled by loops, but it is not itself a loop or condition.

Use with *include and *import

*include / *import and *compose all affect the children of a host, but at different stages:

As a result:

Guidance:

Best practices

Examples

Composition from a pre-rendered fragment:

<serc-rod id="app" data='{
  "fragments": {
    "hero": "<h1>Welcome</h1><p>This is Sercrod.</p>"
  }
}'>
  <header *compose="fragments.hero"></header>
</serc-rod>

Using a method to produce HTML:

<serc-rod id="app" data='{"items":[{"name":"Alpha"},{"name":"Beta"}]}'>
  <script type="application/json" *methods='{
    "renderList": function(items){
      return "<ul>" + items.map(function(it){
        return "<li>" + it.name + "</li>";
      }).join("") + "</ul>";
    }
  }'></script>

  <section *compose="renderList(items)"></section>
</serc-rod>

Custom html filter for template names (conceptual pattern):

<script>
  // Before Sercrod loads
  window.__Sercrod_filter = {
    html: function(raw, ctx){
      // Example: treat values starting with "tpl:" as template names
      if(typeof raw === "string" && raw.startsWith("tpl:")){
        var name = raw.slice(4);
        // Resolve name to preloaded HTML (implementation-specific)
        var html = window.TEMPLATES && window.TEMPLATES[name];
        return html || "";
      }
      return raw;
    }
  };
</script>

<serc-rod id="app" data='{"currentTpl":"tpl:card"}'>
  <div class="card-container" *compose="currentTpl"></div>
</serc-rod>

In this pattern:

Notes