HTML Tooling and Libraries

Not every interactive page needs a framework. htmx 79,053 and Alpine.js 208,904 extend HTML with attributes (Lightweight DOM Libraries compares them with other lightweight DOM libraries); Cheerio 30,512 and jsdom 21,691 handle HTML without a browser; template engines generate it.

htmx

htmx (https://github.com/bigskysoftware/htmx 49,522 ) (0BSD licence) lets any element issue a request and swap the HTML response into the page. hx-get/hx-post choose the request, hx-trigger the event (a click for buttons by default), hx-target where the response goes and hx-swap how (innerHTML by default). The server returns HTML fragments, so there is no client-side rendering code. This complete app needs only Node:

server.mjs: a like button with htmx 2 and Node's built-in http moduleJavaScript
import { createServer } from "node:http";
let likes = 0;
const page = () => `<!doctype html>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.10/dist/htmx.min.js"></script>
<button hx-post="/like" hx-target="#count">Like</button>
<span id="count">${likes} likes</span>`;
createServer((req, res) => {
  res.setHeader("Content-Type", "text/html; charset=utf-8");
  if (req.method === "POST" && req.url === "/like") {
    likes++;
    return res.end(`<strong>${likes}</strong> likes`); // an HTML fragment, not JSON
  }
  res.end(page());
}).listen(3000);

Run node server.mjs and open http://localhost:3000: each click sends POST /like with an HX-Request: true header and the span becomes 1 likes, 2 likes and so on. htmx 2.0.10 is npm 2,036 's latest; htmx 4.0 is released under the next tag so 2.x sites are not upgraded by accident.

Alpine.js

Alpine.js (https://github.com/alpinejs/alpine 31,940 ) (MIT) keeps state in the markup: x-data declares a reactive object, @click (short for x-on:click) handles events, x-text binds text and x-show toggles visibility. The script needs defer. The demo clicks both buttons once before the capture:

An Alpine.js quantity picker with a details toggleHTMLLive
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.17.3/dist/cdn.min.js"></script>
<div x-data="{ qty: 1, open: false }" style="font: 16px sans-serif">
  <button @click="qty++">Add one</button>
  <button @click="open = !open" x-text="open ? 'Hide details' : 'Show details'"></button>
  <p>Quantity: <strong x-text="qty"></strong>, total <strong x-text="'$' + qty * 39"></strong></p>
  <p x-show="open">Desk lamp, $39 each, ships in 2 days.</p>
</div>
<script>
  addEventListener("load", () => document.querySelectorAll("button").forEach((b) => b.click()));
</script>
Browser output of Listing 2.73
Browser output of 73
Cheerio and jsdom

Cheerio (https://github.com/cheeriojs/cheerio 30,512 ) (MIT) parses HTML into a tree with a jQuery-style API. It runs no scripts, which makes it fast for scraping, rewriting static builds or checking generated markup:

Extracting and editing HTML with Cheerio 1.2 (npm install cheerio)JavaScript
import * as cheerio from "cheerio";
const $ = cheerio.load(`<ul>
  <li class="item" data-price="39">Desk lamp</li>
  <li class="item sale" data-price="25">Clip light</li>
</ul>`);
const items = $(".item").map((i, el) => `${$(el).text()}: $${$(el).data("price")}`).get();
console.log(items);
$(".sale").append(" (sale)").attr("data-price", "20");
console.log($.html(".sale"));
Output
[ 'Desk lamp: $39', 'Clip light: $25' ]
<li class="item sale" data-price="20">Clip light (sale)</li>

jsdom (https://github.com/jsdom/jsdom 21,691 ) (MIT, version 30) instead implements the DOM and many Web APIs in JavaScript, with events and optional script execution (new JSDOM(html, { runScripts: "dangerously" })); Jest 62,362 and Vitest 102,989 use it to test DOM code. It has no layout engine, so use Playwright 26,280 when you need real rendering.

Template engines

A template engine merges data into HTML on the server or at build time. All four below escape <, > and & by default, blocking cross-site scripting; use the raw form only for trusted HTML.

Template engines for generating HTML in Node
Engine Loop over items Raw output Licence
Handlebars 131,260 (https://github.com/handlebars-lang/handlebars.js 18,679 ) {{#each items}}{{this}}{{/each}} {{{ html }}} MIT
Nunjucks 126 (https://github.com/mozilla/nunjucks 9,000 ) {% for i in items %}{{ i }}{% endfor %} {{ html | safe }} BSD-2-Clause
EJS 689,492 (https://github.com/mde/ejs 8,130 ) <% items.forEach(i => { %><%= i %><% }) %> <%- html %> Apache-2.0
Pug 352,080 (https://github.com/pugjs/pug 21,837 ) each i in items then indented li= i li!= html MIT

Handlebars keeps logic out of templates, Nunjucks (Jinja2-style) adds inheritance and macros, EJS is plain JavaScript inside tags, and Pug replaces tags with indentation.