Hi! Idiomorph maintainer here. Congrats on the release! It's very, very cool to see how Morphlex is tackling some of the trickier subtleties of DOM morphing. We're in the R&D phase for the next version of Idiomorph, so its awesome to see how you're pushing things forward. I can see some overlap, like with `isEqualNode` (cheers to D* for cluing me into this API). But some of your ideas seem totally fresh, like your approach to solving the reordering problem. I'm looking forward to investigating whether any of these ideas would make sense in Idiomorph, and over all, I'm very pleased to see energy being put into making turn-key SSR more viable!
I’m curious, what got you interested in solving this particular problem? I.e. what was your specific use case?
Most websites work fine with plain html. If you need something fancier, the world seems to have settled on using React.
I get that this is to let you render html on the backend and then stream it to the site so that JS can update the dom. But why? Genuine question; I’m not saying there’s no good reason.
Although the OP created it for SSR, these libraries are handy for SPAs as well.
Rendering the whole DOM tree (instead of VDOMs) is a fast process. The slow part is attaching (committing) elements to the doc. e.g., I have a test of 20,000 elements which takes <30ms to render, while attaching them takes 120ms.
Since the performance is mainly bound to the commit phase, libraries like these (and hopefuly a native API) help for creating simple UI frameworks. For example, a helper such as:
function createElement(tag, props, ...children) {
const elem = document.createElement(tag)
for (const [k, v] of Object.entries(props || {}))
if (k === 'ref') v.elem = elem
else if (k === 'style') Object.assign(elem.style, v)
else if (k.startsWith('on')) elem.addEventListener(k.slice(2).toLowerCase(), ...[v].flat())
else if (k in elem) elem[k] = v
else elem.setAttribute(k, v)
elem.append(...children.flat().filter(Boolean))
return elem
}
could be used, like:
function ResetButton() {
return (
r('button', {
className: CSS.ResetButton,
onClick: store.reset
}, 'Reset'))
}
function render() {
document.body.replaceChildren(App())) // but mergeChildren
}
Both Elixir Phoenix and Ruby on Rails use plain HTML by default but they both support view morphing (phoenix via LiveView and rails via Hotwire Turbo). It really doesn't cost anything to add it. Clicking links with a bit of caching can make it feel near instant the way a (small) SPA does. Adding link prefetching algo on top of the that and it will seem even faster.
If anything it removes a ton of the argument for using React absent maybe a small subset of highly complex UI subcomponents which may need it, but rarely required for a whole SaaS app. Frontend teams just want React so they can use a single tool not because it's the best solution.
I've written SSR SPA frameworks with basic DOM "morphing" - e.g. I need to keep a sidebar from changing the HTML content/state when you click on a link, and I've always found advanced DOM morphing to be sketchy/bug prone and unnecessary.
The way I do it is to update everything _except_ for the DOM nodes that need to be excluded (via data attributes), e.g. the sidebar or a video player. I have found no problems with this approach as I maintain state since the JS is already running before clicking a link, and everything else is updated.
I think this is for if you absolutely have to SSR your markup at all times (e.g. using HTMX), but with something like Alpine.js and using <template> elements, there is no reason to DOM morph.
And like you say, if you need to use crazy advanced DOM morphing, you should probably be using a client side framework anyway. If not, I've gotten away with some very tricky state updates with just Alpine.js.
My specific use case was building a form where each change to an input would fetch a new copy of the form from the server and morph it in place.
It means the server-side code can be really simple. You can make parts of the form depend on the values of other parts. For example you can show/hide a section based on a checkbox or fill a select with options based on a previous selection.
Because it was a form, it was really important to maintain object identity and state perfectly so the user would not be interrupted.
If you read the first 5 sentences of the article you’d see there are at least 3 popular front end libraries that do morphing. I think suggesting the world has settled on anything when it comes to technology is very silly.
Super interesting, I'm just getting into DOM morphing libraries to try to patch around what I've found so far as the main limitation of the htmx approach: losing DOM on swaps. I started a discussion around some of the issues here: https://github.com/bigskysoftware/htmx/discussions/3501
Someone proposed using ideomorph, but it doesn't seem to address the issues I've encountered. Curious if you think a different morphing approach would help?
I've been working on some prototypes for possible immutable DOM APIs and though I haven't gotten that far in experimenting with it I've been expecting to encounter the same problem that this library is designed to solve: in my system the DOM will be represented as a deeply immutable tree of JS objects, arrays, and such, so a state update might consist of being given references to two immutable trees, the current state and the desired state, and from there you need to compute a minimal set of changes so that you don't redo layout and drawing work that doesn't need to be redone for parts of the DOM that are unchanged. This sounds like exactly the algorithm you'd want to do that! So basically it could allow me to use the immutable DOM representation as the source of truth and then diff and sync the new state onto the mutable DOM
The novelty here is identifying nodes in a list more consistently vs existing options like morphdom. There is a ton of prior art you can draw from, this is called a virtual DOM and is the approach used by React and many contemporary libraries.
I don't think merging is an accurate term here. Merging makes me think you take the union of both. But in this case you have two DOM structures and want to make one exactly like the other. And morphing is a good term because you want to change it into the other, not just replace it.
Is there a library that can work with JSX? I'd like to render JSX on the server and only send the diff to the client. I started writing some experimental code, but it was a lot of work.
Wait but, don't you render the jsx to html? So you you can still perform the diff on the rendered html?
Also, how do you know what you rendered last time? When you do the diff between what's in the browser and what the server just gave you, you have both sides. If you do it server side, you could... Render twice, once before and once after the state change? Or keep a server side cache?
The server would keep a copy of the virtual dom during the session. Each change would be diffed and the diff sent to the client to apply. The client would only apply apply changes from the server, not change the dom on its own, so they wouldn’t get out of sync.
I can render to html, but then I’d need to parse the html back do diff it. Seems stupid.
I don't understand the hate that SPAs get when these are the hoops people will ultimately jump through to render stuff on the server. Maybe you really do have an application at hand, not a "web page" (whatever that is in current year), and then you might as well use the SPA approach to implement it.
I don’t hate SPAs, I just think some apps are better off being MPAs. I wouldn’t build a todo list app as an MPA. But many apps really are just CRUD forms and tables.
Most websites work fine with plain html. If you need something fancier, the world seems to have settled on using React.
I get that this is to let you render html on the backend and then stream it to the site so that JS can update the dom. But why? Genuine question; I’m not saying there’s no good reason.
The world might have, but I personally have not!!! x(
(I don't think the world really has, the same way the world moved on from jQuery at some point :) and jQuery was probably more widespread)
Rendering the whole DOM tree (instead of VDOMs) is a fast process. The slow part is attaching (committing) elements to the doc. e.g., I have a test of 20,000 elements which takes <30ms to render, while attaching them takes 120ms.
Since the performance is mainly bound to the commit phase, libraries like these (and hopefuly a native API) help for creating simple UI frameworks. For example, a helper such as:
could be used, like: Here's an example of using that helper:https://github.com/ericfortis/mockaton/blob/main/src/client/...
If anything it removes a ton of the argument for using React absent maybe a small subset of highly complex UI subcomponents which may need it, but rarely required for a whole SaaS app. Frontend teams just want React so they can use a single tool not because it's the best solution.
Full page reloads are fine for most CRUD cases. Then layering DOM morphing can be even better UX almost for free
The way I do it is to update everything _except_ for the DOM nodes that need to be excluded (via data attributes), e.g. the sidebar or a video player. I have found no problems with this approach as I maintain state since the JS is already running before clicking a link, and everything else is updated.
I think this is for if you absolutely have to SSR your markup at all times (e.g. using HTMX), but with something like Alpine.js and using <template> elements, there is no reason to DOM morph.
And like you say, if you need to use crazy advanced DOM morphing, you should probably be using a client side framework anyway. If not, I've gotten away with some very tricky state updates with just Alpine.js.
I have a soft spot for Alpine and I’m always on the lookout for things I can do with just Alpine.
It means the server-side code can be really simple. You can make parts of the form depend on the values of other parts. For example you can show/hide a section based on a checkbox or fill a select with options based on a previous selection.
Because it was a form, it was really important to maintain object identity and state perfectly so the user would not be interrupted.
*Edit fixed typo.
Someone proposed using ideomorph, but it doesn't seem to address the issues I've encountered. Curious if you think a different morphing approach would help?
(DOM Morph RealTime HyperText)
I've been working on some prototypes for possible immutable DOM APIs and though I haven't gotten that far in experimenting with it I've been expecting to encounter the same problem that this library is designed to solve: in my system the DOM will be represented as a deeply immutable tree of JS objects, arrays, and such, so a state update might consist of being given references to two immutable trees, the current state and the desired state, and from there you need to compute a minimal set of changes so that you don't redo layout and drawing work that doesn't need to be redone for parts of the DOM that are unchanged. This sounds like exactly the algorithm you'd want to do that! So basically it could allow me to use the immutable DOM representation as the source of truth and then diff and sync the new state onto the mutable DOM
https://github.com/geon/react-node-diff/blob/main/src/diff-r...
Also, how do you know what you rendered last time? When you do the diff between what's in the browser and what the server just gave you, you have both sides. If you do it server side, you could... Render twice, once before and once after the state change? Or keep a server side cache?
I can render to html, but then I’d need to parse the html back do diff it. Seems stupid.
I tried building a redux-style spa like that. Worked fine. https://github.com/geon/server-side-spa/tree/main/src/server
I looked it up on Github and they seem to be using the idiomorph package.
[1] https://dev.37signals.com/turbo-8-released/