Writing templates
@nanoweb/template
is a templating library that provides fast, efficient rendering of asynchronous HTML components. It lets you express web UI as a function of data.
This section introduces the main features and concepts in @nanoweb/template
.
Render static HTML
The simplest thing to do in @nanoweb/template
is to render some static HTML.
import { html, renderToString } from '@nanoweb/template';
// Declare a template
const myTemplate = html`<div>Hello World</div>`;
// Render the template
renderToString(myTemplate)
.then((text) => console.log(text)); // => "<div>Hello World</div>"
The template is a tagged template literal. The template itself looks like a regular JavaScript string, but enclosed in backticks (`
) instead of quotes. The string is passed to @nanoweb/template
's html
tag function.
The html
tag function returns a Template
—a lightweight object that represents the template to be rendered.
The renderToString
function resolves any asynchronous calls and returns a compiled string.
Render dynamic text content
You can't get very far with a static template. @nanoweb/template
lets you create bindings using ${expression}
placeholders in the template literal:
const aTemplate = html`<h1>${title}</h1>`;
To make your template dynamic, you can create a template function. Call the template function any time your data changes.
import { html, renderToString } from '@nanoweb/template';
// Define a template function
const myTemplate = (name) => html`<div>Hello ${name}</div>`;
// Render the template with some data
renderToString(myTemplate('world'))
.then(text => console.log(text)); // => "<div>Hello world</div>"
// ... Later on ...
// Render the template with different data
renderToString(myTemplate('there'))
.then(text => console.log(text)); // => "<div>Hello there</div>"
When you call the template function, @nanoweb/template
captures the current expression values. The template function returns a Template
that's a function of the input data. This is one of the main principles behind using @nanoweb/template
: creating UI as a function of state.
Using expressions
The previous example shows interpolating a simple text value, but the binding can include any kind of JavaScript expression:
const myTemplate = (subtotal, tax) =>
html`<div>Total: ${subtotal + tax}</div>`;
const myTemplate2 = (name) =>
html`<div>${formatName(name.given, name.family, name.title)}</div>`;
Expression values can have the following atomic types or any variation of them in lists and promises:
string
number
boolean
undefined
null
Template
UnsafeHtml
Nest and compose templates
You can also compose templates to create more complex templates. When a binding in the text content of a template returns a Template
, the Template
is interpolated in place.
const myHeader = html`<h1>Header</h1>`;
const myPage = html`
${myHeader}
<div>Here's my main page.</div>
`;
You can use any expression that returns a Template
, like another template function:
// some complex view
const myListView = (items) => html`<ul>...</ul>`;
const myPage = (data) => html`
${myHeader}
${myListView(data.items)}
`;
Composing templates opens a number of possibilities, including conditional and repeating templates.
Conditional templates
@nanoweb/template
has no built-in control-flow constructs. Instead you use normal JavaScript expressions and statements.
Conditionals with ternary operators
Ternary expressions are a great way to add inline conditionals:
html`
${user.isLoggedIn
? html`Welcome ${user.name}`
: html`Please log in`
}
`;
Conditionals with if statements
You can express conditional logic with if statements outside of a template to compute values to use inside of the template:
getUserMessage() {
if (user.isloggedIn) {
return html`Welcome ${user.name}`;
} else {
return html`Please log in`;
}
}
html`
${getUserMessage()}
`
Guard statements
You can add guard statements to display certain values if certain conditions are met or nothing if not. Falsy values are ignored by html
.
html`Welcome ${user.isLoggedIn && user.name}`
Repeating templates
You can use standard JavaScript constructs to create repeating templates.
Repeating templates with Array.map
To render lists, you can use Array.map
to transform a list of data into a list of templates:
html`
<ul>
${items.map((item) => html`<li>${item}</li>`)}
</ul>
`;
Note that this expression returns an array of Template
objects. nanoweb/template
will render an array of subtemplates and other values.
Repeating templates with looping statements
You can also build an array of templates and pass it into a template binding.
const itemTemplates = [];
for (const i of items) {
itemTemplates.push(html`<li>${i}</li>`);
}
html`
<ul>
${itemTemplates}
</ul>
`;
Asynchronous templates
Most applications have to reach out to other systems to query for or persist data and therefore often have to handle Promises. @nanoweb/template
makes consuming promises a breeze:
const fetchPost = async (postId) => {
const response = await fetch(`/api/posts/${id}`);
return response.json();
}
const getPost = async (postId) => {
const post = await fetchPost(postId);
return html`<h1>${post.title}</h1>...`;
}
html`
<header>My Blog<header>
<main>
${getPost(42)}
</main>
<footer>Fancy Footer</footer>
`;
renderToString
collects all templates upfront and tries to execute them as deep as possible. This guarantees best
possible execution plan.
const asyncTemplate = (waitFor) =>
new Promise(resolve =>
setTimeout(() => resolve(html`This took ${waitFor}ms`), waitFor)
);
const page = html`
<header>${asyncTemplate(200)}<header>
<main>${asyncTemplate(100)}</main>
<footer>${asyncTemplate(50)}</footer>
`;
// This should take about 200ms to resolve
renderToString(page).then(text => console.log(text));
Render unsafe strings
html
escapes all string expressions by default to thwart XSS attacks. Yet sometimes we want to allow arbitrary html strings to be embedded in our html templates. @nanoweb/template
provides unsafeHtml
for this. Wrapping any string expression with it will exclude this particular string from being escaped.
// Safe
html`
Hello
${'mamalicious_user42<script>alert("possible xss attack")</script>'}
`
// Unsafe
html`
This is
${unsafeHtml('<b>UNSAFE</b>'}
${unsafeHtml('<script>alert("possible xss attack")</script>'}
`