sercrod

*let

Summary

*let runs a small piece of JavaScript in Sercrod’s sandbox to define local helper variables. These variables are available to expressions on the same element and all of its descendants. Newly created variable names are also promoted into the host data so that later elements inside the same <serc-rod> can read them.

Alias:

Basic example

Compute a derived value once and reuse it in the element’s subtree:

<serc-rod id="invoice" data='{"price": 1200, "qty": 3}'>
  <p *let="total = price * qty">
    Subtotal: <span *print="total"></span> JPY
  </p>
</serc-rod>

In this example:

Behavior

At a high level, *let behaves like “execute this code in the current data scope and keep any new variables”:

Key points:

Expression model

The value of *let is treated as JavaScript code:

Typical patterns:

What the sandbox does:

Evaluation timing

*let is evaluated early in the per-element pipeline:

As a result:

Example:

<li *let="is_expensive = price > 1000"
    *if="is_expensive">
  <span *print="name"></span>
  <span>(premium)</span>
</li>

Here *if can safely use is_expensive because *let runs first.

Execution model

Conceptually, Sercrod handles *let on an element like this:

  1. Compute the current effective scope effScope for this element:

    • Based on the host data for the <serc-rod>.
    • Including any variables from surrounding loops or parent *let directives.
  2. If the element has *let or n-let:

    • Create a new scope object whose prototype points to effScope.
    • Copy the current values from effScope into this new scope.
    • Inject $parent so that branch-local code has access to the nearest ancestor Sercrod’s data.
    • Inject any methods that were registered via *methods and Sercrod’s internal helper methods.
  3. Run the *let code inside Sercrod’s sandbox:

    • Reads go through the scope or, as a fallback, the real global environment for standard objects (such as Math).
    • Writes update only the local scope object created for *let.
    • Unknown identifiers become new variables on this local scope when you assign to them.
  4. After the code runs:

    • Sercrod copies any variables that did not exist in the host data into the host data object.
    • Existing host data keys are not overwritten by *let.
    • The new scope becomes the effective scope for this element and its descendants.
  5. Sercrod schedules a re-render if necessary so that bindings see the updated values.

This model keeps *let local by default, but still lets you share newly defined helper variables with other elements in the same <serc-rod>.

Variable creation and promotion

*let distinguishes between:

Rules:

In practice:

Scope layering and parent access

Inside *let:

The local *let scope then becomes the base scope for:

Use with conditionals and loops

*let works closely with conditionals and loops.

On the same element as *if / *elseif / *else:

Example:

<li *if="kind === 'user'" *let="label = user.name">
  <span *print="label"></span>
</li>
<li *elseif="kind === 'guest'" *let="label = guest.nickname">
  <span *print="label"></span>
</li>
<li *else *let="label = 'Unknown'">
  <span *print="label"></span>
</li>

Here:

Inside loops:

Example:

<ul *each="item of items">
  <li *let="label = item.name + ' (#' + item.id + ')'">
    <span *print="label"></span>
  </li>
</ul>

Each <li> computes its own label using the item from that iteration.

You can also use *let on the loop container:

<ul *each="item of items"
    *let="hasItems = !!items && items.length > 0">
  <li *if="hasItems" *print="item.name"></li>
</ul>

hasItems is computed before the *each and is available to each child *if and *print.

Best practices

Additional examples

Sharing a derived variable with siblings:

<serc-rod id="totals" data='{"items":[{"name":"A","price":100},{"name":"B","price":200}]}'>
  <section *let="
    subtotal = 0;
    for(const item of items){
      subtotal = subtotal + item.price;
    }
  ">
    <p>Subtotal: <span *print="subtotal"></span> JPY</p>
  </section>

  <!-- Later element can also see subtotal, because it was a new name -->
  <p>Summary: total amount is <span *print="subtotal"></span> JPY</p>
</serc-rod>

Using $parent data in a nested component:

<serc-rod id="root" data='{"currency":"JPY"}'>
  <serc-rod id="child" data='{"price": 500}'>
    <p *let="text = price + ' ' + $parent.currency">
      <span *print="text"></span>
    </p>
  </serc-rod>
</serc-rod>

Here:

Notes