@context-action/lit-ui 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +8 -1
- package/dist/hydrate.cjs +82 -3
- package/dist/hydrate.cjs.map +1 -1
- package/dist/hydrate.d.cts.map +1 -1
- package/dist/hydrate.d.ts.map +1 -1
- package/dist/hydrate.js +82 -3
- package/dist/hydrate.js.map +1 -1
- package/docs/06-ssr-hydration.md +28 -1
- package/docs/07-release-and-verification.md +27 -9
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## [0.1.1] (2026-10-23)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- `hydrateLit(host.shadowRoot, host)` now finds `lit-part` markers anywhere
|
|
8
|
+
under the shadow root instead of assuming they are direct siblings of the
|
|
9
|
+
first node. Declarative shadow roots with `static styles` are adopted instead
|
|
10
|
+
of appending a second client render.
|
|
11
|
+
- If a client render already appended a duplicate tree before `hydrateLit` runs,
|
|
12
|
+
the server root part is isolated, the client tree is removed, and the server
|
|
13
|
+
tree is re-adopted. Final item counts and server node identity are preserved.
|
|
14
|
+
|
|
15
|
+
### Tests
|
|
16
|
+
|
|
17
|
+
- Added static-styles + three-item DSD hydration regression coverage in jsdom
|
|
18
|
+
and Chromium, including the late `hydrateLit` after first-update scenario.
|
|
19
|
+
|
|
3
20
|
## [0.1.0] (2026-10-23)
|
|
4
21
|
|
|
22
|
+
Published to npm on 2026-10-23. `latest` and `next` resolve to `0.1.0`.
|
|
23
|
+
|
|
5
24
|
### Added
|
|
6
25
|
|
|
7
26
|
- Initial public release of the Lit UI primitives and integrations.
|
package/README.md
CHANGED
|
@@ -80,7 +80,14 @@ pnpm --filter @context-action/lit-ui test:browser # Chromium DSD hydration contr
|
|
|
80
80
|
|
|
81
81
|
## Status
|
|
82
82
|
|
|
83
|
-
Public package
|
|
83
|
+
Public package. `0.1.1` is the current patch release candidate for the
|
|
84
|
+
static-styles hydration fix; `0.1.0` remains the last published version until
|
|
85
|
+
the candidate/promotion workflows complete.
|
|
86
|
+
|
|
87
|
+
- https://www.npmjs.com/package/@context-action/lit-ui
|
|
88
|
+
- Release plan: [`releases/LIT_PUBLIC_0.1.1.md`](../../releases/LIT_PUBLIC_0.1.1.md)
|
|
89
|
+
|
|
90
|
+
Requires Node.js `>=24.11.0`.
|
|
84
91
|
|
|
85
92
|
## Architecture
|
|
86
93
|
|
package/dist/hydrate.cjs
CHANGED
|
@@ -17,7 +17,16 @@ let _lit_labs_ssr_client = require("@lit-labs/ssr-client");
|
|
|
17
17
|
const COMMENT_NODE = 8;
|
|
18
18
|
const DOCUMENT_FRAGMENT_NODE = 11;
|
|
19
19
|
function hasLitPartMarker(root) {
|
|
20
|
-
|
|
20
|
+
const document = root.ownerDocument ?? root.host?.ownerDocument ?? null;
|
|
21
|
+
if (document && typeof document.createTreeWalker === "function") {
|
|
22
|
+
const walker = document.createTreeWalker(root, 128);
|
|
23
|
+
while (walker.nextNode()) if (walker.currentNode.data.startsWith("lit-part")) return true;
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
for (let node = root.firstChild; node; node = node.nextSibling) {
|
|
27
|
+
if (node.nodeType === COMMENT_NODE && node.data.startsWith("lit-part")) return true;
|
|
28
|
+
if (node.nodeType === 1 && hasLitPartMarker(node)) return true;
|
|
29
|
+
}
|
|
21
30
|
return false;
|
|
22
31
|
}
|
|
23
32
|
function findServerHost(root, element) {
|
|
@@ -37,6 +46,67 @@ function isHydrationSupportInstalled() {
|
|
|
37
46
|
return false;
|
|
38
47
|
}
|
|
39
48
|
}
|
|
49
|
+
function partStartNode(part) {
|
|
50
|
+
return part?._$startNode ?? part?._$AA ?? null;
|
|
51
|
+
}
|
|
52
|
+
function isServerRootPart(part) {
|
|
53
|
+
const start = partStartNode(part);
|
|
54
|
+
return start?.nodeType === COMMENT_NODE && start.data.startsWith("lit-part");
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* When a client render appended a separate tree beside the server DSD tree,
|
|
58
|
+
* keep only the server root part (plus preceding static style tags), remove the
|
|
59
|
+
* client anchors/children, and re-adopt the surviving server tree.
|
|
60
|
+
*/
|
|
61
|
+
function isolateServerTree(shadow) {
|
|
62
|
+
const children = Array.from(shadow.childNodes);
|
|
63
|
+
const rootIndex = children.findIndex((node) => node.nodeType === COMMENT_NODE && node.data.startsWith("lit-part"));
|
|
64
|
+
if (rootIndex < 0) return false;
|
|
65
|
+
let beginIndex = rootIndex;
|
|
66
|
+
while (beginIndex > 0) {
|
|
67
|
+
const previous = children[beginIndex - 1];
|
|
68
|
+
if (previous.nodeType === 1 && previous.localName === "style") {
|
|
69
|
+
beginIndex -= 1;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
let depth = 0;
|
|
75
|
+
let endIndex = -1;
|
|
76
|
+
for (let index = rootIndex; index < children.length; index += 1) {
|
|
77
|
+
const node = children[index];
|
|
78
|
+
if (node.nodeType !== COMMENT_NODE) continue;
|
|
79
|
+
const data = node.data;
|
|
80
|
+
if (data.startsWith("lit-part")) depth += 1;
|
|
81
|
+
else if (data.startsWith("/lit-part")) {
|
|
82
|
+
depth -= 1;
|
|
83
|
+
if (depth === 0) {
|
|
84
|
+
endIndex = index;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (endIndex < 0) return false;
|
|
90
|
+
for (let index = children.length - 1; index > endIndex; index -= 1) children[index].parentNode?.removeChild(children[index]);
|
|
91
|
+
for (let index = 0; index < beginIndex; index += 1) children[index].parentNode?.removeChild(children[index]);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
function adoptServerShadow(host, shadow) {
|
|
95
|
+
host._$needsHydration = false;
|
|
96
|
+
host._$AG = false;
|
|
97
|
+
if (isolateServerTree(shadow)) {
|
|
98
|
+
delete shadow._$litPart$;
|
|
99
|
+
try {
|
|
100
|
+
const rendered = host.render();
|
|
101
|
+
(0, _lit_labs_ssr_client.hydrate)(rendered, shadow);
|
|
102
|
+
host.requestUpdate();
|
|
103
|
+
return;
|
|
104
|
+
} catch {}
|
|
105
|
+
}
|
|
106
|
+
shadow.replaceChildren();
|
|
107
|
+
delete shadow._$litPart$;
|
|
108
|
+
host.requestUpdate();
|
|
109
|
+
}
|
|
40
110
|
function hydrateHost(host) {
|
|
41
111
|
const shadow = host.shadowRoot;
|
|
42
112
|
if (!shadow) {
|
|
@@ -48,7 +118,12 @@ function hydrateHost(host) {
|
|
|
48
118
|
configurable: true,
|
|
49
119
|
value: () => shadow
|
|
50
120
|
});
|
|
51
|
-
|
|
121
|
+
const livePart = shadow._$litPart$;
|
|
122
|
+
if (livePart !== void 0) {
|
|
123
|
+
if (hasLitPartMarker(shadow) && !isServerRootPart(livePart)) {
|
|
124
|
+
adoptServerShadow(host, shadow);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
52
127
|
host.requestUpdate();
|
|
53
128
|
return;
|
|
54
129
|
}
|
|
@@ -107,7 +182,8 @@ function hydrateLit(root, element) {
|
|
|
107
182
|
}
|
|
108
183
|
if (root.nodeType === DOCUMENT_FRAGMENT_NODE && "host" in root && root.host === element) {
|
|
109
184
|
const shadow = root;
|
|
110
|
-
|
|
185
|
+
const livePart = shadow._$litPart$;
|
|
186
|
+
if (livePart === void 0) {
|
|
111
187
|
Object.defineProperty(element, "createRenderRoot", {
|
|
112
188
|
configurable: true,
|
|
113
189
|
value: () => shadow
|
|
@@ -116,6 +192,9 @@ function hydrateLit(root, element) {
|
|
|
116
192
|
const rendered = element.render();
|
|
117
193
|
(0, _lit_labs_ssr_client.hydrate)(rendered, shadow);
|
|
118
194
|
} else shadow.replaceChildren();
|
|
195
|
+
} else if (hasLitPartMarker(shadow) && !isServerRootPart(livePart)) {
|
|
196
|
+
adoptServerShadow(element, shadow);
|
|
197
|
+
return;
|
|
119
198
|
}
|
|
120
199
|
element._$needsHydration = false;
|
|
121
200
|
element._$AG = false;
|
package/dist/hydrate.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydrate.cjs","names":["LitElement"],"sources":["../src/hydrate.ts"],"sourcesContent":["/**\n * @fileoverview Client-only hydration entry (`@context-action/lit-ui/hydrate`).\n *\n * This module intentionally imports only `@lit-labs/ssr-client` and the Lit\n * runtime, never the server renderer. Client bundles that only need\n * `hydrateLit()` can avoid pulling `@lit-labs/ssr` into the browser graph.\n *\n * @implements view-layer\n * @implements web-component-primitive\n * @memberof core-concepts\n */\n\nimport '@lit-labs/ssr-client/lit-element-hydrate-support.js';\nimport { LitElement } from 'lit';\nimport { hydrate } from '@lit-labs/ssr-client';\n\nconst COMMENT_NODE = 8;\nconst DOCUMENT_FRAGMENT_NODE = 11;\n\nfunction hasLitPartMarker(root: Element | ShadowRoot): boolean {\n for (let node = root.firstChild; node; node = node.nextSibling) {\n if (\n node.nodeType === COMMENT_NODE &&\n (node as Comment).data.startsWith('lit-part')\n ) {\n return true;\n }\n }\n return false;\n}\n\nfunction findServerHost(\n root: Element | ShadowRoot,\n element: LitElement,\n): Element | null {\n const tagName = element.localName;\n if (!tagName) return null;\n\n // A ShadowRoot owned by the element is a render target, not a host container.\n if (\n root.nodeType === DOCUMENT_FRAGMENT_NODE &&\n 'host' in root &&\n root.host === element\n ) {\n return null;\n }\n\n if (root.nodeType === 1 && (root as Element).localName === tagName) {\n return root as Element;\n }\n\n return root.querySelector?.(tagName) ?? null;\n}\n\nfunction isHydrationSupportInstalled(): boolean {\n const shadow = {};\n const probe = { shadowRoot: shadow } as unknown as LitElement;\n const createRenderRoot = (\n LitElement.prototype as unknown as {\n createRenderRoot(this: LitElement): unknown;\n }\n ).createRenderRoot;\n try {\n return createRenderRoot.call(probe) === shadow;\n } catch {\n return false;\n }\n}\n\nfunction hydrateHost(host: LitElement): void {\n const shadow = host.shadowRoot;\n if (!shadow) {\n host.requestUpdate();\n return;\n }\n\n const hydrationSupportInstalled = isHydrationSupportInstalled();\n if (!hydrationSupportInstalled) {\n Object.defineProperty(host, 'createRenderRoot', {\n configurable: true,\n value: () => shadow,\n });\n }\n\n const livePart = (shadow as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n if (livePart !== undefined) {\n host.requestUpdate();\n return;\n }\n\n if (!hasLitPartMarker(shadow)) {\n // Server markup without lit-html markers cannot be hydrated. Clear it and\n // force the first client render into this shadow root without asking Lit's\n // hydration support to adopt a non-lit tree.\n shadow.replaceChildren();\n host.requestUpdate();\n return;\n }\n\n // Lit's official hydration support patches createRenderRoot/update and\n // adopts the declarative shadow root on the first update. Running hydrate()\n // ourselves would make support hydrate the same container twice.\n if (hydrationSupportInstalled) {\n host.requestUpdate();\n return;\n }\n\n const rendered = (host as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n host.requestUpdate();\n}\n\nfunction upgradeServerHost(host: Element, element: LitElement): void {\n const tagName = element.localName;\n const registry = globalThis.customElements;\n if (!registry) {\n throw new Error('hydrateLit() requires a DOM environment with customElements.');\n }\n\n if (!registry.get(tagName)) {\n registry.define(tagName, element.constructor as CustomElementConstructor);\n }\n\n // A declarative shadow root already contains the server template. When Lit's\n // hydration support is not installed, prevent the first update from calling\n // attachShadow() on the same host. When support is installed, its patched\n // createRenderRoot already does this and must remain in control.\n const existingShadow = host.shadowRoot;\n if (existingShadow && !isHydrationSupportInstalled()) {\n Object.defineProperty(host, 'createRenderRoot', {\n configurable: true,\n value: () => existingShadow,\n });\n }\n\n const elementConstructor = element.constructor as unknown as { new (): LitElement };\n if (host instanceof elementConstructor) {\n hydrateHost(host);\n return;\n }\n\n registry.upgrade(host);\n if (host instanceof elementConstructor) {\n hydrateHost(host);\n }\n}\n\n/**\n * Hydrates server-produced markup on the client.\n *\n * Supported modes:\n * 1. `root` is an element's own shadow root (or contains the Lit template\n * markers produced by `renderLitTemplate`): the element's current template is\n * hydrated into that root, preserving existing DOM and attaching event parts.\n * 2. `root` contains a server-rendered custom element host for the same tag:\n * the host is upgraded so its controllers and attributes connect.\n * 3. `root` is empty: the element is mounted and rendered normally.\n *\n * The function never reads a global `document`; it operates only on the supplied\n * root and element.\n */\nexport function hydrateLit(root: Element | ShadowRoot, element: LitElement): void {\n if (!root || typeof root !== 'object') {\n throw new TypeError('hydrateLit() requires an Element or ShadowRoot.');\n }\n if (!element || typeof element !== 'object') {\n throw new TypeError('hydrateLit() requires a LitElement instance.');\n }\n\n const serverHost = findServerHost(root, element);\n if (serverHost && serverHost !== element) {\n upgradeServerHost(serverHost, element);\n return;\n }\n\n // The element's own shadow root is managed by its Lit update lifecycle.\n // Hydrate it explicitly and bypass the support flag so the first update does\n // not adopt the same container a second time.\n if (\n root.nodeType === DOCUMENT_FRAGMENT_NODE &&\n 'host' in root &&\n root.host === element\n ) {\n const shadow = root as ShadowRoot;\n const livePart = (shadow as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n if (livePart === undefined) {\n Object.defineProperty(element, 'createRenderRoot', {\n configurable: true,\n value: () => shadow,\n });\n if (hasLitPartMarker(shadow)) {\n const rendered = (element as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n } else {\n shadow.replaceChildren();\n }\n }\n // Support uses `_$needsHydration` in development and a minified field in\n // production. Clearing both is harmless when support is absent.\n (element as unknown as { _$needsHydration?: boolean })._$needsHydration = false;\n (element as unknown as { _$AG?: boolean })._$AG = false;\n element.requestUpdate();\n return;\n }\n\n if (hasLitPartMarker(root)) {\n const livePart = (root as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n if (livePart !== undefined) {\n element.requestUpdate();\n return;\n }\n const rendered = (element as unknown as { render(): unknown }).render();\n hydrate(rendered, root);\n return;\n }\n\n if (root !== element && !element.isConnected) {\n root.append(element);\n }\n element.requestUpdate();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hydrate.cjs","names":["LitElement"],"sources":["../src/hydrate.ts"],"sourcesContent":["/**\n * @fileoverview Client-only hydration entry (`@context-action/lit-ui/hydrate`).\n *\n * This module intentionally imports only `@lit-labs/ssr-client` and the Lit\n * runtime, never the server renderer. Client bundles that only need\n * `hydrateLit()` can avoid pulling `@lit-labs/ssr` into the browser graph.\n *\n * @implements view-layer\n * @implements web-component-primitive\n * @memberof core-concepts\n */\n\nimport '@lit-labs/ssr-client/lit-element-hydrate-support.js';\nimport { LitElement } from 'lit';\nimport { hydrate } from '@lit-labs/ssr-client';\n\nconst COMMENT_NODE = 8;\nconst DOCUMENT_FRAGMENT_NODE = 11;\n\nfunction hasLitPartMarker(root: Element | ShadowRoot): boolean {\n const document = root.ownerDocument\n ?? (root as ShadowRoot).host?.ownerDocument\n ?? null;\n if (document && typeof document.createTreeWalker === 'function') {\n const walker = document.createTreeWalker(root, 128 /* NodeFilter.SHOW_COMMENT */);\n while (walker.nextNode()) {\n if ((walker.currentNode as Comment).data.startsWith('lit-part')) {\n return true;\n }\n }\n return false;\n }\n\n // Defensive fallback for environments without TreeWalker.\n for (let node = root.firstChild; node; node = node.nextSibling) {\n if (\n node.nodeType === COMMENT_NODE &&\n (node as Comment).data.startsWith('lit-part')\n ) {\n return true;\n }\n if (node.nodeType === 1 && hasLitPartMarker(node as Element)) {\n return true;\n }\n }\n return false;\n}\n\nfunction findServerHost(\n root: Element | ShadowRoot,\n element: LitElement,\n): Element | null {\n const tagName = element.localName;\n if (!tagName) return null;\n\n // A ShadowRoot owned by the element is a render target, not a host container.\n if (\n root.nodeType === DOCUMENT_FRAGMENT_NODE &&\n 'host' in root &&\n root.host === element\n ) {\n return null;\n }\n\n if (root.nodeType === 1 && (root as Element).localName === tagName) {\n return root as Element;\n }\n\n return root.querySelector?.(tagName) ?? null;\n}\n\nfunction isHydrationSupportInstalled(): boolean {\n const shadow = {};\n const probe = { shadowRoot: shadow } as unknown as LitElement;\n const createRenderRoot = (\n LitElement.prototype as unknown as {\n createRenderRoot(this: LitElement): unknown;\n }\n ).createRenderRoot;\n try {\n return createRenderRoot.call(probe) === shadow;\n } catch {\n return false;\n }\n}\n\ninterface LitPartLike {\n _$startNode?: Node | null;\n _$endNode?: Node | null;\n _$AA?: Node | null;\n _$AB?: Node | null;\n}\n\nfunction partStartNode(part: LitPartLike | undefined): Node | null {\n return part?._$startNode ?? part?._$AA ?? null;\n}\n\nfunction partEndNode(part: LitPartLike | undefined): Node | null {\n return part?._$endNode ?? part?._$AB ?? null;\n}\n\nfunction isServerRootPart(part: LitPartLike | undefined): boolean {\n const start = partStartNode(part);\n return (\n start?.nodeType === COMMENT_NODE &&\n (start as Comment).data.startsWith('lit-part')\n );\n}\n\n/**\n * When a client render appended a separate tree beside the server DSD tree,\n * keep only the server root part (plus preceding static style tags), remove the\n * client anchors/children, and re-adopt the surviving server tree.\n */\nfunction isolateServerTree(shadow: ShadowRoot): boolean {\n const children = Array.from(shadow.childNodes);\n const rootIndex = children.findIndex(\n node => node.nodeType === COMMENT_NODE && (node as Comment).data.startsWith('lit-part'),\n );\n if (rootIndex < 0) return false;\n\n let beginIndex = rootIndex;\n while (beginIndex > 0) {\n const previous = children[beginIndex - 1]!;\n if (previous.nodeType === 1 && (previous as Element).localName === 'style') {\n beginIndex -= 1;\n continue;\n }\n break;\n }\n\n let depth = 0;\n let endIndex = -1;\n for (let index = rootIndex; index < children.length; index += 1) {\n const node = children[index]!;\n if (node.nodeType !== COMMENT_NODE) continue;\n const data = (node as Comment).data;\n if (data.startsWith('lit-part')) {\n depth += 1;\n } else if (data.startsWith('/lit-part')) {\n depth -= 1;\n if (depth === 0) {\n endIndex = index;\n break;\n }\n }\n }\n if (endIndex < 0) return false;\n\n for (let index = children.length - 1; index > endIndex; index -= 1) {\n children[index]!.parentNode?.removeChild(children[index]!);\n }\n for (let index = 0; index < beginIndex; index += 1) {\n children[index]!.parentNode?.removeChild(children[index]!);\n }\n return true;\n}\n\nfunction adoptServerShadow(host: LitElement, shadow: ShadowRoot): void {\n // Clear Lit hydration support flags so the following requestUpdate does not\n // adopt the same tree a second time.\n (host as unknown as { _$needsHydration?: boolean })._$needsHydration = false;\n (host as unknown as { _$AG?: boolean })._$AG = false;\n\n if (isolateServerTree(shadow)) {\n delete (shadow as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n try {\n const rendered = (host as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n host.requestUpdate();\n return;\n } catch {\n // Fall through to a clean client render when the server tree cannot be\n // adopted (for example after an incompatible template change).\n }\n }\n\n shadow.replaceChildren();\n delete (shadow as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n host.requestUpdate();\n}\n\nfunction hydrateHost(host: LitElement): void {\n const shadow = host.shadowRoot;\n if (!shadow) {\n host.requestUpdate();\n return;\n }\n\n const hydrationSupportInstalled = isHydrationSupportInstalled();\n if (!hydrationSupportInstalled) {\n Object.defineProperty(host, 'createRenderRoot', {\n configurable: true,\n value: () => shadow,\n });\n }\n\n const livePart = (shadow as ShadowRoot & { _$litPart$?: LitPartLike })._$litPart$;\n if (livePart !== undefined) {\n if (hasLitPartMarker(shadow) && !isServerRootPart(livePart)) {\n adoptServerShadow(host, shadow);\n return;\n }\n host.requestUpdate();\n return;\n }\n\n if (!hasLitPartMarker(shadow)) {\n // Server markup without lit-html markers cannot be hydrated. Clear it and\n // force the first client render into this shadow root without asking Lit's\n // hydration support to adopt a non-lit tree.\n shadow.replaceChildren();\n host.requestUpdate();\n return;\n }\n\n // Lit's official hydration support patches createRenderRoot/update and\n // adopts the declarative shadow root on the first update. Running hydrate()\n // ourselves would make support hydrate the same container twice.\n if (hydrationSupportInstalled) {\n host.requestUpdate();\n return;\n }\n\n const rendered = (host as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n host.requestUpdate();\n}\n\nfunction upgradeServerHost(host: Element, element: LitElement): void {\n const tagName = element.localName;\n const registry = globalThis.customElements;\n if (!registry) {\n throw new Error('hydrateLit() requires a DOM environment with customElements.');\n }\n\n if (!registry.get(tagName)) {\n registry.define(tagName, element.constructor as CustomElementConstructor);\n }\n\n // A declarative shadow root already contains the server template. When Lit's\n // hydration support is not installed, prevent the first update from calling\n // attachShadow() on the same host. When support is installed, its patched\n // createRenderRoot already does this and must remain in control.\n const existingShadow = host.shadowRoot;\n if (existingShadow && !isHydrationSupportInstalled()) {\n Object.defineProperty(host, 'createRenderRoot', {\n configurable: true,\n value: () => existingShadow,\n });\n }\n\n const elementConstructor = element.constructor as unknown as { new (): LitElement };\n if (host instanceof elementConstructor) {\n hydrateHost(host);\n return;\n }\n\n registry.upgrade(host);\n if (host instanceof elementConstructor) {\n hydrateHost(host);\n }\n}\n\n/**\n * Hydrates server-produced markup on the client.\n *\n * Supported modes:\n * 1. `root` is an element's own shadow root (or contains the Lit template\n * markers produced by `renderLitTemplate`): the element's current template is\n * hydrated into that root, preserving existing DOM and attaching event parts.\n * 2. `root` contains a server-rendered custom element host for the same tag:\n * the host is upgraded so its controllers and attributes connect.\n * 3. `root` is empty: the element is mounted and rendered normally.\n *\n * The function never reads a global `document`; it operates only on the supplied\n * root and element.\n */\nexport function hydrateLit(root: Element | ShadowRoot, element: LitElement): void {\n if (!root || typeof root !== 'object') {\n throw new TypeError('hydrateLit() requires an Element or ShadowRoot.');\n }\n if (!element || typeof element !== 'object') {\n throw new TypeError('hydrateLit() requires a LitElement instance.');\n }\n\n const serverHost = findServerHost(root, element);\n if (serverHost && serverHost !== element) {\n upgradeServerHost(serverHost, element);\n return;\n }\n\n // The element's own shadow root is managed by its Lit update lifecycle.\n // Hydrate it explicitly and bypass the support flag so the first update does\n // not adopt the same container a second time.\n if (\n root.nodeType === DOCUMENT_FRAGMENT_NODE &&\n 'host' in root &&\n root.host === element\n ) {\n const shadow = root as ShadowRoot;\n const livePart = (shadow as ShadowRoot & { _$litPart$?: LitPartLike })._$litPart$;\n if (livePart === undefined) {\n Object.defineProperty(element, 'createRenderRoot', {\n configurable: true,\n value: () => shadow,\n });\n if (hasLitPartMarker(shadow)) {\n const rendered = (element as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n } else {\n shadow.replaceChildren();\n }\n } else if (hasLitPartMarker(shadow) && !isServerRootPart(livePart)) {\n adoptServerShadow(element, shadow);\n return;\n }\n // Support uses `_$needsHydration` in development and a minified field in\n // production. Clearing both is harmless when support is absent.\n (element as unknown as { _$needsHydration?: boolean })._$needsHydration = false;\n (element as unknown as { _$AG?: boolean })._$AG = false;\n element.requestUpdate();\n return;\n }\n\n if (hasLitPartMarker(root)) {\n const livePart = (root as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n if (livePart !== undefined) {\n element.requestUpdate();\n return;\n }\n const rendered = (element as unknown as { render(): unknown }).render();\n hydrate(rendered, root);\n return;\n }\n\n if (root !== element && !element.isConnected) {\n root.append(element);\n }\n element.requestUpdate();\n}\n"],"mappings":"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"}
|
package/dist/hydrate.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydrate.d.cts","names":[],"sources":["../src/hydrate.ts"],"mappings":";;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"hydrate.d.cts","names":[],"sources":["../src/hydrate.ts"],"mappings":";;;;;;;;;;;;;;;;wBAsRgB,WAAW,MAAM,UAAU,YAAY,SAAS"}
|
package/dist/hydrate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydrate.d.ts","names":[],"sources":["../src/hydrate.ts"],"mappings":";;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"hydrate.d.ts","names":[],"sources":["../src/hydrate.ts"],"mappings":";;;;;;;;;;;;;;;;wBAsRgB,WAAW,MAAM,UAAU,YAAY,SAAS"}
|
package/dist/hydrate.js
CHANGED
|
@@ -5,7 +5,16 @@ import { hydrate } from "@lit-labs/ssr-client";
|
|
|
5
5
|
const COMMENT_NODE = 8;
|
|
6
6
|
const DOCUMENT_FRAGMENT_NODE = 11;
|
|
7
7
|
function hasLitPartMarker(root) {
|
|
8
|
-
|
|
8
|
+
const document = root.ownerDocument ?? root.host?.ownerDocument ?? null;
|
|
9
|
+
if (document && typeof document.createTreeWalker === "function") {
|
|
10
|
+
const walker = document.createTreeWalker(root, 128);
|
|
11
|
+
while (walker.nextNode()) if (walker.currentNode.data.startsWith("lit-part")) return true;
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
for (let node = root.firstChild; node; node = node.nextSibling) {
|
|
15
|
+
if (node.nodeType === COMMENT_NODE && node.data.startsWith("lit-part")) return true;
|
|
16
|
+
if (node.nodeType === 1 && hasLitPartMarker(node)) return true;
|
|
17
|
+
}
|
|
9
18
|
return false;
|
|
10
19
|
}
|
|
11
20
|
function findServerHost(root, element) {
|
|
@@ -25,6 +34,67 @@ function isHydrationSupportInstalled() {
|
|
|
25
34
|
return false;
|
|
26
35
|
}
|
|
27
36
|
}
|
|
37
|
+
function partStartNode(part) {
|
|
38
|
+
return part?._$startNode ?? part?._$AA ?? null;
|
|
39
|
+
}
|
|
40
|
+
function isServerRootPart(part) {
|
|
41
|
+
const start = partStartNode(part);
|
|
42
|
+
return start?.nodeType === COMMENT_NODE && start.data.startsWith("lit-part");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* When a client render appended a separate tree beside the server DSD tree,
|
|
46
|
+
* keep only the server root part (plus preceding static style tags), remove the
|
|
47
|
+
* client anchors/children, and re-adopt the surviving server tree.
|
|
48
|
+
*/
|
|
49
|
+
function isolateServerTree(shadow) {
|
|
50
|
+
const children = Array.from(shadow.childNodes);
|
|
51
|
+
const rootIndex = children.findIndex((node) => node.nodeType === COMMENT_NODE && node.data.startsWith("lit-part"));
|
|
52
|
+
if (rootIndex < 0) return false;
|
|
53
|
+
let beginIndex = rootIndex;
|
|
54
|
+
while (beginIndex > 0) {
|
|
55
|
+
const previous = children[beginIndex - 1];
|
|
56
|
+
if (previous.nodeType === 1 && previous.localName === "style") {
|
|
57
|
+
beginIndex -= 1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
let depth = 0;
|
|
63
|
+
let endIndex = -1;
|
|
64
|
+
for (let index = rootIndex; index < children.length; index += 1) {
|
|
65
|
+
const node = children[index];
|
|
66
|
+
if (node.nodeType !== COMMENT_NODE) continue;
|
|
67
|
+
const data = node.data;
|
|
68
|
+
if (data.startsWith("lit-part")) depth += 1;
|
|
69
|
+
else if (data.startsWith("/lit-part")) {
|
|
70
|
+
depth -= 1;
|
|
71
|
+
if (depth === 0) {
|
|
72
|
+
endIndex = index;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (endIndex < 0) return false;
|
|
78
|
+
for (let index = children.length - 1; index > endIndex; index -= 1) children[index].parentNode?.removeChild(children[index]);
|
|
79
|
+
for (let index = 0; index < beginIndex; index += 1) children[index].parentNode?.removeChild(children[index]);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
function adoptServerShadow(host, shadow) {
|
|
83
|
+
host._$needsHydration = false;
|
|
84
|
+
host._$AG = false;
|
|
85
|
+
if (isolateServerTree(shadow)) {
|
|
86
|
+
delete shadow._$litPart$;
|
|
87
|
+
try {
|
|
88
|
+
const rendered = host.render();
|
|
89
|
+
hydrate(rendered, shadow);
|
|
90
|
+
host.requestUpdate();
|
|
91
|
+
return;
|
|
92
|
+
} catch {}
|
|
93
|
+
}
|
|
94
|
+
shadow.replaceChildren();
|
|
95
|
+
delete shadow._$litPart$;
|
|
96
|
+
host.requestUpdate();
|
|
97
|
+
}
|
|
28
98
|
function hydrateHost(host) {
|
|
29
99
|
const shadow = host.shadowRoot;
|
|
30
100
|
if (!shadow) {
|
|
@@ -36,7 +106,12 @@ function hydrateHost(host) {
|
|
|
36
106
|
configurable: true,
|
|
37
107
|
value: () => shadow
|
|
38
108
|
});
|
|
39
|
-
|
|
109
|
+
const livePart = shadow._$litPart$;
|
|
110
|
+
if (livePart !== void 0) {
|
|
111
|
+
if (hasLitPartMarker(shadow) && !isServerRootPart(livePart)) {
|
|
112
|
+
adoptServerShadow(host, shadow);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
40
115
|
host.requestUpdate();
|
|
41
116
|
return;
|
|
42
117
|
}
|
|
@@ -95,7 +170,8 @@ function hydrateLit(root, element) {
|
|
|
95
170
|
}
|
|
96
171
|
if (root.nodeType === DOCUMENT_FRAGMENT_NODE && "host" in root && root.host === element) {
|
|
97
172
|
const shadow = root;
|
|
98
|
-
|
|
173
|
+
const livePart = shadow._$litPart$;
|
|
174
|
+
if (livePart === void 0) {
|
|
99
175
|
Object.defineProperty(element, "createRenderRoot", {
|
|
100
176
|
configurable: true,
|
|
101
177
|
value: () => shadow
|
|
@@ -104,6 +180,9 @@ function hydrateLit(root, element) {
|
|
|
104
180
|
const rendered = element.render();
|
|
105
181
|
hydrate(rendered, shadow);
|
|
106
182
|
} else shadow.replaceChildren();
|
|
183
|
+
} else if (hasLitPartMarker(shadow) && !isServerRootPart(livePart)) {
|
|
184
|
+
adoptServerShadow(element, shadow);
|
|
185
|
+
return;
|
|
107
186
|
}
|
|
108
187
|
element._$needsHydration = false;
|
|
109
188
|
element._$AG = false;
|
package/dist/hydrate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hydrate.js","names":[],"sources":["../src/hydrate.ts"],"sourcesContent":["/**\n * @fileoverview Client-only hydration entry (`@context-action/lit-ui/hydrate`).\n *\n * This module intentionally imports only `@lit-labs/ssr-client` and the Lit\n * runtime, never the server renderer. Client bundles that only need\n * `hydrateLit()` can avoid pulling `@lit-labs/ssr` into the browser graph.\n *\n * @implements view-layer\n * @implements web-component-primitive\n * @memberof core-concepts\n */\n\nimport '@lit-labs/ssr-client/lit-element-hydrate-support.js';\nimport { LitElement } from 'lit';\nimport { hydrate } from '@lit-labs/ssr-client';\n\nconst COMMENT_NODE = 8;\nconst DOCUMENT_FRAGMENT_NODE = 11;\n\nfunction hasLitPartMarker(root: Element | ShadowRoot): boolean {\n for (let node = root.firstChild; node; node = node.nextSibling) {\n if (\n node.nodeType === COMMENT_NODE &&\n (node as Comment).data.startsWith('lit-part')\n ) {\n return true;\n }\n }\n return false;\n}\n\nfunction findServerHost(\n root: Element | ShadowRoot,\n element: LitElement,\n): Element | null {\n const tagName = element.localName;\n if (!tagName) return null;\n\n // A ShadowRoot owned by the element is a render target, not a host container.\n if (\n root.nodeType === DOCUMENT_FRAGMENT_NODE &&\n 'host' in root &&\n root.host === element\n ) {\n return null;\n }\n\n if (root.nodeType === 1 && (root as Element).localName === tagName) {\n return root as Element;\n }\n\n return root.querySelector?.(tagName) ?? null;\n}\n\nfunction isHydrationSupportInstalled(): boolean {\n const shadow = {};\n const probe = { shadowRoot: shadow } as unknown as LitElement;\n const createRenderRoot = (\n LitElement.prototype as unknown as {\n createRenderRoot(this: LitElement): unknown;\n }\n ).createRenderRoot;\n try {\n return createRenderRoot.call(probe) === shadow;\n } catch {\n return false;\n }\n}\n\nfunction hydrateHost(host: LitElement): void {\n const shadow = host.shadowRoot;\n if (!shadow) {\n host.requestUpdate();\n return;\n }\n\n const hydrationSupportInstalled = isHydrationSupportInstalled();\n if (!hydrationSupportInstalled) {\n Object.defineProperty(host, 'createRenderRoot', {\n configurable: true,\n value: () => shadow,\n });\n }\n\n const livePart = (shadow as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n if (livePart !== undefined) {\n host.requestUpdate();\n return;\n }\n\n if (!hasLitPartMarker(shadow)) {\n // Server markup without lit-html markers cannot be hydrated. Clear it and\n // force the first client render into this shadow root without asking Lit's\n // hydration support to adopt a non-lit tree.\n shadow.replaceChildren();\n host.requestUpdate();\n return;\n }\n\n // Lit's official hydration support patches createRenderRoot/update and\n // adopts the declarative shadow root on the first update. Running hydrate()\n // ourselves would make support hydrate the same container twice.\n if (hydrationSupportInstalled) {\n host.requestUpdate();\n return;\n }\n\n const rendered = (host as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n host.requestUpdate();\n}\n\nfunction upgradeServerHost(host: Element, element: LitElement): void {\n const tagName = element.localName;\n const registry = globalThis.customElements;\n if (!registry) {\n throw new Error('hydrateLit() requires a DOM environment with customElements.');\n }\n\n if (!registry.get(tagName)) {\n registry.define(tagName, element.constructor as CustomElementConstructor);\n }\n\n // A declarative shadow root already contains the server template. When Lit's\n // hydration support is not installed, prevent the first update from calling\n // attachShadow() on the same host. When support is installed, its patched\n // createRenderRoot already does this and must remain in control.\n const existingShadow = host.shadowRoot;\n if (existingShadow && !isHydrationSupportInstalled()) {\n Object.defineProperty(host, 'createRenderRoot', {\n configurable: true,\n value: () => existingShadow,\n });\n }\n\n const elementConstructor = element.constructor as unknown as { new (): LitElement };\n if (host instanceof elementConstructor) {\n hydrateHost(host);\n return;\n }\n\n registry.upgrade(host);\n if (host instanceof elementConstructor) {\n hydrateHost(host);\n }\n}\n\n/**\n * Hydrates server-produced markup on the client.\n *\n * Supported modes:\n * 1. `root` is an element's own shadow root (or contains the Lit template\n * markers produced by `renderLitTemplate`): the element's current template is\n * hydrated into that root, preserving existing DOM and attaching event parts.\n * 2. `root` contains a server-rendered custom element host for the same tag:\n * the host is upgraded so its controllers and attributes connect.\n * 3. `root` is empty: the element is mounted and rendered normally.\n *\n * The function never reads a global `document`; it operates only on the supplied\n * root and element.\n */\nexport function hydrateLit(root: Element | ShadowRoot, element: LitElement): void {\n if (!root || typeof root !== 'object') {\n throw new TypeError('hydrateLit() requires an Element or ShadowRoot.');\n }\n if (!element || typeof element !== 'object') {\n throw new TypeError('hydrateLit() requires a LitElement instance.');\n }\n\n const serverHost = findServerHost(root, element);\n if (serverHost && serverHost !== element) {\n upgradeServerHost(serverHost, element);\n return;\n }\n\n // The element's own shadow root is managed by its Lit update lifecycle.\n // Hydrate it explicitly and bypass the support flag so the first update does\n // not adopt the same container a second time.\n if (\n root.nodeType === DOCUMENT_FRAGMENT_NODE &&\n 'host' in root &&\n root.host === element\n ) {\n const shadow = root as ShadowRoot;\n const livePart = (shadow as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n if (livePart === undefined) {\n Object.defineProperty(element, 'createRenderRoot', {\n configurable: true,\n value: () => shadow,\n });\n if (hasLitPartMarker(shadow)) {\n const rendered = (element as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n } else {\n shadow.replaceChildren();\n }\n }\n // Support uses `_$needsHydration` in development and a minified field in\n // production. Clearing both is harmless when support is absent.\n (element as unknown as { _$needsHydration?: boolean })._$needsHydration = false;\n (element as unknown as { _$AG?: boolean })._$AG = false;\n element.requestUpdate();\n return;\n }\n\n if (hasLitPartMarker(root)) {\n const livePart = (root as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n if (livePart !== undefined) {\n element.requestUpdate();\n return;\n }\n const rendered = (element as unknown as { render(): unknown }).render();\n hydrate(rendered, root);\n return;\n }\n\n if (root !== element && !element.isConnected) {\n root.append(element);\n }\n element.requestUpdate();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"hydrate.js","names":[],"sources":["../src/hydrate.ts"],"sourcesContent":["/**\n * @fileoverview Client-only hydration entry (`@context-action/lit-ui/hydrate`).\n *\n * This module intentionally imports only `@lit-labs/ssr-client` and the Lit\n * runtime, never the server renderer. Client bundles that only need\n * `hydrateLit()` can avoid pulling `@lit-labs/ssr` into the browser graph.\n *\n * @implements view-layer\n * @implements web-component-primitive\n * @memberof core-concepts\n */\n\nimport '@lit-labs/ssr-client/lit-element-hydrate-support.js';\nimport { LitElement } from 'lit';\nimport { hydrate } from '@lit-labs/ssr-client';\n\nconst COMMENT_NODE = 8;\nconst DOCUMENT_FRAGMENT_NODE = 11;\n\nfunction hasLitPartMarker(root: Element | ShadowRoot): boolean {\n const document = root.ownerDocument\n ?? (root as ShadowRoot).host?.ownerDocument\n ?? null;\n if (document && typeof document.createTreeWalker === 'function') {\n const walker = document.createTreeWalker(root, 128 /* NodeFilter.SHOW_COMMENT */);\n while (walker.nextNode()) {\n if ((walker.currentNode as Comment).data.startsWith('lit-part')) {\n return true;\n }\n }\n return false;\n }\n\n // Defensive fallback for environments without TreeWalker.\n for (let node = root.firstChild; node; node = node.nextSibling) {\n if (\n node.nodeType === COMMENT_NODE &&\n (node as Comment).data.startsWith('lit-part')\n ) {\n return true;\n }\n if (node.nodeType === 1 && hasLitPartMarker(node as Element)) {\n return true;\n }\n }\n return false;\n}\n\nfunction findServerHost(\n root: Element | ShadowRoot,\n element: LitElement,\n): Element | null {\n const tagName = element.localName;\n if (!tagName) return null;\n\n // A ShadowRoot owned by the element is a render target, not a host container.\n if (\n root.nodeType === DOCUMENT_FRAGMENT_NODE &&\n 'host' in root &&\n root.host === element\n ) {\n return null;\n }\n\n if (root.nodeType === 1 && (root as Element).localName === tagName) {\n return root as Element;\n }\n\n return root.querySelector?.(tagName) ?? null;\n}\n\nfunction isHydrationSupportInstalled(): boolean {\n const shadow = {};\n const probe = { shadowRoot: shadow } as unknown as LitElement;\n const createRenderRoot = (\n LitElement.prototype as unknown as {\n createRenderRoot(this: LitElement): unknown;\n }\n ).createRenderRoot;\n try {\n return createRenderRoot.call(probe) === shadow;\n } catch {\n return false;\n }\n}\n\ninterface LitPartLike {\n _$startNode?: Node | null;\n _$endNode?: Node | null;\n _$AA?: Node | null;\n _$AB?: Node | null;\n}\n\nfunction partStartNode(part: LitPartLike | undefined): Node | null {\n return part?._$startNode ?? part?._$AA ?? null;\n}\n\nfunction partEndNode(part: LitPartLike | undefined): Node | null {\n return part?._$endNode ?? part?._$AB ?? null;\n}\n\nfunction isServerRootPart(part: LitPartLike | undefined): boolean {\n const start = partStartNode(part);\n return (\n start?.nodeType === COMMENT_NODE &&\n (start as Comment).data.startsWith('lit-part')\n );\n}\n\n/**\n * When a client render appended a separate tree beside the server DSD tree,\n * keep only the server root part (plus preceding static style tags), remove the\n * client anchors/children, and re-adopt the surviving server tree.\n */\nfunction isolateServerTree(shadow: ShadowRoot): boolean {\n const children = Array.from(shadow.childNodes);\n const rootIndex = children.findIndex(\n node => node.nodeType === COMMENT_NODE && (node as Comment).data.startsWith('lit-part'),\n );\n if (rootIndex < 0) return false;\n\n let beginIndex = rootIndex;\n while (beginIndex > 0) {\n const previous = children[beginIndex - 1]!;\n if (previous.nodeType === 1 && (previous as Element).localName === 'style') {\n beginIndex -= 1;\n continue;\n }\n break;\n }\n\n let depth = 0;\n let endIndex = -1;\n for (let index = rootIndex; index < children.length; index += 1) {\n const node = children[index]!;\n if (node.nodeType !== COMMENT_NODE) continue;\n const data = (node as Comment).data;\n if (data.startsWith('lit-part')) {\n depth += 1;\n } else if (data.startsWith('/lit-part')) {\n depth -= 1;\n if (depth === 0) {\n endIndex = index;\n break;\n }\n }\n }\n if (endIndex < 0) return false;\n\n for (let index = children.length - 1; index > endIndex; index -= 1) {\n children[index]!.parentNode?.removeChild(children[index]!);\n }\n for (let index = 0; index < beginIndex; index += 1) {\n children[index]!.parentNode?.removeChild(children[index]!);\n }\n return true;\n}\n\nfunction adoptServerShadow(host: LitElement, shadow: ShadowRoot): void {\n // Clear Lit hydration support flags so the following requestUpdate does not\n // adopt the same tree a second time.\n (host as unknown as { _$needsHydration?: boolean })._$needsHydration = false;\n (host as unknown as { _$AG?: boolean })._$AG = false;\n\n if (isolateServerTree(shadow)) {\n delete (shadow as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n try {\n const rendered = (host as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n host.requestUpdate();\n return;\n } catch {\n // Fall through to a clean client render when the server tree cannot be\n // adopted (for example after an incompatible template change).\n }\n }\n\n shadow.replaceChildren();\n delete (shadow as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n host.requestUpdate();\n}\n\nfunction hydrateHost(host: LitElement): void {\n const shadow = host.shadowRoot;\n if (!shadow) {\n host.requestUpdate();\n return;\n }\n\n const hydrationSupportInstalled = isHydrationSupportInstalled();\n if (!hydrationSupportInstalled) {\n Object.defineProperty(host, 'createRenderRoot', {\n configurable: true,\n value: () => shadow,\n });\n }\n\n const livePart = (shadow as ShadowRoot & { _$litPart$?: LitPartLike })._$litPart$;\n if (livePart !== undefined) {\n if (hasLitPartMarker(shadow) && !isServerRootPart(livePart)) {\n adoptServerShadow(host, shadow);\n return;\n }\n host.requestUpdate();\n return;\n }\n\n if (!hasLitPartMarker(shadow)) {\n // Server markup without lit-html markers cannot be hydrated. Clear it and\n // force the first client render into this shadow root without asking Lit's\n // hydration support to adopt a non-lit tree.\n shadow.replaceChildren();\n host.requestUpdate();\n return;\n }\n\n // Lit's official hydration support patches createRenderRoot/update and\n // adopts the declarative shadow root on the first update. Running hydrate()\n // ourselves would make support hydrate the same container twice.\n if (hydrationSupportInstalled) {\n host.requestUpdate();\n return;\n }\n\n const rendered = (host as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n host.requestUpdate();\n}\n\nfunction upgradeServerHost(host: Element, element: LitElement): void {\n const tagName = element.localName;\n const registry = globalThis.customElements;\n if (!registry) {\n throw new Error('hydrateLit() requires a DOM environment with customElements.');\n }\n\n if (!registry.get(tagName)) {\n registry.define(tagName, element.constructor as CustomElementConstructor);\n }\n\n // A declarative shadow root already contains the server template. When Lit's\n // hydration support is not installed, prevent the first update from calling\n // attachShadow() on the same host. When support is installed, its patched\n // createRenderRoot already does this and must remain in control.\n const existingShadow = host.shadowRoot;\n if (existingShadow && !isHydrationSupportInstalled()) {\n Object.defineProperty(host, 'createRenderRoot', {\n configurable: true,\n value: () => existingShadow,\n });\n }\n\n const elementConstructor = element.constructor as unknown as { new (): LitElement };\n if (host instanceof elementConstructor) {\n hydrateHost(host);\n return;\n }\n\n registry.upgrade(host);\n if (host instanceof elementConstructor) {\n hydrateHost(host);\n }\n}\n\n/**\n * Hydrates server-produced markup on the client.\n *\n * Supported modes:\n * 1. `root` is an element's own shadow root (or contains the Lit template\n * markers produced by `renderLitTemplate`): the element's current template is\n * hydrated into that root, preserving existing DOM and attaching event parts.\n * 2. `root` contains a server-rendered custom element host for the same tag:\n * the host is upgraded so its controllers and attributes connect.\n * 3. `root` is empty: the element is mounted and rendered normally.\n *\n * The function never reads a global `document`; it operates only on the supplied\n * root and element.\n */\nexport function hydrateLit(root: Element | ShadowRoot, element: LitElement): void {\n if (!root || typeof root !== 'object') {\n throw new TypeError('hydrateLit() requires an Element or ShadowRoot.');\n }\n if (!element || typeof element !== 'object') {\n throw new TypeError('hydrateLit() requires a LitElement instance.');\n }\n\n const serverHost = findServerHost(root, element);\n if (serverHost && serverHost !== element) {\n upgradeServerHost(serverHost, element);\n return;\n }\n\n // The element's own shadow root is managed by its Lit update lifecycle.\n // Hydrate it explicitly and bypass the support flag so the first update does\n // not adopt the same container a second time.\n if (\n root.nodeType === DOCUMENT_FRAGMENT_NODE &&\n 'host' in root &&\n root.host === element\n ) {\n const shadow = root as ShadowRoot;\n const livePart = (shadow as ShadowRoot & { _$litPart$?: LitPartLike })._$litPart$;\n if (livePart === undefined) {\n Object.defineProperty(element, 'createRenderRoot', {\n configurable: true,\n value: () => shadow,\n });\n if (hasLitPartMarker(shadow)) {\n const rendered = (element as unknown as { render(): unknown }).render();\n hydrate(rendered, shadow);\n } else {\n shadow.replaceChildren();\n }\n } else if (hasLitPartMarker(shadow) && !isServerRootPart(livePart)) {\n adoptServerShadow(element, shadow);\n return;\n }\n // Support uses `_$needsHydration` in development and a minified field in\n // production. Clearing both is harmless when support is absent.\n (element as unknown as { _$needsHydration?: boolean })._$needsHydration = false;\n (element as unknown as { _$AG?: boolean })._$AG = false;\n element.requestUpdate();\n return;\n }\n\n if (hasLitPartMarker(root)) {\n const livePart = (root as ShadowRoot & { _$litPart$?: unknown })._$litPart$;\n if (livePart !== undefined) {\n element.requestUpdate();\n return;\n }\n const rendered = (element as unknown as { render(): unknown }).render();\n hydrate(rendered, root);\n return;\n }\n\n if (root !== element && !element.isConnected) {\n root.append(element);\n }\n element.requestUpdate();\n}\n"],"mappings":"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"}
|
package/docs/06-ssr-hydration.md
CHANGED
|
@@ -84,10 +84,37 @@ hydrateLit(root: Element | ShadowRoot, element: LitElement): void
|
|
|
84
84
|
- support가 없으면 수동으로 기존 shadow root를 hydrate합니다.
|
|
85
85
|
- 기존 `_$litPart$`가 있으면 재hydrate하지 않습니다.
|
|
86
86
|
3. element 자신의 shadow root를 직접 넘기는 경우
|
|
87
|
-
-
|
|
87
|
+
- shadow root 전체에서 `lit-part` comment marker를 찾습니다. `static styles`로
|
|
88
|
+
첫 자식이 `<style>`이어도 그 뒤/하위의 marker를 인식합니다.
|
|
89
|
+
- 기존 server tree를 manual hydrate하고 hydration support flag를 해제해
|
|
90
|
+
double hydration을 막습니다.
|
|
91
|
+
- 첫 client update가 이미 별도 tree를 append한 경우 server root part만
|
|
92
|
+
분리하고 client tree를 제거한 뒤 server tree를 다시 채택합니다.
|
|
88
93
|
4. `root`가 비어 있는 경우
|
|
89
94
|
- element를 mount하고 일반 client render를 수행합니다.
|
|
90
95
|
|
|
96
|
+
### Static styles와 marker 탐색
|
|
97
|
+
|
|
98
|
+
`hasLitPartMarker`는 direct sibling만 보지 않고 `TreeWalker`로 root 아래의
|
|
99
|
+
comment node를 순회합니다. 따라서 다음 markup에서도 hydration 경로를 탑니다.
|
|
100
|
+
|
|
101
|
+
```html
|
|
102
|
+
<template shadowrootmode="open">
|
|
103
|
+
<style>...</style>
|
|
104
|
+
<!--lit-part ...-->
|
|
105
|
+
<div class="tl-item">...</div>
|
|
106
|
+
<!--/lit-part-->
|
|
107
|
+
</template>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 이미 중복된 client tree 복구
|
|
111
|
+
|
|
112
|
+
`hydrateLit` 호출 전에 LitElement의 첫 update가 실행되어 server tree 옆에
|
|
113
|
+
client tree가 append된 경우, `_$litPart$`가 server root part인지 확인합니다.
|
|
114
|
+
별도 client render라면 server root marker 범위만 남기고 client anchor/children을
|
|
115
|
+
제거한 뒤 `hydrate()`로 다시 채택합니다. 서버 tree를 분리할 수 없으면 shadow
|
|
116
|
+
root를 비우고 client render로 fallback합니다.
|
|
117
|
+
|
|
91
118
|
### Import 순서
|
|
92
119
|
|
|
93
120
|
```ts
|
|
@@ -5,6 +5,26 @@
|
|
|
5
5
|
[`releases/LIT_PUBLIC_0.1.0.md`](../../../releases/LIT_PUBLIC_0.1.0.md)와
|
|
6
6
|
[`releases/lit-public-2026-10.json`](../../../releases/lit-public-2026-10.json)입니다.
|
|
7
7
|
|
|
8
|
+
## 0. 게시 상태
|
|
9
|
+
|
|
10
|
+
두 패키지는 2026-10-23 npm에 게시되었습니다.
|
|
11
|
+
|
|
12
|
+
| 패키지 | 버전 | latest | next |
|
|
13
|
+
| --- | --- | --- | --- |
|
|
14
|
+
| `@context-action/lit` | `0.1.0` | `0.1.0` | `0.1.0` |
|
|
15
|
+
| `@context-action/lit-ui` | `0.1.0` | `0.1.0` | `0.1.0` |
|
|
16
|
+
|
|
17
|
+
- Candidate: `publish-lit-public-candidate.yml` — 성공
|
|
18
|
+
- Promotion: `promote-lit-public.yml` — 성공
|
|
19
|
+
- Registry consumer smoke — 25 assertions 통과
|
|
20
|
+
|
|
21
|
+
### 0.1.1 patch candidate
|
|
22
|
+
|
|
23
|
+
`@context-action/lit-ui@0.1.1`은 static styles DSD hydration과 중복 client tree
|
|
24
|
+
복구 patch입니다. `@context-action/lit@0.1.0`은 재사용하며, candidate/promotion
|
|
25
|
+
workflow는 기존 lit artifact의 attested commit과 새 lit-ui commit을 함께
|
|
26
|
+
검증합니다. 배포 완료 후 이 절의 상태를 갱신합니다.
|
|
27
|
+
|
|
8
28
|
## 1. 패키지 경계
|
|
9
29
|
|
|
10
30
|
```text
|
|
@@ -78,17 +98,15 @@ Candidate는 다음을 fail-closed로 수행합니다.
|
|
|
78
98
|
6. `@context-action/lit` → `@context-action/lit-ui` 순서로 `next` 게시
|
|
79
99
|
7. provenance, published consumer smoke, registry evidence
|
|
80
100
|
|
|
81
|
-
### 첫 배포 bootstrap
|
|
82
|
-
|
|
83
|
-
두 패키지는 아직 npm에 없으므로 첫 배포는 반드시:
|
|
101
|
+
### 첫 배포 bootstrap (완료)
|
|
84
102
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
NPM_TOKEN in npm-stable environment
|
|
88
|
-
```
|
|
103
|
+
0.1.0은 두 패키지가 npm에 없던 상태였으므로 `publish_auth=token`과 `npm-stable`의
|
|
104
|
+
`NPM_TOKEN`으로 bootstrap했습니다.
|
|
89
105
|
|
|
90
|
-
|
|
91
|
-
|
|
106
|
+
이제 두 package version이 registry에 존재합니다. npm trusted publisher가 이
|
|
107
|
+
workflow/environment에 구성된 경우 후속 릴리스는 `publish_auth=oidc`를 사용할 수
|
|
108
|
+
있습니다. Candidate workflow는 OIDC 선택 시 package 존재 여부를 확인하고 없으면
|
|
109
|
+
fail-closed로 중단합니다. `NPM_TOKEN`은 bootstrap/recovery 경로로 유지합니다.
|
|
92
110
|
|
|
93
111
|
### Promotion
|
|
94
112
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@context-action/lit-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@context-action/core": "^1.2.6",
|
|
60
|
-
"@context-action/lit": "^0.1.
|
|
60
|
+
"@context-action/lit": "^0.1.1"
|
|
61
61
|
},
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"@lit-labs/ssr": "^4.1.0",
|