I have been playing around with V2 and routing.. I have a router implementation that subscribes to window.location by hooking into pushState and popstate to keep the page location in sync with the hyperapp state.. once that was working I started thinking about other features of a router.. what first came to mind is that changing "page" should update the page title and description. So I started implementing that in a quick and dirty way like:
export const meta = ({ title, description }) => {
title && (document.querySelector('title').innerText = title)
description && (document.querySelector("meta[name='description']").content = description)
}
But then I got into the usual document.querySelector mess that vdom diff/patch avoids. So I thought what if you could manage the document head with the vdom. Which led me to this discovery:
app({
init: {},
view: state =>
html([
head([
title('Yoyoyoy'),
meta({ type: 'description', content: 'Hello Wolrd' }),
style(`html { background: red }`),
]),
body([
h1('Welcome to my app'),
p('Hyperapp manages the whole document including the head'),
]),
]),
container: document,
})
If you specify document as the container for the app.. then you can use vdom all the way down – including the document head. As @jorgebucaran pointed out on slack, there is nothing exceptional happening here, the DOM and the vdom are just doing what they should do according to their respective APIs. Nevertheless it kind of blew my mind.
Your index.html becomes just a single line which imports your main javascript file:
<script src="/index.js" type="module"></script>
Why had we not tried this before? Are there any pitfalls or advantages? Could this be a feature and does it open doors for any other cool stuff (other than being able to manipulate title and meta tags) like dynamically adding styles/scripts? Does it make SSR easier or harder?
I'd love to hear your thoughts @zaceno @frenzzy @Alber70g @ngryman @SkaterDad et al.
This is certainly an interesting idea. It's sort of funny that the app keeps running after the <script> tag is removed from the document.
My main concern would be interop with 3rd party stuff, and possible browser inconsistencies. Have you tried it on multiple browsers? Might be worth searching around to see if any other JS framework communities have tried it?
Sure.. it is totally experimental 😅 I have seen scripts running after they have been removed before and this particular example seems to work in Chrome, Firefox, Safari on Desktop and Safari and Chrome on iOS. That is all the browsers I have to test on.
I see integrating this with an existing web app impossible (because it wipes the whole document and takes control) but I don't see it making much difference the other way around. That said.. I haven't tried yet. Anything in particular that you think might prove an issue?
I looked briefly to see if others had done it but to no avail.. although here is react rendering to document which seems to work ok https://codesandbox.io/s/4r8k9jy7n0.
Anything in particular that you think might prove an issue?
If users have browser extensions that rely on adding/removing things from the DOM, your app would probably conflict. It's not something I think about often, but it seems possible.
I also just tested this in IE11, and it errors out, since document.children does not exist. MDN has a browser compat table for this, but it might be wrong since you said it worked for you on iOS.
Document is less safe because it can only contain one child; the root document element that the browser always creates. This root node is document.documentElement and in contrast to document.body exist before any script execution so you may default to that when document or nothing is explicitly passed as the container.
I like the fact that it can only contain one child.. it aligns with the root view in a hyperapp which has to be a single DOM node with children.
I tried document.documentElement first but it didn't behave as I wanted because of the single child thing; the documentElement seemed to be the html tag which expects two children (head and body) which hyperapp can't render side by side without a parent node. Which begged the question "what tag do you wrap head and body in?" the answer seemed obvious "an html tag" so I bumped up to document and created my own html tag which seemed to work as expected.
That again goes to the limitation that HyperApp could not render an array as the app’s root.
Most helpful comment
If users have browser extensions that rely on adding/removing things from the DOM, your app would probably conflict. It's not something I think about often, but it seems possible.
I also just tested this in IE11, and it errors out, since
document.childrendoes not exist. MDN has a browser compat table for this, but it might be wrong since you said it worked for you on iOS.