bucklescript-tea icon indicating copy to clipboard operation
bucklescript-tea copied to clipboard

Experiment of initializing to pre-rendered DOM

Open samuli opened this issue 5 years ago • 5 comments

This is an experiment of initializing a bucklescript-tea application to a pre-rendered DOM.

The app is provided with a flag at startup that tells if the parent node already includes rendered HTML which should be preserved.

Apart from supplying the application with the flag, the only changes are:

  • initializing the priorRenderedVdom either to empty or to vdom of initial model state
  • skipping the emptying of the parent node

The page does not flash when bucklescript-tea is inited. Chrome developer-tools claims that the DOM is preserved and does not get re-rendered at page load. Page scroll position is also preserved.

Am I missing something or could this work?

Minimal example for testing: https://github.com/samuli/bucklescript-tea-server-render

samuli avatar Nov 23 '18 19:11 samuli

This was an entirely planned use-case! I just never got around to it. ^.^

I was initially thinking of another pattern to do it but this hydrate way is interesting, and since undefined is false then it should be backwards compatible with the current javascript API (though would change the ocaml API, but trivially so).

In addition to this might need to flesh out the vdom->htmlString converter function, the existing one was just for debugging so it no doubt could use some work. ^.^;

Also on the ref [view initModel] in the if hydrate = true then branch it probably needs to be recursively gone over to 'clean up' of dynamic elements like listeners so the HTML will get properly populated when the initial run is executed. Such a cleanup function would belong in the vdom module itself of course.

This could grow to a fairly useful PR if you are wanting to do it. :-)

OvermindDL1 avatar Nov 26 '18 20:11 OvermindDL1

For note, the original pattern I was thinking of was a new vdom function to merge a vdom into an "existing" DOM, instead of comparing two vdom's, and call the one to merge instead of diff/merge instead. This may still be overall more generically useful and more performant, but it is a lot more code to write.

OvermindDL1 avatar Nov 26 '18 20:11 OvermindDL1

Also on the ref [view initModel] in the if hydrate = true then branch it probably needs to be recursively gone over to 'clean up' of dynamic elements like listeners so the HTML will get properly populated when the initial run is executed.

Good point. I didn't think about this... Any idea how we can remove the "pre-rendered" listeners without having a reference to the actual listener (which is needed by removeEventListener)?

I can only think of the following workarounds:

  • Put an extra attribute to the parent node and all child nodes that have dynamic properties. If needed, TEA would traverse the parent and replace all flagged child nodes.
  • Require users to override addEventListener and pass the listeners to TEA at startup
  • Use getEventListeners (non-standard)

I think only the first alternative could be somewhat usable. But would it have a realistic use-case? If TEA is a component that communicates with some other part, you could have, say, a click handler that would work before TEA is inited.

Or could hydrate be a feature that is known and documented to work with only static markup?

I was initially thinking of another pattern to do it but this hydrate way is interesting, and since undefined is false then it should be backwards compatible with the current javascript API (though would change the ocaml API, but trivially so).

I also considered having "hydrated" versions of program types (standardProgramHydrated, beginnerProgramHydrated...).

samuli avatar Nov 27 '18 06:11 samuli

Good point. I didn't think about this... Any idea how we can remove the "pre-rendered" listeners without having a reference to the actual listener (which is needed by removeEventListener)?

Reasons like this a dedicated 'merge' instead of patch call in the vdom might be more useful, but that is why recursively iterating through the vdom node as it stands is good as you can then just remove the listeners, that way when the first patch happens then it will patch the no-listeners vdom to the listeners vdom. 'Should' be a relatively simple function, I could probably whip it up into the vdom.ml file today if you want to merge it in and use it if it would help?

Put an extra attribute to the parent node and all child nodes that have dynamic properties. If needed, TEA would traverse the parent and replace all flagged child nodes. Require users to override addEventListener and pass the listeners to TEA at startup Use getEventListeners (non-standard)

Eh, none of those are really necessary, just 'sanitize' the first view call to get rid of them instead, much easier. :-)

Or could hydrate be a feature that is known and documented to work with only static markup?

This is what I'd do regardless, you can't assume a lot of things, and this is also why I think doing an in-dom patch instead of a vdom->vdom patch might be better for the first iteration as it would 'fixup' the DOM regardless, that way the person making it could even have html that doesn't exist in there like a "Please wait" message or something, it would be all around more useful... I think I'm leaning more to that pattern... I'll see if I can add it to the vdom function sometime today and that way you could then make use of it in your patch to use it instead.

I also considered having "hydrated" versions of program types (standardProgramHydrated, beginnerProgramHydrated...).

Except that constrains it to being enforced by program instead of usage, which means something could either build inline or it could build anew. I'm thinking it might be better to just 'transform' all existing ones to an inline merge instead, so it would just 'always' do that. It would have no functionality changes for the existing usage but it would just transparently add the capability for upgrading static HTML. :-)

OvermindDL1 avatar Nov 27 '18 15:11 OvermindDL1

think I'm leaning more to that pattern... I'll see if I can add it to the vdom function sometime today and that way you could then make use of it in your patch to use it instead.

Yes, that would be good. I'm still a bit confused how the removal of the pre-rendered events could be done without re-rendering the nodes...

samuli avatar Nov 27 '18 15:11 samuli