v1.0.2 of HTMP adds auto self-healing for reactive bindings and :for support on table rows. Bundle went from 2.9 kB to 3.3 kB gzipped. Here's what happened, and why it's worth 0.4 kB.
Test for AdminLTE
I was converting AdminLTE 4's users.html page to HTMP — the first real test of HTMP in a real admin template. Look so good but where is <tr>?:
Bug 1: Disappeared
<table>
<tbody>
<tr :for="user in users">
<td>{{ user.name }}</td>
</tr>
</tbody>
</table>
The browser's strict HTML parser strips <tr> when it's parsed outside table context. This is correct behavior — the HTML spec is clear about which elements can be children of <tbody>.
But it broke :for on table rows. And tables are 80% of admin dashboards. So hard to tell, suggest users translate it to <div> card or <li> list that works fine its horrible. I though it should worked fine, just code again.
Bug 2: External DOM Breaks Bindings
HTMP tracks nodes by path — [5, 0, 0]. It's fast. It's surgical. But if jQuery (or a browser extension, or an analytics script) inserts a node somewhere inside the HTMP root, every path shifts.
So before its happen, I append child in many place in the dom, yes path track intercept new node was broken. its rare happen intentionaly, but how much it cost to make it resilient?
The Fixes (Both Used Native Browser APIs)
Fix 1: Wrap in <template>
The browser has a native element designed exactly for this: . Its content is parsed as an HTML fragment — no context restrictions.
const templateWrapper = document.createElement('template');
templateWrapper.innerHTML = originalHTML;
const freshTemplate = templateWrapper.content.firstElementChild;
<tr> inside <template> doesn't get stripped. Because <template> is a valid parent for anything.
I choose Native one. Works everywhere.
Fix 2: Self-Healing Binding
Every binding node gets tagged with _htmp (its binding ID):
node._htmp = id;
When a path fails, HTMP no longer gives up. It:
Detects the path is stale (node at path isn't the expected binding).
Finds the node by _htmp ID — scanning the DOM.
Corrects the path.
[HTMP] Self-healing: Path corrected for ID 5
[HTMP] Self-healing: Path corrected for ID 11
[HTMP] Self-healing: Path corrected for ID 10
Once healed, paths are valid again.
Now self-repairing and table loop :for <tr> ready.
What It Costs
| Version | Minified | Gzipped | Download (Slow 3G) |
|---|---|---|---|
| v1.0.1 | 8.3 kB | 2.9 kB | 55 ms |
| v1.0.2 | 9.4 kB | 3.3 kB | 66 ms |
+0.4 kB gzipped. 11 ms slower on Slow 3G.
That's the entire cost of resilience.
For comparison:
| Library | Gzipped |
|---|---|
| HTMP v1.0.2 | 3.3 kB |
| snabbdom | 4.72 kB |
| virtual-dom | 5.28 kB |
| Alpine.js | ~7 - 18 kB |
| jQuery | ~30 kB |
| React + ReactDOM | ~40 kB |
Still the smallest. Still zero dependencies. Still works from a file:// URL.
Native along the way
Both fixes used browser APIs that already exist:
- Table fix : Native content fragment, no workaround
- Self-healing : node._htmp DOM tagging and TreeWalker Browser API
A theme creator still HTML user make it reactive.
A PHP developer can use an existing AdminLTE theme with HTMP bindings instead of React.
An LLM can be asked to "make this AdminLTE table reactive" and just do it.
HTMP doesn't invent new tools. It connects browser capability.
Future-proof by construction: as long as browsers support DOMParser, Proxy, and , HTMP works. Those aren't going away.
New Possibility after tables loop active and self healing
AdminLTE, SB Admin, and Bootstrap-based templates use tables everywhere. Before v1.0.2, HTMP couldn't handle them. Now it can — without refactoring a single line of the original HTML.
That means:
The trade-off was 0.4 kB. The unlock is an entire ecosystem.
Source: github.com/erlanggasatria-source/HTMP
HTMP — Pattern. Proxy. Program. Zero dependencies. 3.3 kB. Future-proof by construction.
Two bugs. One fix. And a reminder that the browser already has everything you need — if you know where to look.
Top comments (0)