Re-purpose / reuse root for SSR hydration / prerendering.
See original GitHub issueOur current hydration implementation is broken! We are simply avoiding the duplication of your SSR nodes, but we are still patching everything from scratch. 😆
We could improve it this way:
function hydrate(element, map) {
return element
? {
tag: element.tagName.toLowerCase(),
data: {},
children: map.call(element.childNodes, function(element) {
return element.nodeType === 3
? element.nodeValue
: hydrate(element, map)
})
}
: element
}
Unfortunately, this still does not take into account empty text nodes, which if not trimmed, would yield a tree that is not consistent with the new tree returned by the view, causing patch to do more work than it needs if we were hydrating correctly.
A more complex solution could be explored, but then how much is enough and can this be moved outside core?
I’ve been thinking about this today and considered many alternatives, and here is my proposal.
Re-purpose root
for SSR-use for what-would-be the old element during the initial render.
I originally chose the name root
because it was short and simple, but now I think that root
should describe your application’s top element: <main>
, <div>
, etc., and NOT the HTML element to act as a container / host of your application tree (which is what we do now).
How will this affect existing apps?
Apps not using root
.
Everything will continue to work as usual. 🎉
Apps using root
.
Say you have this HTML:
<body>
<div id="app"></div>
</body>
and used root
like this:
app({
view: () => <h1>Hello.</h1>,
root: document.getElementById("main")
})
Now your app will render overwriting / obliterating <div id="app">
, so the final HTML will look like this:
<body>
<h1>Hello.</h1>
</body>
This is similar to how React behaves.
SSR
Using root
for SSR pages would allow us to remove this part from core.
element = document.querySelector("[data-ssr]")
And use instead:
app({
...,
root: document.querySelector("[data-ssr]")
})
Or a different data attribute; it’s up to you.
Finally, the old node for the initial render. Instead of introducing a new property, I propose using the load
event, currently unused.
app({
events: {
load(state, actions, root) {
return myOwnHydrationFormula(root)
}
}
})
so we can assign that result value to the node inside core like this:
node = emit("load", root)
Using a mixin for that would work as well. Obviously, we’d call that mixin Hydrator
!
app({
mixins: [Hydrator]
})
SSR Example
A full SSR page example would work like this:
<html>
<head>
<script defer src="bundle.js"></script>
</head>
<body>
<main data-hyperapp-ssr>
<h1>My app.</h1>
</main>
</body>
</html>
app({
view: () =>
<main>
<h1>My app.</h1>
</main>,
mixins: [Hydrator],
root: document.querySelector("[data-hyperapp-ssr]"),
})
Issue Analytics
- State:
- Created 6 years ago
- Comments:28 (27 by maintainers)
👍
I’m in favor of this idea. Replacing the root node makes more sense, and it’s one less useless
<div>
wrapper around an app. Also good that we can now specify what attributes/id to target.However you decide to move the hydration out of core, that’s probably for the best also. No reason to ship code in core that 75% of your users won’t be needing (number pull out of thin air).
By the way, I want to clarify that I do plan to use SSR in apps that require SSR, e.g., systems that want to adopt hyperapp, but are already largely SSR-based like a rails app.
But I don’t see why we should include it in core when we could use a mixin just as well.
P.S: Obviously this.