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. |
|
Hi @jlukic would you have a moment? |
|
Sorry again but haven't had time yet to review, will do next week. Wanted to point to this as it might be related depending on what version of Astro you are using. withastro/astro#15904 (comment) Can you confirm your exact versions |
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