Server rendering

Render a template to a string with hashJS.render, with no DOM and no browser involved.

The same template syntax, called as an ordinary function. This is what makes the library usable on a server: a template is a string, the data is an object, and the result is HTML you can write straight into a response.

<style>
    body { font: 14px/1.5 system-ui, sans-serif; margin: 0; color: #24292f; }
    h4 { margin: 0 0 6px; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: #57606a; }
    pre { background: #0d1117; color: #c9d1d9; padding: 10px 12px; border-radius: 6px;
          overflow-x: auto; font-size: 12px; line-height: 1.45; margin: 0 0 16px; }
    ul { list-style: none; padding: 0; margin: 0; border: 1px solid #d8dee4; border-radius: 6px; }
    li { display: flex; justify-content: space-between; padding: 7px 12px; border-bottom: 1px solid #eaeef2; }
    li:last-child { border-bottom: 0; }
    a { color: #0969da; text-decoration: none; }
    .price { font-variant-numeric: tabular-nums; color: #57606a; }
</style>

<h4>The string the server produces</h4>
<pre id="source"></pre>

<h4>The same string, in a browser</h4>
<div id="rendered"></div>

<script src="https://cdn.jsdelivr.net/gh/richdafunk/hashJS@v1.3.6/hashJS.js"></script>
<script>
    // A template is just a string, and hashJS.render is just a function.
    // There is no DOM in this call and nothing to bind to — it takes a
    // template and data and hands back finished HTML.
    const template = [
        '<ul>',
        '#for(let p of products) {#',
        '    <li>',
        '        <a href="/product/#p.sku#">#htmlEncode(p.name)#</a>',
        '        <span class="price">#p.price# NOK</span>',
        '    </li>',
        '#}#',
        '</ul>'
    ].join('\n');

    const data = {
        products: [
            { sku: 'A-1042', name: 'Ceramic mug',           price: 249 },
            { sku: 'B-3310', name: 'Notebook, A5 "dotted"', price: 129 },
            { sku: 'C-0087', name: 'Pencil set <HB & 2B>',  price:  89 }
        ]
    };

    const html = hashJS.render(template, data);

    // Left as text so you can read exactly what came out.
    document.getElementById('source').textContent = html.trim();

    // And again as markup, which is what a browser receives from the server.
    document.getElementById('rendered').innerHTML = html;
</script>
Output result:
The same template on a server:
// server.js — the same template and the same call, in a request handler.
//
// Nothing about the template changes when it moves to a server. There is no
// DOM to bind to and no element to resolve, so hashJS.render is the whole API.

const http = require('http');

// The library is a single file with no dependencies, so it is vendored rather
// than installed. It exports through module.exports as well as to the global.
const hashJS = require('./hashJS.js');

// Compile once, at startup. Compilation is the expensive half of rendering,
// and a page template does not change between requests.
const page = hashJS.compile(`<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>#htmlEncode(title)#</title>
</head>
<body>
    <h1>#htmlEncode(title)#</h1>
    <ul>
    #for(let p of products) {#
        <li>
            <a href="/product/#p.sku#">#htmlEncode(p.name)#</a>
            <span>#p.price# NOK</span>
        </li>
    #}#
    </ul>
    #if(products.length === 0) {#
    <p>Nothing in this category yet.</p>
    #}#
</body>
</html>`);

http.createServer(async function (req, res) {
    const products = await db.products.inCategory(req.url);

    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
    res.end(page({ title: 'Catalogue', products: products }));
}).listen(3000);

The constructor form needs a document: it resolves an element, reads its markup and writes the result back into the page. hashJS.render skips all of that and does only the part that matters — compile the template, run it against the data, return the string.

Note #htmlEncode(p.name)#. Values are substituted exactly as they are, so anything originating with a user has to be escaped where it is written. One product name here contains < and & and another contains quotes; both come out as text rather than as markup.

The server listing uses hashJS.compile instead. Compilation is the expensive half of rendering and a page template does not change between requests, so it belongs at startup rather than in the handler.