Sometimes you need to render HTML elements on a web page. And like Goldilocks' search for "just right", you have to try a few techniques before you find the right one. Using a framework may be too hard. Using pure HTML and the DOM API may be too soft. What you need is something in the middle that is just right. Is lit-html "just right"? Let's find out.
First, I'll show how this all works. Then at the end of this article, I'll explain everything you need to get started with lit-html to try this for yourself.
When you're done, you can push your HTML app with lit-html to the cloud to see it in all of its glory! I included a link to a free Azure trial, so you can try it yourself.
Resources:
You can learn about rendering HTML with no libraries and using vanilla JavaScript/TypeScript in my other article
The Sample App
Here is the app I'll demonstrate in this article. It fetches a list of heroes and renders them when you click the button. It also renders a progress indicator while it is fetching.
What's the Value of lit-html
When you focus on rendering content, and nothing else, lit-html is a good fit. It works closely with the DOM to render content, and refresh it in an optimal manner. The docs can provide you with more details, but the basic code for lit-html looks like this.
// Credit: As seen in official docs https://lit-html.polymer-project.org/guide/getting-started
// Import lit-html
import { html, render } from 'lit-html';
// Define a template
const myTemplate = name =>
html`
<p>Hello ${name}</p>
`;
// Render the template to the document
render(myTemplate('World'), document.body);
You import lit-html, define a template, then render it to the DOM. That's it!
Rendering HTML
A progress bar is fairly basic. There is some HTML, and we show it when needed and hide it when it is not required. While we could use a template, or innerHTML, or the DOM API for this, let's see what this would look like with lit-html.
First, we get a reference to the element in the DOM where the progress bar will appear.
Then we define the template. This code looks and feels like JSX (or TSX). The advantage here is that you can write the HTML. You wrap the HTML in a template string (notice the back-tick character is used and not a single quote). Template strings allow you to span lines and insert variables where needed (we'll see this soon). The magic that makes this work is the html
tag that precedes the template string. The html
tag is what tells lit-html that you are about to define a template.
Next, we compile the template and pass those results to lit-html's render
function, which places the results in the DOM. Finally, we hide or show the progress bar as needed.
function showProgress(show = true) {
const container = document.getElementById('progress-placeholder');
const template: () => TemplateResult = () => html`
<progress class="progress is-medium is-info" max="100"></progress>
`;
const result = template();
render(result, container);
container.style.display = show ? 'block' : 'none';
}
Now you can run this showProgress
function any time you want to show the progress bar.
Note that when a template is re-rendered, the only part that is updated is the data that changed. If no data changed, nothing is updated.
Rendering HTML with Dynamic Values
The progress bar does not change each time it is rendered. You will have situations where you want your HTML to change. For example, you may have a message area on your web app that shows a styled message box with a title and a message. The title and message will change every time you show the message area. Now you have dynamic values.
The HTML is defined with a template string, so it is trivial to add a variable into it. Notice the code below adds a title and text into the template, using the ${data.title}
and ${data.text}
syntax, respectively.
Then the template is compiled and rendered were needed.
When this template is re-rendered, the only part that is updated is the data that changed. In this case, that's the title and text.
function showMessage(text: string, title = 'Info') {
const template: (data: any) => TemplateResult = (data: Message) => html`
<div id="message-box" class="message is-info">
<h3 class="message-header">${data.title}</h3>
<p class="message-body">${data.text}</p>
</div>
`;
const el = document.getElementById('message-placeholder');
const result = template({ title, text });
render(result, el);
el.style.visibility = !!text ? 'visible' : 'hidden';
}
Rendering a List
Things get a little more real when we render a list. Let's think about that for a moment. A list requires that we have a plan if there is data and a backup plan if there is no data. A list requires that we render the same thing for each row, and we don't know how many rows we have. A list requires that we pass different values for each row, too. Then we have to take the rows and wrap them in a container such as a <ul>
or a <table>
.
So there is a little more logic here, regardless of whether we use lit-html or any other technique. Let's explore how the replaceHeroList
function renders the rows using lit-html.
function replaceHeroList(heroes?: Hero[]) {
const heroPlaceholder = document.querySelector('.hero-list');
// Define the template
let template: () => TemplateResult;
if (heroes && heroes.length) {
// Create the template for every hero row
template = createList();
} else {
// Create the template with a simple "not found" message
template = () =>
html`
<p>heroes not found</p>
`;
}
// Compile the template
const result = template();
// Render the template
render(result, heroPlaceholder);
Notice that when there are heroes, we call the createList
function. This function begins by creating an array of TemplateResult
. So for every hero in the heroes
array, we define a template that represents the <li>
containing the HTML that displays that respective hero.
Then we create another template that contains the <ul>
and embeds the array of hero templates. It's pretty cool that we can embed templates like this! Finally, we return it all and let the logic compile the templates and render them.
function createList() {
// Create an array of the templates for each hero
const templates: TemplateResult[] = heroes.map(hero => {
return html`
<li>
<div class="card">
<div class="card-content">
<div class="content">
<div class="name">${hero.name}</div>
<div class="description">${hero.description}</div>
</div>
</div>
</div>
</li>
`;
});
// Create a template that includes the hero templates
const ulTemplate: () => TemplateResult = () =>
html`
<ul>
${templates}
</ul>
`;
return ulTemplate;
}
Summary
When you want to render HTML, lit-html is a fast and light-weight option. Is it better than using templates and the DOM API? You'll have to decide what is best for you. But the real story here is that you have another great option to consider when determining the right tool for your job.
Prologue
You can also get editor help with your lit-html templates. Notice the image below shows the syntax highlighting for the HTML template!
Setup
You can install the lit-html package with npm.
npm install lit-html
Alternately you can load it directly from the unpkg.com CDN
import { html, render } from 'https://unpkg.com/lit-html?module';
You have a choice here. npm is my preference, but feel 100% free to use the CDN if that suits you.
TypeScript and lit-html
You only need to include the library for lit-html and you're done. But I like to use TypeScript, and I absolutely recommend enabling your tooling to work great with typeScript and lit-html.
Let me be very clear here - you do not need TypeScript. I choose to use it because it helps identify mistakes while I write code. If you don't want TypeScript, you can opt to use plain JavaScript.
Here are the steps to make TypeScript and lit-html light up together:
- Install TypeScript support for lit-html
- Configure your
tsconfig.json
file - Install the VS Code extension for lit-html
Run this command to install the plugin and typescript, as development dependencies to your project.
npm install --save-dev typescript-lit-html-plugin typescript
Edit your tsconfig.json
by adding the following to your compilerOptions
section.
"compilerOptions": {
"plugins": [
{
"name": "typescript-lit-html-plugin"
}
]
}
Finally, install the VS Code extension for lit-html.
Now you get syntax highlighting for all of your lit-html templates!
Top comments (14)
Nice article!
typescript-lit-html-plugin is great, but I would recommend the ts-lit-plugin for tsc and lit-plugin for VS Code - they have a lot more features around type checking templates, and bindings to attributes and properties, including for custom elements. See github.com/runem/lit-analyzer/tree... and marketplace.visualstudio.com/items...
I’ll check those out. Thanks for sharing.
thanks for the article, discovered about lit-html now!
curious to know why markup is created in
JavaScript
instead ofHTML
.then call this in
JavaScript
(with data) for dynamic rendering. given that there may be several dynamic components in a page that could help in code readability.One reason is that those
${...}
expressions are part of JavaScript, you can't just put that in HTML and have them do anything.Another is that we don't have a great way of co-locating HTML and JavaScript right now. You could put HTML templates in the main document and your component in JS, but that's a pretty bad developer experience, and it would be tough-to-impossible to scale to 3rd party components.
And most importantly is that lit-html doesn't have any of its own control-flow constructs. It has no if-statements or loops - that's all left up to JavaScript. We'd have to re-implement these for HTML.
Hopefully soon we'll have HTML modules in the browser and then we can do something like this.
Thanks for contributing to the conversation!
thanks for the reply, Justin!
Well done! Justin has done a remarkable job keeping lit-html lean and mean. lit-html and it's equally lean base class lit-element really changed the way I think about creating applications. This article is a nice demonstration and introduction. It would be a great addition to the curated Awesome lit-html repo. github.com/web-padawan/awesome-lit...
Thanks! Looks like it was added.
Lit-html works great with a state management library like redux. I manage to create a TodoMVC app binding lit-html with a homemade redux using directives.
That's cool, thanks for sharing
Theres also haunted which adds react-like hooks to lit-html!
neat!
Cool post!
VS Code and Type Script
Here
Type Script
reminds me of old days people writingShare Point
Using lit-HTML's declarative syntax, it is easy to build lightweight Web Components as well as use other existing Web Components in our applications. That really works for me.