Skip to content
This repository has been archived by the owner on Feb 14, 2019. It is now read-only.

Releases: github/lit-html

v0.9.0

18 Apr 08:52
c384c23
Compare
Choose a tag to compare

lit-html

HTML templates, via JavaScript template literals

Build Status

Overview

lit-html lets you write HTML templates with JavaScript template literals, and efficiently render and re-render those templates to DOM.

import {html, render} from 'lit-html';

// This is a lit-html template function. It returns a lit-html template.
const helloTemplate = (name) => html`<div>Hello ${name}!</div>`;

// Call the function with some data, and pass the result to render()

// This renders <div>Hello Steve!</div> to the document body
render(helloTemplate('Steve'), document.body);

// This updates to <div>Hello Kevin!</div>, but only updates the ${name} part
render(helloTemplate('Kevin'), document.body);

lit-html provides two main exports:

  • html: A JavaScript template tag used to produce a TemplateResult, which is a container for a template, and the values that should populate the template.
  • render(): A function that renders a TemplateResult to a DOM container, such as an element or shadow root.

Announcement at Polymer Summit 2017

Efficient, Expressive, and Extensible HTML Templates video
Efficient, Expressive, and Extensible HTML Templates video

Motivation

lit-html has four main goals:

  1. Efficient updates of previously rendered DOM.
  2. Expressiveness and easy access to the JavaScript state that needs to be injected into DOM.
  3. Standard JavaScript without required build steps, understandable by standards-compliant tools.
  4. Very small size.

How it Works

lit-html utilizes some unique properties of HTML <template> elements and JavaScript template literals. So it's helpful to understand them first.

Tagged Template Literals

A JavaScript template literal is a string literal that can have other JavaScript expressions embedded in it:

`My name is ${name}.`

A tagged template literal is prefixed with a special template tag function:

let name = 'Monica';
tag`My name is ${name}.`

Tags are functions of the form: tag(strings, ...values), where strings is an immutable array of the literal parts, and values are the results of the embedded expressions.

In the preceding example, strings would be ['My name is ', '.'], and values would be ['Monica'].

HTML <template> Elements

A <template> element is an inert tree of DOM (script don't run, images don't load, custom elements aren't upgraded, etc) that can be efficiently cloned. It's usually used to tell the HTML parser that a section of the document must not be instantiated when parsed, but by code at a later time, but it can also be created imperatively with createElement and innerHTML.

Template Creation

The first time html is called on a particular template literal it does one-time setup work to create the template. It joins all the string parts with a special placeholder, "{{}}", then creates a <template> and sets its innerHTML to the result. Then it walks the template's DOM and extracts the placeholder and remembers their location.

Every call to html returns a TemplateResult which contains the template created on the first call, and the expression values for the current call.

Template Rendering

render() takes a TemplateResult and renders it to a DOM container. On the initial render it clones the template, then walks it using the remembered placeholder positions, to create Parts.

A Part is a "hole" in the DOM where values can be injected. lit-html includes two type of parts by default: NodePart and AttributePart, which let you set text content and attribute values respectively. The Parts, container, and template they were created from are grouped together in an object called a TemplateInstance.

Rendering can be customized by providing alternate render() implementations which create different kinds of TemplateInstances and Parts, like PropertyPart and EventPart included in lib/lit-extended.ts which let templates set properties and event handlers on elements.

Performance

lit-html is designed to be lightweight and fast (though performance benchmarking is just starting).

  • It utilizes the built-in JS and HTML parsers - it doesn't include any expression or markup parser of its own.
  • It only updates the dynamic parts of templates - static parts are untouched, not even walked for diffing, after the initial render.
  • It uses cloning for initial render.

This should make the approach generally fast and small. Actual science and optimization and still TODOs at this time.

Features

Simple expressions and literals

Anything coercible to strings are supported:

const render = () => html`foo is ${foo}`;

Attribute-value Expressions

const render = () => html`<div class="${blue}"></div>`;

SVG Support

To create partial SVG templates - template that will rendering inside and <svg> tag (in the SVG namespace), use the svg template tag instead of the html template tag:

const grid = svg`
  <g>
    ${[0, 10, 20].map((x) => svg`<line x1=${x} y1="0" x2=${x} y2="20"/>`)}
    ${[0, 10, 20].map((y) => svg`<line x1="0" y1=${y} x2="0" y2=${y}/>`)}
  </g>
`;

Safety

Because lit-html templates are parsed before values are set, they are safer than generating HTML via string-concatenation. Attributes are set via setAttribute() and node text via textContent, so the structure of template instances cannot be accidentally changed by expression values, and values are automatically escaped.

TODO: Add sanitization hooks to disallow inline event handlers, etc.

Case-sensitive Attribute Names

Attribute parts store both the HTML-parsed name and the raw name pulled from the string literal. This allows extensions, such as those that might set properties on elements using attribute syntax, to get case-sensitive names.

const render = () => html`<div someProp="${blue}"></div>`;
render().template.parts[0].rawName === 'someProp';

Arrays/Iterables

const items = [1, 2, 3];
const render = () => html`items = ${items.map((i) => `item: ${i}`)}`;
const items = {
  a: 1,
  b: 23,
  c: 456,
};
const render = () => html`items = ${Object.entries(items)}`;

Nested Templates

const header = html`<h1>${title}</h1>`;
const render = () => html`
  ${header}
  <p>And the body</p>
`;

Promises

Promises are rendered when they resolve, leaving the previous value in place until they do. Races are handled, so that if an unresolved Promise is overwritten, it won't update the template when it finally resolves.

const render = () => html`
  The response is ${fetch('sample.txt').then((r) => r.text())}.
`;

Directives

Directives are functions that can extend lit-html by directly interacting with the Part API.

Directives will usually be created from factory functions that accept some arguments for values and configuration. Directives are created by passing a function to lit-html's directive() function:

html`<div>${directive((part) => { part.setValue('Hello')})}</div>`

The part argument is a Part object with an API for directly managing the dynamic DOM associated with expressions. See the Part API in api.md.

Here's an example of a directive that takes a function, and evaluates it in a try/catch to implement exception safe expressions:

const safe = (f) => directive((part) => {
  try {
    return f();
  } catch (e) {
    console.error(e);
  }
});

Now safe() can be used to wrap a function:

let data;
const render = () => html`foo = ${safe(_=>data.foo)}`;

This example increments a counter on every render:

const render = () => html`
  <div>
    ${directive((part) => part.setValue((part.previousValue + 1) || 0))}
  </div>`;

lit-html includes a few directives:

repeat(items, keyfn, template)

A loop that supports efficient re-ordering by using item keys.

Example:

const render = () => html`
  <ul>
    ${repeat(items, (i) => i.id, (i, index) => html`
      <li>${index}: ${i.name}</li>`)}
  </ul>
`;

until(promise, defaultContent)

Renders defaultContent until promise resolves, then it renders the resolved value of promise.

Example:

const render = () => html`
  <p>
    ${until(
        fetch('content.txt').then((r) => r.text()),
        html`<span>Loading...</span>`)}
  </p>
`;

asyncAppend(asyncIterable) and asyncReplace(asyncIterable)

JavaScript asynchronous iterators provide a generic interface for asynchronous sequential access to data. Much like an iterator, a consumer requests the next data item with a a call to next(), but with asynchronous iterators next() returns a Promise, allowing the iterator to provide the item when it's ready.

lit-html offers two directives to consume asynchronous iterators:

  • asyncAppend renders the values of an async iterable,
    appending each new value after the previous.
  • asyncReplace renders the values of an async iterable,
    replacing the previous value with the new value.

Example:

const wait = (t) => new Promise((resolve) => setTimeout(resolve, t));

/**
 * Returns an async iterable that yields i...
Read more