This repository discusses multiple approachs to efficiently serialize web component to HTML and rehydrate the generated markup on the client.
The HTML specification doesn't provide today a way to declaratively define a shadow tree via HTML. In order to properly implement Web Component server-side rendering, it will require to send enough information along with the generated HTML for the browser to properly recreate the component tree. Now that JSDOM supports shadow DOM and that custom element is currently under active work, we can start discussing what would be the best way to transfer web components over the wire.
Each approach discussed below is associated with 2 javascript files:
serialize.js
: exposes a method to convert a DOM tree to a valid HTML stringrehydrate.js
: script injected along with the generated HTML to recreate the DOM treeThis project also contains a set of sample pages in the sample directory with the associated pre-rendered page for each approach.
This approach is similar to the work done by Trey Shugart in @skatejs/ssr. With this approach, each shadow root is transformed into a standard element (<shadow-root>
) and all the elements in the original shadow tree are moved into the element.
<x-foo>
#shadow-root
<p>
Hello
<!-- Serialized -->
<x-foo>
<shadow-root> <p>Hello</p></shadow-root></x-foo
>
</p></x-foo
>
During serialization, <script>
tags are inserted along each <shadow-root>
element. This way once the parser has done processing the content of the tag, it can be updated in place by: creating a new shadow root on the parent element, assign its children to the shadow root and remove itself from the DOM.
This approach as the benefit to be simple and straight-forward at the same time since there is not much DOM manipulation. However, the main drawback of this approach is that slotted content is not in the right place which can hurt SEO.
<x-foo>
#shadow-root Hello
<x-strong>
#shadow-root
<strong>
<slot>
world !
<!-- Serialized -->
<x-foo>
<shadow-root>
Hello
<x-strong>
<shadow-root>
<strong>
<slot> world !</slot></strong
></shadow-root
></x-strong
></shadow-root
></x-foo
></slot
></strong
></x-strong
></x-foo
>
As you can see in the previous example, the world
text node instead of being wrapped into the <strong>
tag, its a child node of the <x-strong>
element.
This approach is comparable to the previous one, except that instead of adding a <script>
along the <shadow-root>
, a native <shadow-root>
custom element is defined at the end of the page. During the custom element upgrade, it executes a similar algorithm that described above to recreate the right DOM structure.
In this approach, the DOM tree is serialized into a flat tree, where each node is assigned to the proper assignedNode
.
During serialization:
shadow-root
attributeslot-fallback
attribute<template>
tag with a slot-fallback
attribute in order to keep the slot fallback semanticWhile this approach requires more DOM manipulation compared to the 2 previous one, this approach provides better SEO since it keeps the slotted content at its expected place in the DOM.
sample | tree of trees - script | tree of trees - custom element | flat tree |
---|---|---|---|
simple | example | example | example |
nested | example | example | example |
slot | example | example | example |
slot-fallback | example | example | example |
slot-named | example | example | example |
slot-nested | example | example | example |
large | example | example | example |
npm run serve # start local server
npm run serialize # update all html the generated prerendered pages
npm run validate # run the validation tests against the generated pages