stampino

    0.6.2 • Public • Published

    Stampino

    Stampino is a fast and extremely powerful HTML template system, where you write dynamic templates using real HTML <template> tags:

    <template id="my-template">
      <h1>Hello {{ name }}</h1>
    </template>

    Overview

    Stampino uses HTML <template> tags to define templates, lit-html for the underlying template rendering, and jexpr for binding expressions.

    Stampino is based on the idea that a template defines a function from data to DOM, so it transforms <template> elements into lit-html render functions. Control flow, template composition, and extensibility are built on top of function composition.

    This approach leads to a low-cost for features like conditionals and repeating, which are just <template>s themselves:

    <template id="my-template">
      <h2>Messages</h2>
    
      <template type="if" if="{{ important }}">
        <p class="important">These messages are important</p>
      </template>
    
      <template type="repeat" repeat="{{ messages }}">
        <p>{{ item.text }}</p>
      </template>
    </template>

    <template type="if"> and <template type="repeat"> are not hard-coded into the core of Stampino. Instead they are just default template handlers that are matched against the "type" attribute. Users can implement their own template handlers just like if and repeat.

    Use cases

    Stampino is very useful for custom elements that want to allow custom rendering or user-extensibility.

    Consider the example of an <npm-packages> element that fetches a list of npm packages and renders it, letting users override a default shadow-DOM template. The element may accept a template as a child and render it with Stampino and the package data:

    <script type="module" src="/npm-packages.js"></script>
    <npm-packages query="router">
      <template>
        <h1>{{ package.name }}</h1>
        <h2>{{ package.description }}</h2>
        <p>Version: {{ package.version }}</p>
      </template>
    </npm-packages>

    When Stampino processes a template, it creates a lit-html template function.

    To render this template:

    <template id="my-template">
      <h1>Hello {{ name }}</h1>
    </template>

    Pass it to prepareTemplate() and use lit-html to render the returned function:

    import * as stampino from 'stampino';
    import {render} from 'lit';
    
    const templateElement = document.querySelector('#my-template');
    
    // Returns a lit-html template function that accepts data and
    // returns a renderable TemplateResult
    const myTemplate = stampino.prepareTemplate(templateElement);
    
    render(myTemplate({name: 'World'}), document.body);

    prepareTemplate() takes options for template handlers (to handle <template type=>), renderers (to handle <template name=>), and a super template.

    Note: this API is known to have some flaws and will definitely change!

    Features

    Binding Expressions

    Expressions are delimted by double braces - {{ }}. They can appear in attribute values and as text/children of elements.

    Expressions are interpreted by jexpr, which provides a fast and expressive expression evaluator and supports a subset of JavaScript expressions.

    Expressions can include variables, property access, arithmetic, function and method calls, lists, maps and filters.

    <template>
      <p>What's the answer to life, the universe and everything?</p>
      <p>{{ 6 * 7 }}</p>
    </template>

    See the jexpr README for more expression features.

    Property, event, and boolean attribute bindings

    Stampino uses lit-html's syntax for binding to properties, events, and boolean attributes.

    Binding Type Prefix Example
    Attribute none class=${c}
    Property . .foo=${bar}
    Event @ @click=${handleClick}
    Boolean Attribute ? ?disabled=${disabled}

    Control flow

    Stampino control flow is based on nested <template> elements with a type attribute. Stampino comes with two built-in template handlers, if and repeat

    if templates

    For conditional rendering, use type="if" and an if attribute.

    <template id="my-template">
      <template type="if" if="{{ condition }}">
        Render when <code>condition</code> is true.
      </template>
    </template>

    repeat templates

    For repeated templates use type="repeat" and a repeat attribute. The repeat handler automatically adds an item loop variable to the template's scope.

    <template id="my-template">
      <ul>
        <template type="repeat" repeat="{{ items }}">
          <li>{{ item }}</li>
        </template>
      </ul>
    </template>

    Inheritance

    Stampino supports template inheritance similar to how the popular Jinja library does.

    Because Stampino does not automatically find templates in the DOM, even for simple rendering, specifying inheritance is done entirely out-of-band. Set up code must find the template and it's super template, then pass both to prepareTemplate().

    <template id="base-template">
      This the base template, that defines a "block"
      <template name="A">
        This is a block with fallback content.
      </template>
    </template>
    
    <template id="my-template">
      <template name="A">
        This is a sub-template providing new content for a block.
      </template>
    </template>
    import {render} from 'lit-html';
    import {prepareTemplate} from 'stampino';
    
    const baseEl = document.querySelector('#base-template');
    const templateEl = document.querySelector('#my-template');
    
    const myTemplate = prepareTemplate(
      templateEl,
      undefined, // use default handlers
      undefined, // use default (empty) renderers
      baseEl); // super template
    
    // Note: the above API isn't great. It'll change
    
    // Use lit-html to render:
    render(myTemplate(data), container);

    Templates can explicitly include a call to the super template with <template name="super">. This lets the sub-template render content before and after the super template:

    <template id="my-template">
      This is before the super-template
      <template name="super">
        <template name="A">
          This is a sub-template providing new content for a block.
        </template>
      </template>
      This is after the super-template
    </template>

    Extensibility

    Most template systems have built-in control-flow constructs like 'if' and 'repeat'. Stampino includes these too, but they are implemented as extensions called template handlers, just like user-provided handlers.

    Handlers are functions that implement this signature:

    function handler<Model>(
      template: HTMLTemplateElement,
      model: Model,
      handlers: Handlers,
      renderers: renderers,
    ): TemplateResult|Directive|string|number|((string|number)[]);

    They can return any lit-html rendereable object, including strings, numbers, arrays, TemplateResults, or directives.

    Handlers are passed to prepareTemplate():

    import {prepareTemplate, evaluateTemplate} from 'stampino';
    
    const myTemplate = prepareTemplate(
      element,
      {
        // Renders a template twice
        'echo': (template, model, handlers, renderers) => {
          return [
            evaluateTemplate(template, model, handlers, renderers),
            evaluateTemplate(template, model, handlers, renderers)];
        },
      }
    );

    Handlers are referenced inside templates via the type attribute:

    <template>
      <h1>Do I head an echo?</h1>
      <template type="echo"> Yes, I hear an echo! </template>
    </template>

    You can think of this very much like <templates type= is syntax for a function call. Here's the entire implementation of the 'if' handler:

    handlers: {
      'if': (template, model, handlers, renderers) => {
        const ifAttribute = template.getAttribute('if');
        if (ifAttribute !== null && getSingleValue(ifAttribute, model)) {
          return evaluateTemplate(template, model, handlers, renderers);
        }
      return undefined;
      },
    },

    Note: getSingleValue() evaluates an expression against a model. It's provided by the Stampino library.

    Status

    Stampino is a side-project and development typically happens in bursts. Things are changing quickly, so be careful. If you find a problem or are intested in contributing please reach out in the issues.

    Install

    npm i stampino

    DownloadsWeekly Downloads

    7

    Version

    0.6.2

    License

    MIT

    Unpacked Size

    75.7 kB

    Total Files

    10

    Last publish

    Collaborators

    • justinfagnani