Efficient Template Rendering Using lit-html

Out of the box, Web Components don’t come with anything to help create and render templates like popular frontend frameworks do. Thankfully however, small and flexible libraries are starting to appear to fill that gap. hyperHTML is a good example, and now lit-html from the Polymer team is also turning out to be a great option.

lit-html It’s a small (~1.7Kb) library that uses the power of tagged template literals to give us an easy and pure-JavaScript way to define templates to be inserted inside the DOM or Shadow DOM. lit-html templates are very efficient, mostly due to the fact that when a template changes only the dynamic parts are re-rendered.

Setup

Simply add lit-html to your project using npm or Yarn:

$ yarn add lit-html

# or:
$ npm install lit-html

Basic Usage

Here we’ll play with examples that don’t make use of any build steps and that use ES6 modules, so you’ll want to test things out in a browser that supports <script type="module"> out of the box. Refer to Can I Use for the latest support data.

Let’s start with a barebones HTML document with a container div:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>lit-html fun!</title>
    <script type="module" src="/index.js"></script>
  </head>

  <body>
    <div id="container"></div>
  </body>
</html>

Now in index.js, let’s demonstrate a simple use for lit-html:

index.js

import { html, render } from './node_modules/lit-html/lit-html.js';

const greeting = (prefix, name) => {
  return html`
    <h1>Well, hello there ${prefix} ${name}</h1>
  `;
};

const el = document.querySelector('#container');

render(greeting('Mr.', 'Alligator'), el);

The above will render an h1 inside our container div element with: Well, hello there Mr. Alligator.

Here are a few things to note:

  • First we create an html-lit template function, which returns a lit-html template.
  • We’re using the html tag function on our template literal and within the literal we can use any JavaScript expression.
  • We’re using the render function with our template function as the first argument and a container element as the second argument.

Nested templates

Templates can be composed by nesting templates within other templates. In the following example, we render out a story template that includes nested start and end templates:

import { html, render } from './node_modules/lit-html/lit-html.js';

const start = html`<h1>One Great Title</h1>`;
const end = html`<p>The End!</p>`;

const story = content => {
  return html`
    ${start}
    <p>${content}</p>
    ${end}
  `;
};

const el = document.querySelector('#container');

render(story('Once upon a time...'), el);

Directives

lit-html can be extended with directives, which have access to the template parts. Two very useful directives are available out of the box:

repeat

Use repeat to stamp out multiple templates given a collection. Here’s an example that renders todo items:

import { html, render } from './node_modules/lit-html/lit-html.js';
import { repeat } from './node_modules/lit-html/lib/repeat.js';

const todo = items => {
  return html`
    <h1>My Todos</h1>
    <ul>
      ${repeat(
        items,
        item => item.id,
        item => html`
          <li class="${item.done ? 'done' : ''}">${item.value}</li>
        `
      )}
    </ul>
  `;
};

const someTodos = [
  { id: 1, value: 'Mop the floor', done: false },
  { id: 2, value: 'Prepare fancy salad', done: true },
  { id: 3, value: 'Get a funky haircut', done: false }
];

const el = document.querySelector('#container');

render(todo(someTodos), el);

The repeat directive takes 3 arguments: the collection, a function that returns a key for the collection items and finally a function that returns a template for one item. The rendered HTML will look like this:

<h1>My Todos</h1>
<ul>
  <li class="">Mop the floor</li>
  <li class="done">Prepare fancy salad</li>
  <li class="">Get a funky haircut</li>
</ul>

until

The built-in until directive will render a fallback template while it waits for a promise to resolve.

Here’s a simple example that renders a message until a fetch request resolves, after which the loading template will be replaced by a template with a list of users:

import { html, render } from './node_modules/lit-html/lit-html.js';
import { repeat } from './node_modules/lit-html/lib/repeat.js';
import { until } from './node_modules/lit-html/lib/until.js';

const users = html`
  ${until(
    fetch('https://jsonplaceholder.typicode.com/users')
      .then(res => res.json())
      .then(users => {
        return html`
          <ul>
          ${repeat(
            users,
            user => user.id,
            user => {
              return html`<li>${user.name}</li>`;
            }
          )}
          </ul>
        `;
      }),
    html`
      <span>💁‍ Getting some users...</span>
    `
  )}
`;

const el = document.querySelector('#container');

render(users, el);

SVG Support

lit-html also comes with a svg tag function to generate SVG templates. Here’s an example that creates a graphic with concentric circles or various colors:

import { render, svg } from './node_modules/lit-html/lit-html.js';

const colors = [
  'deeppink',
  'deepskyblue',
  'darkviolet',
  'firebrick',
  'goldenrod',
  'lightcoral'
];

const circles = svg`
  <g>
    ${[10, 30, 60, 90, 120, 150, 180, 210, 240].map(
      radius => svg`
      <circle r="${radius}" cy="97" cx="497" stroke-width="4" stroke="${colors[
        Math.floor(Math.random() * colors.length)
      ]}" fill="transparent" />
    `
    )}
  </g>
`;

const el = document.querySelector('#svg-container');

render(circles, el);

Our SVG container looks like this:

<svg id="svg-container" viewBox="0 0 1000 350"></svg>

And here's the resulting SVG graphic...

  Tweet It
✖ Clear

🕵 Search Results

🔎 Searching...