Show HN: µJS, a 5KB alternative to Htmx and Turbo with zero dependencies
Posted by amaury_bouchard 3 days ago
I built µJS because I wanted AJAX navigation without the verbosity of HTMX or the overhead of Turbo.
It intercepts links and form submissions, fetches pages via AJAX, and swaps fragments of the DOM. Single <script> tag, one call to `mu.init()`. No build step, no dependencies.
Key features: patch mode (update multiple fragments in one request), SSE support, DOM morphing via idiomorph, View Transitions, prefetch on hover, polling, and full HTTP verb support on any element.
At ~5KB gzipped, it's smaller than HTMX (16KB) and Turbo (25KB), and works with any backend: PHP, Python, Go, Ruby, whatever.
Playground: https://mujs.org/playground
Comparison with HTMX and Turbo: https://mujs.org/comparison
About the project creation, why and when: https://mujs.org/about
GitHub: https://github.com/Digicreon/muJS
Happy to discuss the project.
Comments
Comment by recursivedoubts 2 days ago
i have added it to the htmx alternatives page:
Comment by tagfowufe 2 days ago
Comment by fleahunter 2 days ago
Comment by amaury_bouchard 2 days ago
Comment by gavmor 2 days ago
I just struggle to envision what application benefits from the efficiency that this or htmx offer, but from neither the ultra-interactive, nor the ultra-collaborative. Maybe updating stock ticker prices? Fast-service back-of-house ticketing displays?
I would love to feel called to reach for this library.
Comment by xhcuvuvyc 2 days ago
Relying on server side as the source of truth inverts complexity. Suddenly 90% of the modern webs problems evaporate. Turns out building everything on top of a shadow dom in the client creates a boatload of unforced errors.
Comment by amaury_bouchard 2 days ago
Comment by qingcharles 2 days ago
Comment by nattaylor 2 days ago
htmz is a minimalist HTML microframework for creating interactive and modular web user interfaces with the familiar simplicity of plain HTML.
Comment by amaury_bouchard 2 days ago
Comment by ok_dad 2 days ago
htmz is a masterclass in simplicity. It’s gotta be the all time code golf winner.
Comment by oso2k 2 days ago
Comment by whiterock 2 days ago
Comment by lastdong 2 days ago
Comment by amaury_bouchard 1 day ago
Comment by oso2k 2 days ago
There’s Artifex’s interpreter from muPDF. It’s also the basis of several JS related projects: https://mujs.com/
There’s also a lesser known interpreter: https://github.com/ccxvii/mujs
And IIRC, there was a CommonJS library of the same name.
Comment by amaury_bouchard 2 days ago
Comment by stanfordkid 2 days ago
Comment by amaury_bouchard 2 days ago
µJS handles the AJAX call and swaps the fragment in the DOM. No JavaScript to write, no state to manage client-side. jQuery is more powerful and flexible, but that flexibility comes with complexity: you have to write the fetch call, handle the response, find the right DOM node, update it.
µJS does all of that declaratively via HTML attributes. It's not for every use case — highly interactive apps (think Google Maps or a rich text editor) still need proper JavaScript. But for the vast majority of web pages, server-rendered HTML fragments are simpler, faster to develop, and easier to maintain.
Comment by freakynit 2 days ago
Comment by amaury_bouchard 2 days ago
Let's take an example. Say we have a website with 3 pages:
- Homepage "website.com/"
- Products "website.com/products"
- About "website.com/about"
Your browser can load any of these pages directly, as usual. But when you are on the homepage (for example) and you click on the "About" link, µJS load the "/about" URL, and replace the current page's `<body>` tag with the fetched page's one.
It's that simple.
Comment by freakynit 1 day ago
Comment by vbernat 2 days ago
Comment by lofaszvanitt 2 days ago
Comment by logicprog 2 days ago
Comment by josephernest 2 days ago
If you want something even more minimalistic, I did Swap.js: 100 lines of code, handles AJAX navigation, browser history, custom listeners when parts of DOM are swapped, etc.
https://github.com/josephernest/Swap.js
Using it for a few production products and it works quite well!
Comment by amaury_bouchard 2 days ago
Comment by zoezoezoezoe 1 day ago
Comment by amaury_bouchard 1 day ago
That said, the vast majority of websites are purely transactional: click a link, load a page; submit a form, load a page. For those, there's little reason to add a full frontend framework. HTML-over-the-wire can improve responsiveness without adding complexity.
Comment by zoezoezoezoe 20 hours ago
I have been extremely bearish on html-over-the-wire solutions from the minute I saw and understood what HTMX was trying to achieve. In my eyes, the only way to truly achieve what I, and users, expect in a web page is with a SPA. I understand the hesitation to use heavier SPAs, but my hesitation to fetching html from the server after the page load to update the page is much larger. But I also do understand how html-over-the-wire provides a good middle ground between web 1.0 apps where basically every interaction reloaded the entire page, and web 2.0 apps that feel closer to an actual application rather than a website.
Comment by gaigalas 2 days ago
I've done this previously with morphdom to AJAXify a purely server-driven backoffice system in a company.
I would love something even smaller. No `mu-` attributes (just rely on `id`, `href`, `rel`, `rev` and standard HTML semantics).
There's a nice `resource` attribute in RDFa which makes a lot of sense for these kinds of things: https://www.w3.org/TR/rdfa-lite/#h-resource
Overall, I think old 2015-era microdata like RDFa and this approach would work very well. Instead of reinventing attributes, using a standard.
Comment by amaury_bouchard 2 days ago
Comment by 0x20cowboy 2 days ago
Comment by ohghiZai 3 days ago
Comment by amaury_bouchard 2 days ago
That said, µJS and Datastar have quite different philosophies. µJS is a lightweight AJAX navigation library (~5 KB); it intercepts links and forms, swaps fragments, and stays out of your way. There's no client-side state: your server renders HTML, µJS delivers it.
Datastar is more of a reactive hypermedia framework. It brings client-side signals (reactive state in HTML attributes, à la Alpine.js) and uses SSE as its primary transport: the server pushes updates rather than the client fetching them. It's a different mental model: Datastar manages state and reactivity, while µJS is purely about navigation and content replacement.
Both are small, zero-build-step, and attribute-driven, so the comparison is definitely interesting. I'll look into adding it!
Comment by scuff3d 2 days ago
Comment by amaury_bouchard 2 days ago
Comment by jadbox 2 days ago
Comment by amaury_bouchard 2 days ago
Comment by jadbox 1 day ago
That said, I worry about Form elements being controlled by uJS to use a different REST method (like DELETE), and this would cause JS-free browsers to break.
Comment by amaury_bouchard 1 day ago
Your concern about `mu-method="delete"` is valid. Without JS, the browser will fall back to the form's native method (usually GET or POST). The classic solution is the `_method` hidden field pattern: the form submits POST with a `_method=DELETE` field, and the server reads that field to route the request correctly. µJS doesn't need to implement anything special for this, it's a server-side convention
Comment by amluto 1 day ago
Also, perhaps the CDN script snippets in the getting started page should include the integrity attribute.
Comment by amaury_bouchard 1 day ago
On relative paths: the current behavior is intentional for simplicity. µJS checks whether the URL starts with a slash (but not a double slash) to identify internal links. No one has reported this as an issue so far, but it's a valid feature request and I'll keep it in mind for a future version.
On the integrity attribute: the reason it was missing is that the library was evolving quickly and the hash would have changed with every release. Now that it's stable, I'll add it.
Comment by amluto 1 day ago
If I were to adopt µJS, though, I would probably want to opt in on a per-link basis.
Comment by amaury_bouchard 1 day ago
That said, proper support for relative paths is on the roadmap.
Comment by katsura 2 days ago
One gripe with the name though is that I’m used to uTorrent’s and uws’s use of “u” for the “µ” character. So, my first guess would be to look for uJS to find this project, not muJS.
Comment by amaury_bouchard 2 days ago
I deliberately chose the phonetic spelling 'mu' of the 'µ' greek letter, rather than the typographic approximation 'u'. The 'u' convention comes from ASCII limitations; when µ wasn't available, 'u' was used as a fallback. Since we're well past those limitations, 'mu' felt more accurate and more original.
Comment by h4ch1 2 days ago
Could you please add all sources as tabs? For example in Form (GET) I would really like to see /demos/search-results.html and the same goes for other examples.
Thanks!
Comment by amaury_bouchard 16 hours ago
Comment by h4ch1 14 hours ago
Comment by amaury_bouchard 2 days ago
Comment by _HMCB_ 2 days ago
Comment by amaury_bouchard 2 days ago
In the Playground, the scroll-to-top behavior is intentiona, it's there to illustrate that feature.
Comment by ranger_danger 2 days ago
Sorry if I need to use existing APIs I cannot change.
Comment by WesolyKubeczek 2 days ago
Comment by amaury_bouchard 2 days ago
Comment by scuff3d 2 days ago
As someone else mentioned, having your own server backend act as an intermediary between your front end and the API that serves JSON is probably the most straightforward solution to keep everything HTMX-y.
Comment by williamcotton 2 days ago
Browser -> your server route -> server calls API -> server renders HTML -> htmx swaps it?Comment by lioeters 2 days ago
Is there a mechanism for loading HTML partials that require additional style or script file? And possibly a way to trigger a JS action when loaded? For example, loading an image gallery.
Comment by amaury_bouchard 2 days ago
Comment by lioeters 2 days ago
Comment by heddycrow 2 days ago
I'll be checking this out. Any chance you (or anyone) has had a run with this lib + web components? I'd love to hear about it.
Comment by amaury_bouchard 2 days ago
Comment by heddycrow 2 days ago
Comment by captn3m0 2 days ago
Comment by amaury_bouchard 2 days ago
Comment by pwdisswordfishy 2 days ago
Comment by amaury_bouchard 2 days ago
Comment by panny 1 day ago
Comment by amaury_bouchard 22 hours ago
Comment by reverseblade2 2 days ago
As of now you don't need any frameworks for this. The new command and navigation apis, would just do the same trick.
Comment by amaury_bouchard 2 days ago
On the native APIs: yes, the Navigation API and Speculation Rules are exciting, but browser support is still uneven. µJS works today across all modern browsers without any configuration. That said, I agree the gap will narrow over time.
Comment by poobrains 2 days ago
Comment by reverseblade2 2 days ago
Comment by swaminarayan 2 days ago
Comment by amaury_bouchard 2 days ago
Here's what I deliberately left out compared to Turbo (~25KB gzip) and htmx (~16KB gzip):
- No client-side template/extension system. htmx has a full extension API, header-based control flow (HX-Trigger, HX-Redirect, HX-Reswap...), and dozens of response headers the server can use to command the client. µJS has 5 custom headers total; the server returns HTML, the client renders it.
- No CSS selector-based targeting in attributes. htmx lets you write hx-target="closest tr", hx-target="find .result", hx-target="next div" with a mini query language. µJS uses plain CSS selectors only (mu-target="#id"), no traversal keywords.
- No built-in transition/animation classes. htmx adds/removes CSS classes during swaps (htmx-settling, htmx-swapping, htmx-added) with configurable timing. Turbo has turbo-preview, transition classes, etc. µJS defers to the native View Transitions API: zero animation code in the library, and it falls back silently.
- No client-side cache or snapshots. Turbo keeps a page cache for "instant" back-button rendering and manages preview snapshots. µJS stores only scroll position in history.state and lets the browser's own cache + fetch() handle the rest.
- Patch mode simpler than Turbo Frames. Turbo has <turbo-frame>, a custom element with lazy-loading, src rewriting, and frame-scoped navigation. µJS handles multi-fragment updates via patch mode (mu-mode="patch") with regular HTML elements. No custom elements, no frame concept. It's simpler but it works perfectly.
- No request queuing or throttling. htmx has hx-sync with queuing strategies (queue, drop, abort, replace). µJS simply aborts the previous in-flight request. One request at a time per navigation.
- No form serialization formats. htmx supports JSON encoding (hx-encoding), nested object serialization, etc. µJS uses the browser's native FormData for POST and URL params for GET. That's it.
- No JavaScript API surface to speak of. htmx exposes htmx.ajax(), htmx.process(), htmx.trigger(), event details, etc. µJS exposes mu.init(), mu.fetch(), mu.render(), and a few setters and a few events. The goal is that you shouldn't need the JS API — the HTML attributes should be enough.
What I kept: the features that cover 90% of real-world use cases. AJAX navigation, 8 injection modes, multi-fragment patch, morphing (via Idiomorph), SSE, prefetch, forms with all HTTP verbs, triggers with debounce/polling, progress bar, history/scroll management. Just without the layers of abstraction around them.
The philosophy: if the browser already does it (View Transitions, FormData, fetch, AbortController, history API), don't reimplement it.
Comment by majorchord 2 days ago
Comment by networked 2 days ago
A better thing to suggest is to use multiple forges, including GitHub, and mirror your projects across them. This way you will have exposure and options; you won't be as tied to any one forge.
Comment by majorchord 2 days ago
Comment by networked 2 days ago
Comment by satvikpendem 2 days ago
Comment by hombre_fatal 2 days ago
That link you provided only points out GitHub has integrated "create pull request with Copilot" that you can't opt out of. Since anyone can create a pull request with any agent, and probably is, that's a pretty dated complaint.
Frankly not very compelling reasons to ditch the most popular forge if you value other people using/contributing to your project at all.
Comment by amaury_bouchard 2 days ago
Comment by satvikpendem 2 days ago