fix: enable hydration for Lit elements used as raw HTML tags#7
fix: enable hydration for Lit elements used as raw HTML tags#7piotrekwitkowski wants to merge 1 commit intoSemantic-Org:mainfrom
Conversation
Points @semantic-ui/astro-lit at our fork which fixes hydration for Lit elements used as raw HTML tags without client:* directives. Upstream PR: Semantic-Org/Astro-Lit#7
7c0c653 to
3292a32
Compare
|
Update from testing in production (cumulus-ui docs site, 83 pages, 80+ Lit components): Static components (button, container, header, link, badge, etc.) hydrate perfectly — SSR output matches client render, hydrate() adopts the DSD content, zero flicker. Dynamic components (side-navigation, breadcrumb-group, table — those with attribute:false properties set via inline scripts) still produce duplicate shadow DOM content after hydration. The replaceSSRContent path clears and re-renders the parent, but nested child components that were already upgraded produce duplicate .root divs. The Vite plugin approach (auto-prepending hydration-support.js into Astro script chunks) works correctly. The SSR guard (skipping transform during SSR build) works. The _$AG flag fix works. The remaining issue is specifically in how replaceSSRContent interacts with already-upgraded nested Lit elements. |
Hydration support was injected via 'before-hydration', which only fires for pages with Astro client:* directives. Pages using Lit elements as raw HTML tags never got hydration, causing duplicate shadow DOM content. The fix splits hydration into two phases: 1. A synchronous inline script in <head> that sets up the globalThis.litElementHydrateSupport callback before any module script can import lit. 2. A page-level module that patches LitElement.update() to handle elements with defer-hydration whose SSR output may not match the client render (replaceChildren + fresh render instead of hydrate). At build time, we check whether @lit-labs/ssr-client already handles deferred hydration natively. If it does, both of the above are skipped and we fall back to the original before-hydration import.
3292a32 to
997f6d9
Compare
|
Thanks I'll take a look sometime next week. |
Fixes #6
Problem
Hydration support is injected via
before-hydration, which only fires for pages withclient:*directives. Pages using Lit elements as raw HTML tags (<my-element>) get SSR'd with DSD but never hydrated, causing duplicate shadow DOM content.On top of that,
globalThis.litElementHydrateSupportis a one-shot callback — LitElement checks for it once during class initialization.injectScript('page')creates a separate module entry point, and Vite may evaluatelitbefore the callback is set, so it's too late.Approach
Three pieces working together:
1.
hydration-support-global.js— injected ashead-inlineSynchronous
<script>that setsglobalThis.litElementHydrateSupportbefore any module loads. PatchescreateRenderRoot(reuse DSD shadow roots, set_$AGflag),connectedCallback(respectdefer-hydration), andattributeChangedCallback(resume on attribute removal).2.
hydration-support.js— loaded via Vite pluginPatches
LitElement.prototype.update()to choose betweenhydrate()andreplaceChildren() + render(). The decision:defer-hydration→ replaceAlso removes
defer-hydrationfrom document-level elements viaqueueMicrotask, and stores__childPart+ resetsrenderOptions.renderBeforeto prevent duplicate content on re-renders.3. Vite transform plugin
Prepends the
hydration-support.jsimport into Astro<script>modules (matched bytype=scriptin the virtual module ID). This ensures the hydration patches and the user's component import end up in the same Vite chunk, sharing one LitElement prototype. Skips server-side transforms viaoptions.ssr.At build time, we check whether
@lit-labs/ssr-clientalready handles deferred hydration natively. If it does, all of the above is skipped and we fall back to the originalbefore-hydrationimport.Testing