Link 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.