6 ms·
Show HN: Swap.js – a JavaScript micro-framework (HTML fragments over the wire)
Hi HN! I created this lib in the need of a simple and tiny framework to easily do AJAX-style navigation / replacement of fragments in the page, in a web application.
For people who don't want to use client-side-rendering and complex frameworks à la React, there are nowadays a few "HTML-over-the-wire" libraries, like HTMX, Unpoly or this super-tiny one Swap.js :)
One other key thing is that no external tool is needed: no bundler, no webpack, no TypeScript compiler, no minification needed. Just write HTML, JS (+ your preferred server-side language: PHP, Python, etc.) and it works.
The framework makes use of fetch (of course) but also MutationObserver API to be able to launch actions when parts of the DOM change.
Let me know what you think!
- deleted 3y ago[deleted]
- canadiantim 3y agoHow would you compare yourself to HTMX and Unpoly? I'm keenly interesting in this space for sure and love that you're working on another!
- devmunchies 3y agoLib is EXTREMELY minimal. Only GET requests with anchor tags. That you can’t POST forms or send metadata make it mostly useless for anything but the most basic website. I guess the creator will think about the minimal features to add in their remaining 50 LoC budget. (IMO a minimized KB target would be a better ceiling).
- breakfastduck 3y agoI mean, you can just write some code to post data to an API.
- devmunchies 3y agoIt's not about the POSTing. Its how you handle the response. The paradigm of this library is that you replace segments of the page with the response, which it doesn't support for POST requests.
- juancampa 3y agoIsn't that what <form> is for?
- djbusby 3y agoWas. for, "modern" things (libs, framework, etc) intercept these bog standard bits with their "magic" rebuilding to maybe 70% of what already worked.
- hombre_fatal 3y agoRefreshing the page to send an upvote isn’t peak UX.
- josephernest 3y agoThanks for the comment! These 2 other libraries are really great. For something as important as the choice of a web framework, I realized I really wanted to 100% understand the internals, then I starting working on Swap. The key things for me are: - no tool required (just a text editor, no compiler, no bundler/packer, no minification even needed: I just wanted to write JS and that's all). - be able to re-read the source code in 10 minutes and 100% understand how it works, which is impossible with most frameworks - even if simple, I wanted that non-trivial features are included (such as browser history, when using previous / forward browser buttons), because I don't want to have to think about this when creating a new web appliation My first prototypes took a few hundreds of lines of code (for the same result). I spent some good time redesigning it, and finally I was surprised I can cover 80% of my navigation needs (when doing web applications), with only 50 to 100 LoC. For the rest (features not enhanced by Swap, like form submission), of course, I just write normal JS, but I think I would have done it anyway whatever the framework. TL;DR: in my lib I just focused on swapping HTML content (parts of the page) when navigating, thus the name "Swap" :) + I wanted the lib to do the boring stuff for me (make sure the browser history still works, etc.)
- garganzol 3y agoImpressive, especially considering the implementation size. Just by looking at swap.js file, I could not actually believe it really works but it clearly gets the job done.
- LeonB 3y agoYes, I think it’s quite a delightful example. Small enough that it’s quite realistic to be able to read it, understand it, and add to it.
- shaunxcode 3y agoWhy fight evolution?
- dyeje 3y agoI’m bought in to this approach after using Hotwire, but I’m concerned that all the competing implementations are going to undermine adoption. Maybe we should all just use htmx and call it a day.
- deleted 3y ago[deleted]
- quickthrower2 3y agoThe nice thing is the simplicity of all these libraries means changing from one to another is no big deal. Most of the work is server side and already done. It is not like you need to swap all your React components to Vue ones.
- leke 3y agoIt's even my hope that the things in HTMX will become native to HTML one day.
- josephscott 3y agoDefinitely this. It would be great if HTMX has a similar path to jQuery, where the browsers adopt enough of the features as native that it is no longer required.
- chrismorgan 3y agoLink handling needs to check keyboard modifier state, so that you don’t intercept things like Ctrl+click. You can also work with just one event handler rather than many, which is generally easier, and more reliable if other scripts ever touch the DOM. Here’s something you could replace the first register_links() call with (and drop the other register_links call and definition): addEventListener("click", event => { var link; if ( !event.button && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey && (link = event.target.closest("[swap-target]")) ) { update(link.href, link.getAttribute('swap-target'), link.getAttribute('swap-history')); event.preventDefault(); } }); Some other minuscule patches for size and/or performance (of things a minifier won’t do): -document.querySelector("html") +document.documentElement -window.addEventListener +addEventListener -'*[swap-target]' +'[swap-target]' -fallback = null +fallback You can also simplify the code a tad by inlining function dom_changes and dom_load—though any minifier will do this for you, but I find it nicer having those functions inline to begin with. On style, I also wish for consistent single or double quotes rather than mixed. The document.createElement('html') dance feels wrong. I feel like it should be using new DOMParser().parseFromString(). I’m not sure off the top of my head if it will matter, but it feels like you’re inviting mXSS attacks. On update, you’re only applying body stuff, but you normally want to merge head changes in as well, especially document title. You’re going to run into problems with replacing document.body.outerHTML. Browser extensions and other page JavaScript regularly rely on being able to inject their own stuff onto that element, and on a stable identity for the element, too much—so you’re going to break or be broken by various user extensions and other scripts that you load beside swap.js. Major UI libraries have sometimes started with working directly on document.body, but they’ve all ended up recommending that you mount them in a subelement instead, because otherwise too many things mysteriously break. I recommend getting away from this somehow, but I’m not sure quite how—it’d definitely add required complexity. I’m not fond of how loading works: you rely upon this script being loaded blockingly, with customisation also blocking and after it. Some major uses of this script could otherwise be async and completely optional (progressive enhancement territory). Unfortunately, because of how you handle loaders, you can’t just switch to `document.readyState == "loading" ? addEventListener("DOMContentLoaded", dom_load) : dom_load()` to make it support <script async> usage, without losing functionality. You’d need to expose some other way of adding to Swap.loaders.
- krsdcbl 3y agoI'm getting a bit of deja vu from this recebt rediscovery of ajax but i really like it. Back when js was obscure and jQuery became ubiquitous, that practice was a go to and sites started to feel, albeit sluggish, like more and more like todays spa centric ecosystem. Seeing projects spawn that the default that ssr was and making those early, simple optimisation tools much more streamlined, understandable, accessible and purely functional is great!
- ulrischa 3y agoThis is much better than htmx. I like the focus on just swapping content
- replwoacause 3y agoWhy is it much better?
- joeyguerra 3y agoA new world: Part Three.
- tommica 3y agoOh nice, I am working on something similar, but didn't think of using MutationObserver for anything. But be careful with hooking to links, there situations you want to test for that might need to work like a normal link (target blank or external url for example)
- Jessica-1 3y ago[dead]
- janwillemb 3y ago> vanilla JS (no external tool needed: no bundler, no webpack, no TypeScript compiler, no minification needed...) Nice, I like that. I dread the modern way of having to jump through random hoops to get a garbled output that is supposed to contain my code as well. This is way more as I feel it is supposed to work: include a script tag.
- dvh 3y agoAh the good old element.innerHTML = xss_vector_here
- josephernest 3y agoDemo showing the library in action: https://afewthingz.com/swap-library/email/ https://afewthingz.com/swap-library/email/ PS: some music I made if you want a musical background:) https://afewthingz.com/since https://afewthingz.com/since
- harha_ 3y agoI read through the swap.js file and there's a lot of things happening. I also like the fact that it has no dependencies and it's cool that it achieves so much with so little code.
- wryun 3y agoI like it, but it feels funny to have this discussion without acknowledging this is really similar to pjax/turbolinks. Ok, now I'm feeling better; carry on!
- Ciantic 3y agoSomeone who used jQuery.load a lot back in the day, it's nice to see this trend coming back. Only thing that stands out to me is using custom properties without data prefix, e.g. <a href="newpage.html" swap-target="#container" swap-history="true">Click me</a> Shouldn't that be data-swap-target="#container" data-swap-history="true"? Is custom properties okay these days without data?
- solarkraft 3y agoI believe doing this is already quite common and there's no real drawback. As far as I know the main advantage of the data- prefix is that there's a guarantee that no official spec will use it.
- victorbjorklund 3y agoWow. That is beautiful. So concise and yet easy to understand.
- josephernest 3y agoThanks a lot! This is very motivating!
- simonw 3y agoFeature request: documentation describing the accessibility impact this will have. Any time I'm considering a library like this my number one question is always the impact it will have on accessibility tech, most specifically screenreaders. I would love it if libraries could get into the habit of describing this impact in their documentation - and ideally if they could compete on providing the best possible accessibility as one of their promoted features.
- josephernest 3y agoGood question... I haven't thought about it deeply, but I would say that using this lib itself won't have impact on accessibility: if the HTML fragments are carefully crafted to take accessibility into account, then after HTML fragments swapping the DOM will keep this aspect. I would say the construction of the HTML fragments (done by the user of this lib) is the most impactful part. Since the URL can change after a HTML-fragment-swapping with this lib, I can imagine that screenreaders will detect the URL change and take the new layout into consideration, is that right?
- simonw 3y agoI'm not 100% sure myself. My understanding is that if you swap out a piece of the page you need to sleet screenreader users to the change, but I don't know the best way to do that - I presume it involves ARIA attributes.
- replwoacause 3y agoHave you thought about adding polling functionality, so that a given element can check with the server for updates at a set interval?
- josephernest 3y agoThanks for looking at the lib! Polling is typically the kind of thing I usually prefer to handle fully manually in JS. That's what I do for my own web apps. Even if I added a specific HTML tag attribute for polling, at the end, there will always be a missing something, and I'll end up writing JS anyway. So for this kind of thing, I don't think a lib is necessary: you just setTimeout / setInterval in the script that runs when the view is loaded: Swap.loaders[".my_view_with_polling"] = () => { update_task = setTimeout(do_polling, 1000); ... return () => { // unloader function when we quit this view clearTimeout(update_task); }; }; For a real example, see my email client demo: when you open an email, there is a timer. Here it's done: https://github.com/josephernest/Swap/blob/main/demo3-email/app.js https://github.com/josephernest/Swap/blob/main/demo3-email/a...
- replwoacause 3y agoAh makes sense, thanks for the example!
- moritzwarhier 3y agoI wrote something similar by myself for a PHP CMS project with server side templating. My use case was progressive enhancement for pagination and filtering for lists, preserving all functionality with JS turned off, and using shareable "isomorphic" URLs. It worked out pretty well, my headaches mostly stemmed from the backend and the "isomorphic" part: slight tendencies towarda overengineering and hard-to-fix bugs all stemmed from the backend ("ajax vs non-ajax", simply put). Cool URIs don't change. My two cents: 1. If there is a solution tailored to your backend, use it (unless it involves complex DSLs or many dependencies) 2. If not, write the code by yourself. It's not actually hard, most work is in the backend. Use "pushState" instead of "replaceState" only after everything is proved to work. Only push URLs, no data. This is simple, low-hanging 2006ish JS. Don't use backend-agnostic cruft for this. It'll waste time. It you need complex client-side interactivity as well as SSR and/or SSG, use a full-stack TS solution or an SPA with SSR.
- shove 3y agoI built something very similar ages ago with a short php script and a line in the .htaccess It’s cool to see these ideas coming back around.