diffHTML: a virtual DOM that parses real HTML
diffHTML is a web framework that helps you build applications and other interactive content
At a glance
- What is it?
- diffHTML is a JavaScript library that pairs an HTML parser with a virtual DOM, shipped as a set of packages under /packages: a core renderer, React-like components, a linter middleware, delegated event handling and a Babel plugin that removes runtime parsing. The version in the repository is 1.0.0-beta.30 and the project publishes no GitHub releases.
- Who is it for?
- Adopt diffHTML if you want to render markup you already have, since the parser takes real HTML and the project also supports JSX and tagged templates, and if you want a virtual DOM without pulling in a full framework, because the core package works as a single imported function.
- 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 147 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A parser and a virtual DOM in the same package
diffHTML describes itself as an extremely lightweight and optimized HTML parser and Virtual DOM specifically designed for modern web UIs, and the project description calls it a web framework that helps you build applications and other interactive content. The README is more specific about what it renders: interfaces can be applications, games, data visualizations, or anything you want to render in a web browser or in Node.
The pairing of parser and virtual DOM is the defining choice. Most rendering libraries take a description of what the UI should look like and build a tree from it, and the HTML parser in the browser is a separate thing that you fight when you need to work with server-rendered or user-provided markup. Here the parser and the diffing live together, and the README lists parsing real HTML alongside support for JSX and tagged templates.
The second structural decision is how the project is packaged. The core package works like a library, where you can import just one function and have a fully reactive VDOM rendering engine, and when you opt into more functions and use the companion packages you get a framework for structuring your ideas. That is a spectrum rather than a stack, and it is unusual enough to be the main thing to understand before choosing it.
The framework claim rests on a few claims about the core: memory efficient VDOM rendering that utilizes object pooling, middleware that extends diffHTML with additional features, React-like components which can be rendered as Web Components, and a lite build which has a smaller size, meant for optimizing production code.
The README also says it works great with legacy and modern browsers, Node.js, Deno, and with whatever your favorite JavaScript runtime is, which is a claim about portability rather than about any one environment.
Everything lives in /packages, and you install only what you need
The modules are nested in the /packages folder and the README says they form the foundation of the diffHTML ecosystem. Each one is published separately, which is what makes the one-function-library to full-framework spectrum real.
The core is the first one:
npm install diffhtmlIt is the core public API for creating user interfaces, and it contains a standard build which includes everything plus a smaller optimized build that excludes the HTML parser and performance metrics, which is useful for those who want to minimize the filesize. That lite build is the interesting trade in the package, because dropping the parser is only possible if your input does not need parsing at runtime, which is a constraint the build itself enforces.
The components package adds structure:
npm install diffhtml-componentsIt provides constructors and middleware for rendering stateful and stateless components, and the README notes that the API will be very familiar to anyone who has used React, as the class methods and structure are the same. That is a deliberate API compatibility choice, and the framework half of the project is built on it.
Two of the other packages change the toolchain rather than the runtime. The Babel plugin transforms your input into function calls, which eliminates the need for runtime parsing, and the README compares it to how React compiles down JSX:
npm install babel-plugin-transform-diffhtmlThe linter middleware is the third of that group:
npm install diffhtml-middleware-linterIt runs various linting rules on your input to ensure you are writing valid, well-formed HTML, and it was inspired by and uses rules from the HTMLHint project. The remaining packages in the list are the logger middleware, which logs diffHTML state from the start and end of every render transaction, the synthetic events middleware, the service worker middleware, and the diffhtml-website package that holds the source for the www.diffhtml.org site.
Middleware is the extension model, and it has four shipped examples
Middleware is the mechanism the project uses to extend diffHTML with additional features, and the four shipped middleware packages are the clearest evidence of what that means in practice.
The linter runs rules over your input at render time, so malformed HTML is caught while the tree is being built rather than by a separate tool in the build step. Its rules come from HTMLHint, which means the vocabulary of the errors is familiar even if the integration point is different.
The logger outputs diffHTML state from the start and the end of every render transaction. That is a debugging tool with a specific shape: it is not a general console logger, it is aimed at the diff itself, which is the part of a virtual DOM renderer that is usually hardest to see.
The service worker middleware helps with the creation of a service worker for PWAs and is described as a convenience to make development more friendlier. It is the least surprising of the four, since service workers are a known quantity, but it does mean the project treats a PWA setup as a first-class case.
Taken together, these show what a middleware in diffHTML is: something that hooks into the render pipeline and observes or changes what happens. The linter inspects the input, the logger inspects the transaction, the synthetic events package changes how handlers are bound, and the service worker package contributes a build artefact. The design also means the features you do not install are not present, which is the opposite of a framework where the core carries everything.
The components package is described as providing constructors and middleware, so the same extension point carries the component model as well as these four utilities.
Inline handlers become delegated listeners
The synthetic events middleware is the one with a visible behavioural change, and it is worth understanding because it changes how your code is written.
The middleware changes the event binding from inline event handlers like onclick = fn to use addEventListener. Events are attached to the body element and coordinated to children through delegation. In other words, a render transaction no longer wires a listener onto each element that has a handler, and instead one listener on body routes the event to the right child.
The motivation is the standard one for delegation. A listener per node is a listener per node to attach, to remove when the tree changes, and to keep in sync with the diff. With delegation, the handler survives a re-render of the subtree because the subtree is not what holds the listener.
The cost is that the semantics of the handler change. An inline onclick attribute receives the element as its receiver directly, while a delegated listener receives the event and has to work out which element it came from. Any component code that relied on the inline form needs to be adjusted, and the middleware is opt-in, so the migration is per project rather than per library.
It is also a change with a security angle, since inline handlers in markup and addEventListener listeners are treated differently by content security policies, though the README does not discuss that.
The Babel plugin moves parsing out of the runtime
diffHTML parses real HTML and supports JSX and tagged templates. The Babel plugin is what makes the first of those two cheap in a production build.
The plugin transforms your input into function calls, which eliminates the need for runtime parsing, and the README describes it as similar to how React compiles down JSX. The mechanism is the same idea in both cases: the markup is a build-time artefact, so the parse result can be produced ahead of the run rather than on every render.
This connects directly to the lite build in the core package. That build excludes the HTML parser, and the only way it can be safe to ship is if nothing at runtime needs to parse. A project that compiles its templates with the Babel plugin can drop the parser from the bundle; a project that renders markup from a server or from user input at runtime cannot, and the README is explicit that the smaller build is for those who want to minimize the filesize and accept the exclusion.
So the parser and the plugin are two halves of one decision: whether the HTML in your application is known at build time or arrives at runtime. The core package supports both, and the choice of build and plugin together determines which one you are actually paying for.
A Rust parser compiled to WASM, still marked coming soon
One package in the list is not ready. The diffhtml-rust-parser is marked Coming soon in the README.
The description is precise about what it would be: an alternative parser written in Rust and compiled to WASM, providing a wrapper around the tl HTML parsing library, which is then converted into a compatible diffHTML VDOM structure. The value proposition is performance of the parse step, which is the part of the pipeline that scales with the size of the markup.
The status matters for anyone evaluating the package list, because the ecosystem description in the README presents all of these modules as the foundation of diffHTML, and only one of them is unavailable. The npm install command for it is shown in the list, which makes it easy to try before reading the status line.
The Rust and WASM approach also has a structural consequence worth noting. A native parser is a dependency on a runtime that can load and instantiate a WASM module, and the README's list of supported environments, browsers, Node.js and Deno, has to be read with that in mind. It is also a second implementation of the parse step, which means the VDOM structure it produces has to stay compatible with the one the JavaScript parser produces, and that compatibility is exactly what the description promises in the phrase converted into a compatible diffHTML VDOM structure.
Lerna, a beta number, and no release history
The repository root is a lerna monorepo. The root package.json is marked private, is versioned 1.0.0-beta.30, and declares the type as module, which matters for a library that ships as ES modules. The scripts are the lerna ones:
"build": "lerna run build",
"test": "lerna run test",
"test-cov": "lerna run test-cov",
"postinstall": "lerna bootstrap",The devDependency list is one entry, lerna at ^6.0.3, and the rest of the root is conventional: an .editorconfig, a .nvmrc, a CONTRIBUTING.md, an MIT LICENSE, a lerna.json, a package-lock.json, a packages/ directory, and two shell scripts, post-cjs.sh and release.sh, that sit at the root for the packaging and release steps.
The version number is the honest summary of the project state. The README's first line after the badges says Latest version: 1.0.0-beta.30, and the root manifest agrees. A 1.0.0 with a beta suffix means the API is expected to settle but has not, and the project publishes no GitHub releases, so there is no tag history to consult and the CHANGELOG equivalent is the repository itself.
The last push was on 2026-05-29, so the tree is not frozen, but neither is there a release cadence visible from the outside. Anyone depending on it should pin exact versions, because the beta number is the only stability signal the project publishes.
Editorial conclusion
Adopt diffHTML if you want to render markup you already have, since the parser takes real HTML and the project also supports JSX and tagged templates, and if you want a virtual DOM without pulling in a full framework, because the core package works as a single imported function. Do not adopt it expecting a stable 1.0 release, since the version in the repository is 1.0.0-beta.30 and there are no GitHub releases, and do not adopt it if you need a maintained Rust-based parser, because the WASM parser package is still marked coming soon. Before you commit, install only the core package and measure your own bundle, since the README's size claims carry no numbers, and decide whether the middleware model or the Babel plugin fits your build, because the plugin changes when parsing happens rather than only how it works.
Frequently asked questions
How do I install diffHTML?
The core package is installed with npm install diffhtml, and each companion package is published separately, such as diffhtml-components, babel-plugin-transform-diffhtml and the diffhtml-middleware-linter. The README says the modules are nested in the /packages folder of the repository.
What is the difference between the diffHTML standard build and the lite build?
The standard build includes everything, while the smaller optimized build excludes the HTML parser and performance metrics, which is useful when you want to minimize the filesize. Excluding the parser only makes sense if your markup is compiled ahead of time, for example with the Babel plugin.
How does diffHTML middleware extend the framework?
The project ships four middleware packages that show the model: a linter that checks your input for well-formed HTML using rules from HTMLHint, a logger that logs diffHTML state at the start and end of every render transaction, a synthetic events package that changes event binding, and a service worker helper for PWAs.
Is diffHTML stable enough to depend on?
The version in the repository is 1.0.0-beta.30 in both the README and the root package.json, and the project has no GitHub releases, so the beta suffix is the only stability signal it publishes. The last push was on 2026-05-29.
Official sources
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.
[](https://hysenlabs.com/projects/tbranyen-diffhtml)Was this useful?