Open-source project
hmpl-language/hmpl avatar
hmpl-language/hmpl

HMPL.js lets the server send the interface, not just the data

🐜 HMPL.js is a lightweight server-oriented template language for JavaScript. Fetch HTML, render it safely, and keep apps dynamic, modern, and small.

970 stars120 forksTypeScriptMIT

At a glance

What is it?
HMPL.js is a lightweight server oriented template language for JavaScript. Instead of shipping a framework to the browser, you write block syntax in the markup, the server returns HTML fragments, and the client mounts them over the Fetch API, with JSON5 for the object syntax and DOMPurify for safe rendering in a few kilobytes.
Who is it for?
HMPL.js fits a team that wants server rendered interactivity without adopting a framework, and the block syntax means the interaction contract lives in the markup where the server already owns the response.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 15 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • hmpl-language/hmpl970 ★ · +2
Star history
Repository2026-10-212026-11-07
hmpl-language/hmpl968970

2 Stars in the last 17 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Server driven, block based

The introduction states the goal directly. HMPL.js provides the flexibility to build server driven templates with minimal JavaScript, and with its block based syntax, customizable fetch requests and built in support for forms, events and time based syncing, you can deliver dynamic user interfaces without relying on a heavy framework. The reference points are the fetch API and the ideas behind hypermedia, and the topics the repository carries include hateoas and hypermedia alongside ajax, rest and template engine. The interesting claim is in the feature list, where the last item is that you can generate thousands of DOM nodes from a single template. The mental model is that the server composes the interface and the browser mounts it, so the client is a rendering mechanism rather than an application.

The syntax is blocks in the markup

The shortest example shows the whole idea, a request block with an indicator inside it:

html
<div>
  {{#request src="/api/my-component.html"}}
    {{#indicator trigger="pending"}}
      <p>Loading...</p>
    {{/indicator}}
  {{/request}}
</div>

A request block points at a server endpoint, and the indicator block is the state shown while that request is pending, so the loading placeholder is part of the template rather than a piece of client code. Everything in the response is mounted, not just data, which the documentation is explicit about, since you can take out not only data in the form of text but entire components and even pages. The syntax is deliberately terse, and the project argues that the object syntax in the markup requires a minimum number of characters, which is part of the argument about bundle size.

compile returns a function, and the function is your handler

The basic usage shows the JavaScript side, and it is short:

javascript
import hmpl from "hmpl-js";

const templateFn = hmpl.compile(
  `<div>
      <button data-action="increment" id="btn">Click!</button>
      <div>Clicks: {{#request src="/api/clicks" after="click:#btn"}}{{/request}}</div>
  </div>`
);

The compiled template is a function that takes a handler for the request and returns an object with a response and a status, and the handler receives the request event so it can build the body to send back:

javascript
const clicker = templateFn(({ request: { event } }) => ({
  body: JSON.stringify({ action: event.target.getAttribute("data-action") })
})).response;

document.querySelector("#app").append(clicker);

The after attribute on the request block binds the request to an event, here a click on the button, and the data-action attribute is what the handler reads to decide what to send. The result is appended to the container. The relationship between the template and the server is therefore a contract expressed in markup, with the client only responsible for answering requests.

A path with no JavaScript at all

For the case where you do not want to write any JavaScript, there is an additional module called hmpl-dom, and the documentation shows a plain HTML page that uses it. The markup carries a template element with the hmpl attribute:

html
      <template hmpl>
        <div>
          {{#request src="/api/my-component.html"}}
            {{#indicator trigger="pending"}}
              <p>Loading...</p>
            {{/indicator}}
          {{/request}}
        </div>
      </template>

and the page loads four scripts from a CDN, json5, dompurify, hmpl-js and hmpl-dom, and that way components from the server are mounted into the DOM without having to add them manually. The scripts are loaded from unpkg in the example, and the same four are the runtime dependencies of the approach, so an application that wants to avoid a CDN would host them itself. The design point is that the same template can be used from a bundler or from a script tag, which is what makes the library usable in a static site build as well as in an application.

JSON5 and DOMPurify carry the safety story

Two dependencies do the work that a template language usually does not talk about. HMPL integrates with JSON5 for expressive object syntax, so the inline attributes in the markup can be written as objects rather than as stringly typed values, and with DOMPurify for safe HTML rendering. That second one is the important one for a language whose whole premise is that the server sends markup. If the server is trusted, the sanitiser is redundant, and if the server is not trusted, then the server is already sending arbitrary HTML to the browser. The library takes the position that the fetched content is sanitised on the way in, which is a defensible default for a mechanism that fetches fragments from an endpoint, but it also means the sanitiser is on the critical path of every request, which is a cost in a few kilobytes and a little CPU.

The argument is bundle size

The Why HMPL section makes the pitch in terms of size and of what it replaces. Using template language capabilities you can multiply reduce the size of the application bundle, and there is full customization of the request based on the modern fetch standard, plus support for the functionality modern applications need, namely a request indicator, sending by event, automatic generation of the body for a form, and caching, and the terse object syntax in the markup. There is a separate repository for an app size comparison, with a table of bytes, and the project states plainly that HMPL can be a great alternative to popular tools such as HTMX and Alpine.js. The feature list restates the case, with a customizable request sent to the server when the UI is received, memory preserving file sizes on the client, the fetch API instead of XMLHttpRequest, server oriented work through markup with a little JavaScript, and the generation of thousands of DOM nodes from a single template.

The package, the tests and the release state

The package is published as hmpl-js and is MIT licensed, with the author listed as Anton Maklakov. The main entry points are dist/index.js with type declarations in dist/index.d.ts, and the published files are the licence, the README, the dist directory and the src directory, so the source ships alongside the build. The build is TypeScript, with a plain tsc compile for the library and a tsup production build, and the lint step runs eslint over the source with the fix flag, with a separate lint target for the tests. The test suite is mocha run through ts-node with the experimental specifier resolution flag, which is the older style of running TypeScript tests directly, and coverage is produced with c8 in both an lcov reporter mode and a default mode. The repository is a small TypeScript project layout with src/ for the source, test/ for the tests, dist/ for the built output and a www/ directory, alongside a .babelrc, an .eslintrc.json, a .prettierrc.json, a .mocharc.json, a tsconfig.json and a tsup.config.ts. Using Babel and tsup and tsc side by side suggests a transition between build strategies rather than a settled choice. The engines field asks for Node 10.12.0 or newer, which is a very low floor for a package built with modern TypeScript tooling. The release history shows 3.2.1 published on 2025-11-29, 3.2.0 on 2025-11-12 and 3.1.0 on 2025-10-23, and the last push to the repository on 2026-08-31, so the package version moves quickly while the commits are steadier. The project also has a CHANGELOG.md, a SECURITY.md, a code of conduct and a contributing guide, and a documentation site at hmpl-lang.dev with a playground and a demo sandbox linked from the README.

Editorial conclusion

HMPL.js fits a team that wants server rendered interactivity without adopting a framework, and the block syntax means the interaction contract lives in the markup where the server already owns the response. It does not fit a project that wants a rich client side application, since the whole point is to move that work to the server, and it does not fit a team that cannot use a template language at all, because the two libraries it depends on at runtime are loaded from a CDN in the documented no-JavaScript setup. Before you adopt it, read how the response handler is wired, since compile returns a function whose argument carries the request event, and check the version you pin, where the release history shows 3.2.1 published on 2025-11-29 and the last push on 2026-08-31.

Frequently asked questions

What is HMPL.js?

It is a lightweight server oriented template language for JavaScript, with block based syntax, customizable fetch requests and built in support for forms, events and time based syncing, so a page can be interactive with a small amount of JavaScript and no heavy framework.

How does a HMPL template fetch content?

With a request block that points at a server endpoint, such as a request src attribute, optionally bound to an event with an after attribute, and an indicator block inside it that shows the pending state. The server response is mounted into the page, so it can be data, a component or a whole page.

Can I use HMPL without writing JavaScript?

Yes, by adding the hmpl-dom module, which mounts components from the server into the DOM automatically. The documented page loads json5, dompurify, hmpl-js and hmpl-dom as scripts and puts the template in a template element with the hmpl attribute.

How does HMPL handle untrusted HTML?

It renders fetched HTML through DOMPurify for safe rendering, and it uses JSON5 for expressive object syntax in the markup. The sanitiser runs on the path of every fetched fragment, which is the trade for a mechanism where the server supplies the interface.

Is HMPL.js still updated?

The repository is not archived and the last push was on 2026-08-31, with releases 3.2.1 from 2025-11-29, 3.2.0 from 2025-11-12 and 3.1.0 from 2025-10-23. The package is published on npm as hmpl-js under the MIT licence.

Official sources

  1. hmpl-language/hmpl on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/hmpl-language-hmpl.svg)](https://hysenlabs.com/projects/hmpl-language-hmpl)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/hmpl-language-hmpl.svg)](https://hysenlabs.com/projects/hmpl-language-hmpl)

Was this useful?