@llui/vike 0.0.20 → 0.0.21
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/README.md +1 -1
- package/dist/on-render-client.d.ts +4 -4
- package/dist/on-render-client.d.ts.map +1 -1
- package/dist/on-render-client.js +14 -14
- package/dist/on-render-client.js.map +1 -1
- package/dist/on-render-html.d.ts.map +1 -1
- package/dist/on-render-html.js +1 -1
- package/dist/on-render-html.js.map +1 -1
- package/dist/page-slot.d.ts +2 -2
- package/dist/page-slot.d.ts.map +1 -1
- package/dist/page-slot.js +3 -3
- package/dist/page-slot.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -130,7 +130,7 @@ The chain diff on each nav walks old and new chains in parallel and finds the fi
|
|
|
130
130
|
|
|
131
131
|
Layouts and pages are independent component instances with their own state, update, and `send`. They share state and expose cross-cutting operations via **context**, not via direct messaging.
|
|
132
132
|
|
|
133
|
-
The scope-tree integration makes this natural: `pageSlot()` creates its slot as a child of the layout's render scope, and the page's `
|
|
133
|
+
The scope-tree integration makes this natural: `pageSlot()` creates its slot as a child of the layout's render scope, and the page's `rootLifetime` is parented inside that slot. `useContext` from within the page walks up through the slot and finds any providers the layout installed above it.
|
|
134
134
|
|
|
135
135
|
Common pattern — a layout-owned toast system:
|
|
136
136
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyComponentDef, TransitionOptions,
|
|
1
|
+
import type { AnyComponentDef, TransitionOptions, Lifetime } from '@llui/dom';
|
|
2
2
|
import type { VikePageContextData } from './vike-namespace.js';
|
|
3
3
|
export { pageSlot } from './page-slot.js';
|
|
4
4
|
declare global {
|
|
@@ -191,8 +191,8 @@ interface MountOpts {
|
|
|
191
191
|
}
|
|
192
192
|
/**
|
|
193
193
|
* Mount (or hydrate) `chain[startAt..end]` into `initialTarget`, with
|
|
194
|
-
* the initial layer's
|
|
195
|
-
* Threads slot → next-target → next-
|
|
194
|
+
* the initial layer's rootLifetime parented at `initialParentLifetime`.
|
|
195
|
+
* Threads slot → next-target → next-parentLifetime through the chain.
|
|
196
196
|
*
|
|
197
197
|
* `initialTarget` is `HTMLElement` for the outermost layer (container-
|
|
198
198
|
* based mount/hydrate) and `Comment` for inner layers that mount relative
|
|
@@ -205,5 +205,5 @@ interface MountOpts {
|
|
|
205
205
|
* test anchor-mount/dispose contracts directly with hand-built DOM.
|
|
206
206
|
* Not part of the public API.
|
|
207
207
|
*/
|
|
208
|
-
export declare function _mountChainSuffix(chain: LayoutChain, chainData: readonly unknown[], startAt: number, initialTarget: HTMLElement | Comment,
|
|
208
|
+
export declare function _mountChainSuffix(chain: LayoutChain, chainData: readonly unknown[], startAt: number, initialTarget: HTMLElement | Comment, initialParentLifetime: Lifetime | undefined, opts: MountOpts): void;
|
|
209
209
|
//# sourceMappingURL=on-render-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"on-render-client.d.ts","sourceRoot":"","sources":["../src/on-render-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAa,iBAAiB,EAAE,
|
|
1
|
+
{"version":3,"file":"on-render-client.d.ts","sourceRoot":"","sources":["../src/on-render-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAa,iBAAiB,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAExF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAI9D,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,cAAc,CAAC,EAAE,OAAO,CAAA;KACzB;CACF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,eAAe,CAAA;IACrB,IAAI,CAAC,EAAE,mBAAmB,CAAA;IAC1B,cAAc,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;IACnC,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED,KAAK,WAAW,GAAG,aAAa,CAAC,eAAe,CAAC,CAAA;AAoBjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,EAAE,eAAe,GAAG,WAAW,GAAG,CAAC,CAAC,WAAW,EAAE,iBAAiB,KAAK,WAAW,CAAC,CAAA;IAE1F;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEnD;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,IAAI,CAAA;IAEnC;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,cAAc,CAC5B,CAAC,EAAE,iBAAiB,GACnB,IAAI,CAAC,mBAAmB,EAAE,SAAS,GAAG,SAAS,CAAC,CAgBlD;AA2BD;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAOzC;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAEjD;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAElF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,mBAAmB,GAC3B,CAAC,WAAW,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAEnD;AAyID,UAAU,SAAS;IACjB,IAAI,EAAE,OAAO,GAAG,SAAS,CAAA;IACzB,gEAAgE;IAChE,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,SAAS,OAAO,EAAE,EAC7B,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,WAAW,GAAG,OAAO,EACpC,qBAAqB,EAAE,QAAQ,GAAG,SAAS,EAC3C,IAAI,EAAE,SAAS,GACd,IAAI,CAmGN"}
|
package/dist/on-render-client.js
CHANGED
|
@@ -199,7 +199,7 @@ async function renderClient(pageContext, options) {
|
|
|
199
199
|
await options.onLeave(leaveTargetEl);
|
|
200
200
|
}
|
|
201
201
|
// Dispose the divergent suffix, innermost first. Each handle.dispose()
|
|
202
|
-
// calls
|
|
202
|
+
// calls disposeLifetime on that layer's rootLifetime, which cascades through
|
|
203
203
|
// every child scope the layer owned (bindings, portals, onMount
|
|
204
204
|
// cleanups, dialog focus traps, etc.). The surviving layers are
|
|
205
205
|
// untouched because their scopes live above the disposal roots.
|
|
@@ -212,9 +212,9 @@ async function renderClient(pageContext, options) {
|
|
|
212
212
|
// Mount the new suffix starting at firstMismatch.
|
|
213
213
|
// For a root swap, the target is the container HTMLElement.
|
|
214
214
|
// For a deeper swap, the target is the surviving layer's slot anchor (Comment).
|
|
215
|
-
const
|
|
215
|
+
const parentLifetime = firstMismatch === 0 ? undefined : (chainHandles[firstMismatch - 1].slotLifetime ?? undefined);
|
|
216
216
|
const mountTargetArg = firstMismatch === 0 ? rootEl : chainHandles[firstMismatch - 1].slotAnchor;
|
|
217
|
-
mountChainSuffix(newChain, newChainData, firstMismatch, mountTargetArg,
|
|
217
|
+
mountChainSuffix(newChain, newChainData, firstMismatch, mountTargetArg, parentLifetime, {
|
|
218
218
|
mode: 'mount',
|
|
219
219
|
});
|
|
220
220
|
// onEnter fires after the new suffix is in place. Fire-and-forget.
|
|
@@ -229,15 +229,15 @@ async function renderClient(pageContext, options) {
|
|
|
229
229
|
/**
|
|
230
230
|
* Walk the full chain for the first mount or hydration. Starts from
|
|
231
231
|
* depth 0 at the root container, threads each layer's slot into the
|
|
232
|
-
* next layer's mount target +
|
|
232
|
+
* next layer's mount target + parentLifetime.
|
|
233
233
|
*/
|
|
234
234
|
async function mountOrHydrateChain(chain, chainData, rootEl, opts) {
|
|
235
235
|
mountChainSuffix(chain, chainData, 0, rootEl, undefined, opts);
|
|
236
236
|
}
|
|
237
237
|
/**
|
|
238
238
|
* Mount (or hydrate) `chain[startAt..end]` into `initialTarget`, with
|
|
239
|
-
* the initial layer's
|
|
240
|
-
* Threads slot → next-target → next-
|
|
239
|
+
* the initial layer's rootLifetime parented at `initialParentLifetime`.
|
|
240
|
+
* Threads slot → next-target → next-parentLifetime through the chain.
|
|
241
241
|
*
|
|
242
242
|
* `initialTarget` is `HTMLElement` for the outermost layer (container-
|
|
243
243
|
* based mount/hydrate) and `Comment` for inner layers that mount relative
|
|
@@ -250,9 +250,9 @@ async function mountOrHydrateChain(chain, chainData, rootEl, opts) {
|
|
|
250
250
|
* test anchor-mount/dispose contracts directly with hand-built DOM.
|
|
251
251
|
* Not part of the public API.
|
|
252
252
|
*/
|
|
253
|
-
export function _mountChainSuffix(chain, chainData, startAt, initialTarget,
|
|
253
|
+
export function _mountChainSuffix(chain, chainData, startAt, initialTarget, initialParentLifetime, opts) {
|
|
254
254
|
let mountTarget = initialTarget;
|
|
255
|
-
let
|
|
255
|
+
let parentLifetime = initialParentLifetime;
|
|
256
256
|
for (let i = startAt; i < chain.length; i++) {
|
|
257
257
|
const def = chain[i];
|
|
258
258
|
const layerData = chainData[i];
|
|
@@ -273,21 +273,21 @@ export function _mountChainSuffix(chain, chainData, startAt, initialTarget, init
|
|
|
273
273
|
// ComponentDef<unknown, unknown, unknown, unknown> for the mount
|
|
274
274
|
// primitive's signature. The cast is safe — mountApp / hydrateApp
|
|
275
275
|
// don't use the type parameters at runtime.
|
|
276
|
-
handle = hydrateApp(mountTarget, def, layerState, {
|
|
276
|
+
handle = hydrateApp(mountTarget, def, layerState, { parentLifetime });
|
|
277
277
|
}
|
|
278
278
|
else {
|
|
279
279
|
// Comment anchor — inner layer, use hydrateAtAnchor.
|
|
280
|
-
handle = hydrateAtAnchor(mountTarget, def, layerState, {
|
|
280
|
+
handle = hydrateAtAnchor(mountTarget, def, layerState, { parentLifetime });
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
283
|
else {
|
|
284
284
|
if (mountTarget.nodeType === 1) {
|
|
285
285
|
// HTMLElement — outermost layer, use mountApp (container-based).
|
|
286
|
-
handle = mountApp(mountTarget, def, layerData, {
|
|
286
|
+
handle = mountApp(mountTarget, def, layerData, { parentLifetime });
|
|
287
287
|
}
|
|
288
288
|
else {
|
|
289
289
|
// Comment anchor — inner layer, use mountAtAnchor.
|
|
290
|
-
handle = mountAtAnchor(mountTarget, def, layerData, {
|
|
290
|
+
handle = mountAtAnchor(mountTarget, def, layerData, { parentLifetime });
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
293
|
const slot = _consumePendingSlot();
|
|
@@ -312,13 +312,13 @@ export function _mountChainSuffix(chain, chainData, startAt, initialTarget, init
|
|
|
312
312
|
def,
|
|
313
313
|
handle,
|
|
314
314
|
slotAnchor: slot?.anchor ?? null,
|
|
315
|
-
|
|
315
|
+
slotLifetime: slot?.slotLifetime ?? null,
|
|
316
316
|
data: layerData,
|
|
317
317
|
});
|
|
318
318
|
if (slot !== null) {
|
|
319
319
|
// Next layer mounts relative to the slot's comment anchor.
|
|
320
320
|
mountTarget = slot.anchor;
|
|
321
|
-
|
|
321
|
+
parentLifetime = slot.slotLifetime;
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
324
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"on-render-client.js","sourceRoot":"","sources":["../src/on-render-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { hydrateApp, mountApp, mountAtAnchor, hydrateAtAnchor } from '@llui/dom'\nimport type { AnyComponentDef, AppHandle, TransitionOptions, Scope } from '@llui/dom'\nimport { _consumePendingSlot, _resetPendingSlot } from './page-slot.js'\nimport type { VikePageContextData } from './vike-namespace.js'\n\n// Re-exported so `@llui/vike/client` is a one-stop-shop for everything\n// a pages/+onRenderClient.ts / +Layout.ts file needs.\nexport { pageSlot } from './page-slot.js'\n\ndeclare global {\n interface Window {\n __LLUI_STATE__?: unknown\n }\n}\n\n/**\n * Page context shape as seen by `@llui/vike`'s client-side hooks. The\n * `Page` and `data` fields come from whichever `+Page.ts` and `+data.ts`\n * Vike resolved for the current route.\n *\n * `data` is derived from the global `Vike.PageContext` namespace — the\n * convention users already know from Vike. Consumer augmentations of\n * `Vike.PageContext { interface PageContext { data?: MyData } }` flow\n * through to every callback here without a cast. Unaugmented projects\n * fall back to `unknown`.\n *\n * `lluiLayoutData` is optional and carries per-layer data for the layout\n * chain configured via `createOnRenderClient({ Layout })`. It's indexed\n * outermost-to-innermost, one entry per layout layer. Absent entries\n * mean the corresponding layout's `init()` receives `undefined`. Users\n * wire this from their Vike `+data.ts` files by merging layout-owned\n * data under the `lluiLayoutData` key.\n */\nexport interface ClientPageContext {\n Page: AnyComponentDef\n data?: VikePageContextData\n lluiLayoutData?: readonly unknown[]\n isHydration?: boolean\n}\n\ntype LayoutChain = ReadonlyArray<AnyComponentDef>\n\n/**\n * Resolves the layout chain for a given pageContext. A single layout\n * becomes a one-element chain; a function resolver gives callers full\n * control to return different chains for different routes (e.g. nested\n * layouts keyed on Vike's `pageContext.urlPathname`).\n */\nfunction resolveLayoutChain(\n layoutOption: RenderClientOptions['Layout'],\n pageContext: ClientPageContext,\n): LayoutChain {\n if (!layoutOption) return []\n if (typeof layoutOption === 'function') {\n return layoutOption(pageContext) ?? []\n }\n if (Array.isArray(layoutOption)) return layoutOption\n return [layoutOption as AnyComponentDef]\n}\n\n/**\n * Page-lifecycle hooks that fire around the dispose → mount cycle on\n * client navigation. With persistent layouts in play the cycle only\n * tears down the *divergent* suffix of the layout chain — any layers\n * shared between the old and new routes stay mounted.\n *\n * Navigation sequence for an already-mounted app:\n *\n * ```\n * client nav triggered\n * │\n * ▼\n * compare old chain to new chain → find first mismatch index K\n * │\n * ▼\n * onLeave(leaveTarget) ← awaited; leaveTarget is the slot element\n * │ at depth K-1 (or the root container if K=0)\n * │ whose contents are about to be replaced\n * ▼\n * dispose chainHandles[K..end] innermost first\n * │\n * ▼\n * leaveTarget.textContent = ''\n * │\n * ▼\n * mount newChain[K..end] into leaveTarget, outermost first\n * │\n * ▼\n * onEnter(leaveTarget) ← fire-and-forget; fresh DOM in place\n * │\n * ▼\n * onMount()\n * ```\n *\n * On the initial hydration render, `onLeave` and `onEnter` are NOT\n * called — there's no outgoing page to leave and no animation to enter.\n * Use `onMount` for code that should run on every render including the\n * initial one.\n */\nexport interface RenderClientOptions {\n /** CSS selector for the mount container. Default: `'#app'`. */\n container?: string\n\n /**\n * Persistent layout chain. One of:\n *\n * - A single `ComponentDef` — becomes a one-layout chain.\n * - An array of `ComponentDef`s — outermost layout first, innermost\n * layout last. Every layer except the innermost must call\n * `pageSlot()` in its view to declare where nested content renders.\n * - A function that returns a chain from the current `pageContext` —\n * lets different routes use different chains, e.g. by reading\n * Vike's `pageContext.urlPathname` or `pageContext.config.Layout`.\n *\n * Layers that are shared between the previous and next navigation\n * stay mounted. Only the divergent suffix is disposed and re-mounted.\n * Dialogs, focus traps, and effect subscriptions rooted in a surviving\n * layer are unaffected by the nav.\n */\n Layout?: AnyComponentDef | LayoutChain | ((pageContext: ClientPageContext) => LayoutChain)\n\n /**\n * Called on the slot element whose contents are about to be replaced,\n * BEFORE the divergent suffix is disposed and re-mounted. The slot's\n * current DOM is still attached when this runs — the only moment a\n * leave animation can read/write it. Return a promise to defer the\n * swap until the animation completes.\n *\n * For a plain no-layout setup, the slot element is the root container.\n * Not called on the initial hydration render.\n */\n onLeave?: (el: HTMLElement) => void | Promise<void>\n\n /**\n * Called after the new divergent suffix is mounted, on the same slot\n * element that was passed to `onLeave`. Use this to kick off an enter\n * animation. Fire-and-forget — promise returns are ignored.\n *\n * Not called on the initial hydration render.\n */\n onEnter?: (el: HTMLElement) => void\n\n /**\n * Called after mount or hydration completes. Fires on every render\n * including the initial hydration. Use for per-render side effects\n * that don't fit the animation hooks.\n */\n onMount?: () => void\n}\n\n/**\n * Adapt a `TransitionOptions` object (e.g. the output of\n * `routeTransition()` from `@llui/transitions`, or a preset like `fade`\n * / `slide`) into the `onLeave` / `onEnter` pair expected by\n * `createOnRenderClient`.\n *\n * ```ts\n * import { createOnRenderClient, fromTransition } from '@llui/vike/client'\n * import { routeTransition } from '@llui/transitions'\n *\n * export const onRenderClient = createOnRenderClient({\n * Layout: AppLayout,\n * ...fromTransition(routeTransition({ duration: 200 })),\n * })\n * ```\n *\n * The transition operates on the slot element — in a no-layout setup,\n * the root container; in a layout setup, the innermost surviving\n * layer's `pageSlot()` element. Opacity / transform fades apply to the\n * outgoing page content, then the new page fades in.\n */\nexport function fromTransition(\n t: TransitionOptions,\n): Pick<RenderClientOptions, 'onLeave' | 'onEnter'> {\n return {\n onLeave: t.leave\n ? (el): void | Promise<void> => {\n const result = t.leave!([el])\n return result && typeof (result as Promise<void>).then === 'function'\n ? (result as Promise<void>)\n : undefined\n }\n : undefined,\n onEnter: t.enter\n ? (el): void => {\n t.enter!([el])\n }\n : undefined,\n }\n}\n\n/**\n * One element of the live chain the adapter keeps between navs.\n * `handle` is the AppHandle returned by mountApp/hydrateApp for this\n * layer. `slotAnchor` / `slotScope` are set when the layer called\n * `pageSlot()` during its view pass; they're null for the innermost\n * layer (typically the page component, which doesn't have a slot).\n */\ninterface ChainEntry {\n def: AnyComponentDef\n handle: AppHandle\n slotAnchor: Comment | null\n slotScope: Scope | null\n /**\n * The data slice this layer was most recently mounted or updated\n * with. Compared shallow-key against the next nav's `lluiLayoutData[i]`\n * to decide whether a surviving layer needs a `propsMsg` dispatch.\n * Layers that didn't receive any layout data carry `undefined` here.\n */\n data: unknown\n}\n\n// Live chain of mounted layers. Module-level state: there's exactly\n// one chain per Vike-managed app per page load.\nlet chainHandles: ChainEntry[] = []\n\n/**\n * @internal — test helper. Disposes every layer in the current chain\n * and clears the module state so subsequent calls behave as a first\n * mount. Not part of the public API; subject to change without notice.\n */\nexport function _resetChainForTest(): void {\n // Dispose innermost-first to match the normal teardown path.\n for (let i = chainHandles.length - 1; i >= 0; i--) {\n chainHandles[i]!.handle.dispose()\n }\n chainHandles = []\n _resetPendingSlot()\n}\n\n/**\n * Back-compat alias for the pre-layout test helper name.\n * @internal\n * @deprecated — use `_resetChainForTest` instead.\n */\nexport function _resetCurrentHandleForTest(): void {\n _resetChainForTest()\n}\n\n/**\n * Default onRenderClient hook — no layout, no animation hooks. Hydrates\n * on first load, mounts fresh on subsequent navs. Use `createOnRenderClient`\n * for the customizable factory form.\n */\nexport async function onRenderClient(pageContext: ClientPageContext): Promise<void> {\n await renderClient(pageContext, {})\n}\n\n/**\n * Factory to create a customized onRenderClient hook. See `RenderClientOptions`\n * for the full option surface — this is the entry point for persistent\n * layouts, route transitions, and lifecycle hooks.\n *\n * **Do not name your layout file `+Layout.ts`.** Vike reserves the `+`\n * prefix for its own framework config conventions, and `+Layout.ts` is\n * interpreted by `vike-react` / `vike-vue` / `vike-solid` framework\n * adapters as a native layout config. `@llui/vike` isn't a framework\n * adapter in that sense — it's a render adapter, and `createOnRenderClient`\n * consumes the layout component directly via the `Layout` option. Name\n * the file `Layout.ts`, `app-layout.ts`, or anywhere outside `/pages`\n * that Vike won't scan, and import it here by path.\n *\n * ```ts\n * // pages/+onRenderClient.ts\n * import { createOnRenderClient, fromTransition } from '@llui/vike/client'\n * import { routeTransition } from '@llui/transitions'\n * import { AppLayout } from './Layout' // ← NOT './+Layout'\n *\n * export const onRenderClient = createOnRenderClient({\n * Layout: AppLayout,\n * ...fromTransition(routeTransition({ duration: 200 })),\n * onMount: () => console.log('page rendered'),\n * })\n * ```\n */\nexport function createOnRenderClient(\n options: RenderClientOptions,\n): (pageContext: ClientPageContext) => Promise<void> {\n return (pageContext) => renderClient(pageContext, options)\n}\n\nasync function renderClient(\n pageContext: ClientPageContext,\n options: RenderClientOptions,\n): Promise<void> {\n const selector = options.container ?? '#app'\n const container = document.querySelector(selector)\n if (!container) {\n throw new Error(`@llui/vike: container \"${selector}\" not found in DOM`)\n }\n const rootEl = container as HTMLElement\n\n // Resolve the chain for this render. The page component is always\n // the innermost entry, regardless of layout configuration.\n const layoutChain = resolveLayoutChain(options.Layout, pageContext)\n const layoutData = pageContext.lluiLayoutData ?? []\n const newChain: LayoutChain = [...layoutChain, pageContext.Page]\n const newChainData: readonly unknown[] = [...layoutData, pageContext.data]\n\n if (pageContext.isHydration) {\n // First load — the chain starts empty and we hydrate every layer\n // against server-rendered HTML. No onLeave/onEnter on hydration.\n await mountOrHydrateChain(newChain, newChainData, rootEl, {\n mode: 'hydrate',\n serverStateEnvelope: window.__LLUI_STATE__,\n })\n options.onMount?.()\n return\n }\n\n // Subsequent nav — diff the chain to find the divergent suffix.\n let firstMismatch = 0\n const minLen = Math.min(chainHandles.length, newChain.length)\n while (firstMismatch < minLen && chainHandles[firstMismatch]!.def === newChain[firstMismatch]) {\n firstMismatch++\n }\n\n // Push fresh data into surviving layers (layers in the shared prefix).\n // Without this, persistent layouts can't react to nav-driven data\n // changes — pathname, breadcrumbs, session, nav-highlight state all\n // belong to the layout but change on every client navigation. Each\n // surviving layer's def can opt in via `propsMsg(data) => Msg`; we\n // dispatch the resulting message through the handle's `send` so the\n // layout's update loop processes it like any other state change.\n //\n // Diff is shallow-key Object.is on record-shaped data, falling back\n // to whole-value Object.is for primitives / non-records. This matches\n // child()'s prop-diff behavior, which is what the report asked us to\n // mirror. Layers without `propsMsg` are skipped silently — opt-in.\n for (let i = 0; i < firstMismatch; i++) {\n const entry = chainHandles[i]!\n const newData = newChainData[i]\n if (!hasDataChanged(entry.data, newData)) continue\n entry.data = newData\n const propsMsg = (entry.def as { propsMsg?: (data: unknown) => unknown }).propsMsg\n if (typeof propsMsg !== 'function') continue\n const msg = propsMsg(newData)\n if (msg !== null && msg !== undefined) {\n entry.handle.send(msg)\n }\n }\n\n // Determine whether this nav replaces the entire root or only a suffix.\n // For the root swap, the outermost layer mounts/hydrates via mountApp/\n // hydrateApp on rootEl. For a deeper swap, the mount target is an\n // anchor comment owned by the surviving layer's slot.\n const isRootSwap = firstMismatch === 0\n\n // If everything matches (same chain end-to-end with same defs), this\n // is effectively a no-op nav — the page def hasn't changed. We still\n // fire onMount so callers can run per-render side effects, and the\n // surviving-layer data updates above already ran. But there's nothing\n // to dispose or mount, so skip the rest of the work.\n const isNoOp = firstMismatch === chainHandles.length && firstMismatch === newChain.length\n if (isNoOp) {\n options.onMount?.()\n return\n }\n\n // onLeave runs BEFORE any teardown. Outgoing DOM still mounted here.\n // Skip on the very first mount — there's no outgoing page to leave.\n const isFirstMount = chainHandles.length === 0\n if (options.onLeave && !isFirstMount) {\n const leaveTargetEl = isRootSwap\n ? rootEl\n : (chainHandles[firstMismatch - 1]!.slotAnchor?.parentElement ?? rootEl)\n await options.onLeave(leaveTargetEl)\n }\n\n // Dispose the divergent suffix, innermost first. Each handle.dispose()\n // calls disposeScope on that layer's rootScope, which cascades through\n // every child scope the layer owned (bindings, portals, onMount\n // cleanups, dialog focus traps, etc.). The surviving layers are\n // untouched because their scopes live above the disposal roots.\n // For anchor-based mounts, dispose() also removes the owned DOM region\n // between the anchor and end sentinel — no additional textContent clear needed.\n for (let i = chainHandles.length - 1; i >= firstMismatch; i--) {\n chainHandles[i]!.handle.dispose()\n }\n chainHandles = chainHandles.slice(0, firstMismatch)\n\n // Mount the new suffix starting at firstMismatch.\n // For a root swap, the target is the container HTMLElement.\n // For a deeper swap, the target is the surviving layer's slot anchor (Comment).\n const parentScope =\n firstMismatch === 0 ? undefined : (chainHandles[firstMismatch - 1]!.slotScope ?? undefined)\n const mountTargetArg: HTMLElement | Comment =\n firstMismatch === 0 ? rootEl : chainHandles[firstMismatch - 1]!.slotAnchor!\n mountChainSuffix(newChain, newChainData, firstMismatch, mountTargetArg, parentScope, {\n mode: 'mount',\n })\n\n // onEnter fires after the new suffix is in place. Fire-and-forget.\n if (options.onEnter) {\n const enterTargetEl = isRootSwap\n ? rootEl\n : (chainHandles[firstMismatch - 1]!.slotAnchor?.parentElement ?? rootEl)\n options.onEnter(enterTargetEl)\n }\n options.onMount?.()\n}\n\n/**\n * Walk the full chain for the first mount or hydration. Starts from\n * depth 0 at the root container, threads each layer's slot into the\n * next layer's mount target + parentScope.\n */\nasync function mountOrHydrateChain(\n chain: LayoutChain,\n chainData: readonly unknown[],\n rootEl: HTMLElement,\n opts: MountOpts,\n): Promise<void> {\n mountChainSuffix(chain, chainData, 0, rootEl, undefined, opts)\n}\n\ninterface MountOpts {\n mode: 'mount' | 'hydrate'\n /** For hydration: the full `window.__LLUI_STATE__` envelope. */\n serverStateEnvelope?: unknown\n}\n\n/**\n * Mount (or hydrate) `chain[startAt..end]` into `initialTarget`, with\n * the initial layer's rootScope parented at `initialParentScope`.\n * Threads slot → next-target → next-parentScope through the chain.\n *\n * `initialTarget` is `HTMLElement` for the outermost layer (container-\n * based mount/hydrate) and `Comment` for inner layers that mount relative\n * to a `pageSlot()` anchor.\n *\n * Fails loudly if a non-innermost layer forgot to call `pageSlot()`,\n * or if the innermost layer called `pageSlot()` unnecessarily.\n *\n * @internal — test helper. Exported so `client-page-slot.test.ts` can\n * test anchor-mount/dispose contracts directly with hand-built DOM.\n * Not part of the public API.\n */\nexport function _mountChainSuffix(\n chain: LayoutChain,\n chainData: readonly unknown[],\n startAt: number,\n initialTarget: HTMLElement | Comment,\n initialParentScope: Scope | undefined,\n opts: MountOpts,\n): void {\n let mountTarget: HTMLElement | Comment = initialTarget\n let parentScope: Scope | undefined = initialParentScope\n\n for (let i = startAt; i < chain.length; i++) {\n const def = chain[i]!\n const layerData = chainData[i]\n const isInnermost = i === chain.length - 1\n\n // Defensive: clear any stale slot from a prior failed mount.\n _resetPendingSlot()\n\n let handle: AppHandle\n if (opts.mode === 'hydrate') {\n // Hydration envelope: each layer pulls its own state slice. The\n // envelope shape is `{ layouts: [...], page: {...} }` with each\n // entry carrying `{ name, state }`. We match by name so a server/\n // client mismatch throws with a clear error instead of silently\n // hydrating the wrong state into the wrong instance.\n const layerState = extractHydrationState(opts.serverStateEnvelope, i, chain.length, def)\n if (mountTarget.nodeType === 1) {\n // HTMLElement — outermost layer, use hydrateApp (container-based).\n // Cross from the type-erased AnyComponentDef back into a concrete\n // ComponentDef<unknown, unknown, unknown, unknown> for the mount\n // primitive's signature. The cast is safe — mountApp / hydrateApp\n // don't use the type parameters at runtime.\n handle = hydrateApp(\n mountTarget as HTMLElement,\n def as unknown as Parameters<typeof hydrateApp>[1],\n layerState,\n { parentScope },\n )\n } else {\n // Comment anchor — inner layer, use hydrateAtAnchor.\n handle = hydrateAtAnchor(\n mountTarget as Comment,\n def as unknown as Parameters<typeof hydrateAtAnchor>[1],\n layerState as never,\n { parentScope },\n )\n }\n } else {\n if (mountTarget.nodeType === 1) {\n // HTMLElement — outermost layer, use mountApp (container-based).\n handle = mountApp(\n mountTarget as HTMLElement,\n def as unknown as Parameters<typeof mountApp>[1],\n layerData,\n { parentScope },\n )\n } else {\n // Comment anchor — inner layer, use mountAtAnchor.\n handle = mountAtAnchor(\n mountTarget as Comment,\n def as unknown as Parameters<typeof mountAtAnchor>[1],\n layerData,\n { parentScope },\n )\n }\n }\n\n const slot = _consumePendingSlot()\n\n if (isInnermost && slot !== null) {\n // Innermost layer declared a slot with nothing to fill it —\n // probably a misuse of pageSlot() in the page component itself.\n handle.dispose()\n throw new Error(\n `[llui/vike] <${def.name}> is the innermost component in the chain ` +\n `but called pageSlot(). pageSlot() only belongs in layout components ` +\n `that wrap a nested page or layout — not in the page itself.`,\n )\n }\n if (!isInnermost && slot === null) {\n // Non-innermost layer didn't declare a slot — there's nowhere to\n // mount the remaining chain.\n handle.dispose()\n throw new Error(\n `[llui/vike] <${def.name}> is a layout layer at depth ${i} but did not ` +\n `call pageSlot() in its view(). There are ${chain.length - i - 1} more ` +\n `layer(s) to mount and no slot to mount them into. Add pageSlot() from ` +\n `@llui/vike/client to the view at the position where nested content renders.`,\n )\n }\n\n chainHandles.push({\n def,\n handle,\n slotAnchor: slot?.anchor ?? null,\n slotScope: slot?.slotScope ?? null,\n data: layerData,\n })\n\n if (slot !== null) {\n // Next layer mounts relative to the slot's comment anchor.\n mountTarget = slot.anchor\n parentScope = slot.slotScope\n }\n }\n}\n\n// Internal alias used by renderClient and mountOrHydrateChain.\n// The public-named export above carries the @internal doc.\nconst mountChainSuffix = _mountChainSuffix\n\n/**\n * Pull the per-layer state from the hydration envelope. Supports both\n * the new chain-aware shape (`{ layouts: [...], page: {...} }`) and the\n * legacy flat shape (`window.__LLUI_STATE__` is the state object itself)\n * for backward compatibility with pages written against 0.0.15 or earlier.\n *\n * Throws on envelope shape mismatch — missing entries, wrong component\n * name at a given index — so server/client drift fails loud instead of\n * silently binding the wrong state to the wrong instance.\n */\n/**\n * Shallow-key data diff for the persistent-layer prop-update path.\n * Returns true when `next` differs from `prev` enough to warrant\n * dispatching a `propsMsg`. Mirrors `child()`'s prop-diff semantics:\n *\n * - `Object.is(prev, next)` short-circuits identical references.\n * - For two plain-object records, walks the union of keys and returns\n * true on the first `Object.is` mismatch.\n * - For anything else (primitives, arrays, class instances), falls\n * back to the top-level `Object.is` result — covers the cases where\n * the host populates `lluiLayoutData[i]` with a primitive or a\n * referentially-stable object.\n */\nfunction hasDataChanged(prev: unknown, next: unknown): boolean {\n if (Object.is(prev, next)) return false\n // Both must be plain object records to do a key walk; otherwise the\n // Object.is above is the only signal.\n if (\n prev === null ||\n next === null ||\n typeof prev !== 'object' ||\n typeof next !== 'object' ||\n Array.isArray(prev) ||\n Array.isArray(next)\n ) {\n return true\n }\n const prevRec = prev as Record<string, unknown>\n const nextRec = next as Record<string, unknown>\n const seen = new Set<string>()\n for (const k of Object.keys(prevRec)) {\n seen.add(k)\n if (!Object.is(prevRec[k], nextRec[k])) return true\n }\n for (const k of Object.keys(nextRec)) {\n if (!seen.has(k)) return true\n }\n return false\n}\n\nfunction extractHydrationState(\n envelope: unknown,\n layerIndex: number,\n chainLength: number,\n def: AnyComponentDef,\n): unknown {\n // Legacy flat envelope — no layout chain at render time. Only valid\n // when the chain has a single layer (the page).\n const isLegacyFlat =\n envelope !== null &&\n typeof envelope === 'object' &&\n !('layouts' in (envelope as object)) &&\n !('page' in (envelope as object))\n\n if (isLegacyFlat) {\n if (chainLength !== 1) {\n throw new Error(\n `[llui/vike] Hydration envelope is in the legacy flat shape but the ` +\n `current render has ${chainLength} chain layers. The server must emit ` +\n `the chain-aware shape ({ layouts, page }) when rendering with a layout.`,\n )\n }\n return envelope\n }\n\n const chainEnvelope = envelope as\n | { layouts?: Array<{ name: string; state: unknown }>; page?: { name: string; state: unknown } }\n | undefined\n if (!chainEnvelope) {\n throw new Error(\n `[llui/vike] Hydration envelope is missing. Server-side onRenderHtml must ` +\n `populate window.__LLUI_STATE__ with the full chain before client hydration.`,\n )\n }\n\n const isPageLayer = layerIndex === chainLength - 1\n const layoutEntries = chainEnvelope.layouts ?? []\n const expected = isPageLayer ? chainEnvelope.page : layoutEntries[layerIndex]\n\n if (!expected) {\n throw new Error(\n `[llui/vike] Hydration envelope has no entry for chain layer ${layerIndex} ` +\n `(<${def.name}>). Server rendered ${layoutEntries.length} layouts + ${\n chainEnvelope.page ? 'a page' : 'no page'\n }, client expected ${chainLength} total entries.`,\n )\n }\n\n if (expected.name !== def.name) {\n throw new Error(\n `[llui/vike] Hydration mismatch at chain layer ${layerIndex}: server ` +\n `rendered <${expected.name}> but client is trying to hydrate <${def.name}>. ` +\n `This usually means the layout chain resolver returns different layouts ` +\n `on the server and the client for the same route.`,\n )\n }\n\n return expected.state\n}\n"]}
|
|
1
|
+
{"version":3,"file":"on-render-client.js","sourceRoot":"","sources":["../src/on-render-client.ts"],"names":[],"mappings":"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","sourcesContent":["import { hydrateApp, mountApp, mountAtAnchor, hydrateAtAnchor } from '@llui/dom'\nimport type { AnyComponentDef, AppHandle, TransitionOptions, Lifetime } from '@llui/dom'\nimport { _consumePendingSlot, _resetPendingSlot } from './page-slot.js'\nimport type { VikePageContextData } from './vike-namespace.js'\n\n// Re-exported so `@llui/vike/client` is a one-stop-shop for everything\n// a pages/+onRenderClient.ts / +Layout.ts file needs.\nexport { pageSlot } from './page-slot.js'\n\ndeclare global {\n interface Window {\n __LLUI_STATE__?: unknown\n }\n}\n\n/**\n * Page context shape as seen by `@llui/vike`'s client-side hooks. The\n * `Page` and `data` fields come from whichever `+Page.ts` and `+data.ts`\n * Vike resolved for the current route.\n *\n * `data` is derived from the global `Vike.PageContext` namespace — the\n * convention users already know from Vike. Consumer augmentations of\n * `Vike.PageContext { interface PageContext { data?: MyData } }` flow\n * through to every callback here without a cast. Unaugmented projects\n * fall back to `unknown`.\n *\n * `lluiLayoutData` is optional and carries per-layer data for the layout\n * chain configured via `createOnRenderClient({ Layout })`. It's indexed\n * outermost-to-innermost, one entry per layout layer. Absent entries\n * mean the corresponding layout's `init()` receives `undefined`. Users\n * wire this from their Vike `+data.ts` files by merging layout-owned\n * data under the `lluiLayoutData` key.\n */\nexport interface ClientPageContext {\n Page: AnyComponentDef\n data?: VikePageContextData\n lluiLayoutData?: readonly unknown[]\n isHydration?: boolean\n}\n\ntype LayoutChain = ReadonlyArray<AnyComponentDef>\n\n/**\n * Resolves the layout chain for a given pageContext. A single layout\n * becomes a one-element chain; a function resolver gives callers full\n * control to return different chains for different routes (e.g. nested\n * layouts keyed on Vike's `pageContext.urlPathname`).\n */\nfunction resolveLayoutChain(\n layoutOption: RenderClientOptions['Layout'],\n pageContext: ClientPageContext,\n): LayoutChain {\n if (!layoutOption) return []\n if (typeof layoutOption === 'function') {\n return layoutOption(pageContext) ?? []\n }\n if (Array.isArray(layoutOption)) return layoutOption\n return [layoutOption as AnyComponentDef]\n}\n\n/**\n * Page-lifecycle hooks that fire around the dispose → mount cycle on\n * client navigation. With persistent layouts in play the cycle only\n * tears down the *divergent* suffix of the layout chain — any layers\n * shared between the old and new routes stay mounted.\n *\n * Navigation sequence for an already-mounted app:\n *\n * ```\n * client nav triggered\n * │\n * ▼\n * compare old chain to new chain → find first mismatch index K\n * │\n * ▼\n * onLeave(leaveTarget) ← awaited; leaveTarget is the slot element\n * │ at depth K-1 (or the root container if K=0)\n * │ whose contents are about to be replaced\n * ▼\n * dispose chainHandles[K..end] innermost first\n * │\n * ▼\n * leaveTarget.textContent = ''\n * │\n * ▼\n * mount newChain[K..end] into leaveTarget, outermost first\n * │\n * ▼\n * onEnter(leaveTarget) ← fire-and-forget; fresh DOM in place\n * │\n * ▼\n * onMount()\n * ```\n *\n * On the initial hydration render, `onLeave` and `onEnter` are NOT\n * called — there's no outgoing page to leave and no animation to enter.\n * Use `onMount` for code that should run on every render including the\n * initial one.\n */\nexport interface RenderClientOptions {\n /** CSS selector for the mount container. Default: `'#app'`. */\n container?: string\n\n /**\n * Persistent layout chain. One of:\n *\n * - A single `ComponentDef` — becomes a one-layout chain.\n * - An array of `ComponentDef`s — outermost layout first, innermost\n * layout last. Every layer except the innermost must call\n * `pageSlot()` in its view to declare where nested content renders.\n * - A function that returns a chain from the current `pageContext` —\n * lets different routes use different chains, e.g. by reading\n * Vike's `pageContext.urlPathname` or `pageContext.config.Layout`.\n *\n * Layers that are shared between the previous and next navigation\n * stay mounted. Only the divergent suffix is disposed and re-mounted.\n * Dialogs, focus traps, and effect subscriptions rooted in a surviving\n * layer are unaffected by the nav.\n */\n Layout?: AnyComponentDef | LayoutChain | ((pageContext: ClientPageContext) => LayoutChain)\n\n /**\n * Called on the slot element whose contents are about to be replaced,\n * BEFORE the divergent suffix is disposed and re-mounted. The slot's\n * current DOM is still attached when this runs — the only moment a\n * leave animation can read/write it. Return a promise to defer the\n * swap until the animation completes.\n *\n * For a plain no-layout setup, the slot element is the root container.\n * Not called on the initial hydration render.\n */\n onLeave?: (el: HTMLElement) => void | Promise<void>\n\n /**\n * Called after the new divergent suffix is mounted, on the same slot\n * element that was passed to `onLeave`. Use this to kick off an enter\n * animation. Fire-and-forget — promise returns are ignored.\n *\n * Not called on the initial hydration render.\n */\n onEnter?: (el: HTMLElement) => void\n\n /**\n * Called after mount or hydration completes. Fires on every render\n * including the initial hydration. Use for per-render side effects\n * that don't fit the animation hooks.\n */\n onMount?: () => void\n}\n\n/**\n * Adapt a `TransitionOptions` object (e.g. the output of\n * `routeTransition()` from `@llui/transitions`, or a preset like `fade`\n * / `slide`) into the `onLeave` / `onEnter` pair expected by\n * `createOnRenderClient`.\n *\n * ```ts\n * import { createOnRenderClient, fromTransition } from '@llui/vike/client'\n * import { routeTransition } from '@llui/transitions'\n *\n * export const onRenderClient = createOnRenderClient({\n * Layout: AppLayout,\n * ...fromTransition(routeTransition({ duration: 200 })),\n * })\n * ```\n *\n * The transition operates on the slot element — in a no-layout setup,\n * the root container; in a layout setup, the innermost surviving\n * layer's `pageSlot()` element. Opacity / transform fades apply to the\n * outgoing page content, then the new page fades in.\n */\nexport function fromTransition(\n t: TransitionOptions,\n): Pick<RenderClientOptions, 'onLeave' | 'onEnter'> {\n return {\n onLeave: t.leave\n ? (el): void | Promise<void> => {\n const result = t.leave!([el])\n return result && typeof (result as Promise<void>).then === 'function'\n ? (result as Promise<void>)\n : undefined\n }\n : undefined,\n onEnter: t.enter\n ? (el): void => {\n t.enter!([el])\n }\n : undefined,\n }\n}\n\n/**\n * One element of the live chain the adapter keeps between navs.\n * `handle` is the AppHandle returned by mountApp/hydrateApp for this\n * layer. `slotAnchor` / `slotLifetime` are set when the layer called\n * `pageSlot()` during its view pass; they're null for the innermost\n * layer (typically the page component, which doesn't have a slot).\n */\ninterface ChainEntry {\n def: AnyComponentDef\n handle: AppHandle\n slotAnchor: Comment | null\n slotLifetime: Lifetime | null\n /**\n * The data slice this layer was most recently mounted or updated\n * with. Compared shallow-key against the next nav's `lluiLayoutData[i]`\n * to decide whether a surviving layer needs a `propsMsg` dispatch.\n * Layers that didn't receive any layout data carry `undefined` here.\n */\n data: unknown\n}\n\n// Live chain of mounted layers. Module-level state: there's exactly\n// one chain per Vike-managed app per page load.\nlet chainHandles: ChainEntry[] = []\n\n/**\n * @internal — test helper. Disposes every layer in the current chain\n * and clears the module state so subsequent calls behave as a first\n * mount. Not part of the public API; subject to change without notice.\n */\nexport function _resetChainForTest(): void {\n // Dispose innermost-first to match the normal teardown path.\n for (let i = chainHandles.length - 1; i >= 0; i--) {\n chainHandles[i]!.handle.dispose()\n }\n chainHandles = []\n _resetPendingSlot()\n}\n\n/**\n * Back-compat alias for the pre-layout test helper name.\n * @internal\n * @deprecated — use `_resetChainForTest` instead.\n */\nexport function _resetCurrentHandleForTest(): void {\n _resetChainForTest()\n}\n\n/**\n * Default onRenderClient hook — no layout, no animation hooks. Hydrates\n * on first load, mounts fresh on subsequent navs. Use `createOnRenderClient`\n * for the customizable factory form.\n */\nexport async function onRenderClient(pageContext: ClientPageContext): Promise<void> {\n await renderClient(pageContext, {})\n}\n\n/**\n * Factory to create a customized onRenderClient hook. See `RenderClientOptions`\n * for the full option surface — this is the entry point for persistent\n * layouts, route transitions, and lifecycle hooks.\n *\n * **Do not name your layout file `+Layout.ts`.** Vike reserves the `+`\n * prefix for its own framework config conventions, and `+Layout.ts` is\n * interpreted by `vike-react` / `vike-vue` / `vike-solid` framework\n * adapters as a native layout config. `@llui/vike` isn't a framework\n * adapter in that sense — it's a render adapter, and `createOnRenderClient`\n * consumes the layout component directly via the `Layout` option. Name\n * the file `Layout.ts`, `app-layout.ts`, or anywhere outside `/pages`\n * that Vike won't scan, and import it here by path.\n *\n * ```ts\n * // pages/+onRenderClient.ts\n * import { createOnRenderClient, fromTransition } from '@llui/vike/client'\n * import { routeTransition } from '@llui/transitions'\n * import { AppLayout } from './Layout' // ← NOT './+Layout'\n *\n * export const onRenderClient = createOnRenderClient({\n * Layout: AppLayout,\n * ...fromTransition(routeTransition({ duration: 200 })),\n * onMount: () => console.log('page rendered'),\n * })\n * ```\n */\nexport function createOnRenderClient(\n options: RenderClientOptions,\n): (pageContext: ClientPageContext) => Promise<void> {\n return (pageContext) => renderClient(pageContext, options)\n}\n\nasync function renderClient(\n pageContext: ClientPageContext,\n options: RenderClientOptions,\n): Promise<void> {\n const selector = options.container ?? '#app'\n const container = document.querySelector(selector)\n if (!container) {\n throw new Error(`@llui/vike: container \"${selector}\" not found in DOM`)\n }\n const rootEl = container as HTMLElement\n\n // Resolve the chain for this render. The page component is always\n // the innermost entry, regardless of layout configuration.\n const layoutChain = resolveLayoutChain(options.Layout, pageContext)\n const layoutData = pageContext.lluiLayoutData ?? []\n const newChain: LayoutChain = [...layoutChain, pageContext.Page]\n const newChainData: readonly unknown[] = [...layoutData, pageContext.data]\n\n if (pageContext.isHydration) {\n // First load — the chain starts empty and we hydrate every layer\n // against server-rendered HTML. No onLeave/onEnter on hydration.\n await mountOrHydrateChain(newChain, newChainData, rootEl, {\n mode: 'hydrate',\n serverStateEnvelope: window.__LLUI_STATE__,\n })\n options.onMount?.()\n return\n }\n\n // Subsequent nav — diff the chain to find the divergent suffix.\n let firstMismatch = 0\n const minLen = Math.min(chainHandles.length, newChain.length)\n while (firstMismatch < minLen && chainHandles[firstMismatch]!.def === newChain[firstMismatch]) {\n firstMismatch++\n }\n\n // Push fresh data into surviving layers (layers in the shared prefix).\n // Without this, persistent layouts can't react to nav-driven data\n // changes — pathname, breadcrumbs, session, nav-highlight state all\n // belong to the layout but change on every client navigation. Each\n // surviving layer's def can opt in via `propsMsg(data) => Msg`; we\n // dispatch the resulting message through the handle's `send` so the\n // layout's update loop processes it like any other state change.\n //\n // Diff is shallow-key Object.is on record-shaped data, falling back\n // to whole-value Object.is for primitives / non-records. This matches\n // child()'s prop-diff behavior, which is what the report asked us to\n // mirror. Layers without `propsMsg` are skipped silently — opt-in.\n for (let i = 0; i < firstMismatch; i++) {\n const entry = chainHandles[i]!\n const newData = newChainData[i]\n if (!hasDataChanged(entry.data, newData)) continue\n entry.data = newData\n const propsMsg = (entry.def as { propsMsg?: (data: unknown) => unknown }).propsMsg\n if (typeof propsMsg !== 'function') continue\n const msg = propsMsg(newData)\n if (msg !== null && msg !== undefined) {\n entry.handle.send(msg)\n }\n }\n\n // Determine whether this nav replaces the entire root or only a suffix.\n // For the root swap, the outermost layer mounts/hydrates via mountApp/\n // hydrateApp on rootEl. For a deeper swap, the mount target is an\n // anchor comment owned by the surviving layer's slot.\n const isRootSwap = firstMismatch === 0\n\n // If everything matches (same chain end-to-end with same defs), this\n // is effectively a no-op nav — the page def hasn't changed. We still\n // fire onMount so callers can run per-render side effects, and the\n // surviving-layer data updates above already ran. But there's nothing\n // to dispose or mount, so skip the rest of the work.\n const isNoOp = firstMismatch === chainHandles.length && firstMismatch === newChain.length\n if (isNoOp) {\n options.onMount?.()\n return\n }\n\n // onLeave runs BEFORE any teardown. Outgoing DOM still mounted here.\n // Skip on the very first mount — there's no outgoing page to leave.\n const isFirstMount = chainHandles.length === 0\n if (options.onLeave && !isFirstMount) {\n const leaveTargetEl = isRootSwap\n ? rootEl\n : (chainHandles[firstMismatch - 1]!.slotAnchor?.parentElement ?? rootEl)\n await options.onLeave(leaveTargetEl)\n }\n\n // Dispose the divergent suffix, innermost first. Each handle.dispose()\n // calls disposeLifetime on that layer's rootLifetime, which cascades through\n // every child scope the layer owned (bindings, portals, onMount\n // cleanups, dialog focus traps, etc.). The surviving layers are\n // untouched because their scopes live above the disposal roots.\n // For anchor-based mounts, dispose() also removes the owned DOM region\n // between the anchor and end sentinel — no additional textContent clear needed.\n for (let i = chainHandles.length - 1; i >= firstMismatch; i--) {\n chainHandles[i]!.handle.dispose()\n }\n chainHandles = chainHandles.slice(0, firstMismatch)\n\n // Mount the new suffix starting at firstMismatch.\n // For a root swap, the target is the container HTMLElement.\n // For a deeper swap, the target is the surviving layer's slot anchor (Comment).\n const parentLifetime =\n firstMismatch === 0 ? undefined : (chainHandles[firstMismatch - 1]!.slotLifetime ?? undefined)\n const mountTargetArg: HTMLElement | Comment =\n firstMismatch === 0 ? rootEl : chainHandles[firstMismatch - 1]!.slotAnchor!\n mountChainSuffix(newChain, newChainData, firstMismatch, mountTargetArg, parentLifetime, {\n mode: 'mount',\n })\n\n // onEnter fires after the new suffix is in place. Fire-and-forget.\n if (options.onEnter) {\n const enterTargetEl = isRootSwap\n ? rootEl\n : (chainHandles[firstMismatch - 1]!.slotAnchor?.parentElement ?? rootEl)\n options.onEnter(enterTargetEl)\n }\n options.onMount?.()\n}\n\n/**\n * Walk the full chain for the first mount or hydration. Starts from\n * depth 0 at the root container, threads each layer's slot into the\n * next layer's mount target + parentLifetime.\n */\nasync function mountOrHydrateChain(\n chain: LayoutChain,\n chainData: readonly unknown[],\n rootEl: HTMLElement,\n opts: MountOpts,\n): Promise<void> {\n mountChainSuffix(chain, chainData, 0, rootEl, undefined, opts)\n}\n\ninterface MountOpts {\n mode: 'mount' | 'hydrate'\n /** For hydration: the full `window.__LLUI_STATE__` envelope. */\n serverStateEnvelope?: unknown\n}\n\n/**\n * Mount (or hydrate) `chain[startAt..end]` into `initialTarget`, with\n * the initial layer's rootLifetime parented at `initialParentLifetime`.\n * Threads slot → next-target → next-parentLifetime through the chain.\n *\n * `initialTarget` is `HTMLElement` for the outermost layer (container-\n * based mount/hydrate) and `Comment` for inner layers that mount relative\n * to a `pageSlot()` anchor.\n *\n * Fails loudly if a non-innermost layer forgot to call `pageSlot()`,\n * or if the innermost layer called `pageSlot()` unnecessarily.\n *\n * @internal — test helper. Exported so `client-page-slot.test.ts` can\n * test anchor-mount/dispose contracts directly with hand-built DOM.\n * Not part of the public API.\n */\nexport function _mountChainSuffix(\n chain: LayoutChain,\n chainData: readonly unknown[],\n startAt: number,\n initialTarget: HTMLElement | Comment,\n initialParentLifetime: Lifetime | undefined,\n opts: MountOpts,\n): void {\n let mountTarget: HTMLElement | Comment = initialTarget\n let parentLifetime: Lifetime | undefined = initialParentLifetime\n\n for (let i = startAt; i < chain.length; i++) {\n const def = chain[i]!\n const layerData = chainData[i]\n const isInnermost = i === chain.length - 1\n\n // Defensive: clear any stale slot from a prior failed mount.\n _resetPendingSlot()\n\n let handle: AppHandle\n if (opts.mode === 'hydrate') {\n // Hydration envelope: each layer pulls its own state slice. The\n // envelope shape is `{ layouts: [...], page: {...} }` with each\n // entry carrying `{ name, state }`. We match by name so a server/\n // client mismatch throws with a clear error instead of silently\n // hydrating the wrong state into the wrong instance.\n const layerState = extractHydrationState(opts.serverStateEnvelope, i, chain.length, def)\n if (mountTarget.nodeType === 1) {\n // HTMLElement — outermost layer, use hydrateApp (container-based).\n // Cross from the type-erased AnyComponentDef back into a concrete\n // ComponentDef<unknown, unknown, unknown, unknown> for the mount\n // primitive's signature. The cast is safe — mountApp / hydrateApp\n // don't use the type parameters at runtime.\n handle = hydrateApp(\n mountTarget as HTMLElement,\n def as unknown as Parameters<typeof hydrateApp>[1],\n layerState,\n { parentLifetime },\n )\n } else {\n // Comment anchor — inner layer, use hydrateAtAnchor.\n handle = hydrateAtAnchor(\n mountTarget as Comment,\n def as unknown as Parameters<typeof hydrateAtAnchor>[1],\n layerState as never,\n { parentLifetime },\n )\n }\n } else {\n if (mountTarget.nodeType === 1) {\n // HTMLElement — outermost layer, use mountApp (container-based).\n handle = mountApp(\n mountTarget as HTMLElement,\n def as unknown as Parameters<typeof mountApp>[1],\n layerData,\n { parentLifetime },\n )\n } else {\n // Comment anchor — inner layer, use mountAtAnchor.\n handle = mountAtAnchor(\n mountTarget as Comment,\n def as unknown as Parameters<typeof mountAtAnchor>[1],\n layerData,\n { parentLifetime },\n )\n }\n }\n\n const slot = _consumePendingSlot()\n\n if (isInnermost && slot !== null) {\n // Innermost layer declared a slot with nothing to fill it —\n // probably a misuse of pageSlot() in the page component itself.\n handle.dispose()\n throw new Error(\n `[llui/vike] <${def.name}> is the innermost component in the chain ` +\n `but called pageSlot(). pageSlot() only belongs in layout components ` +\n `that wrap a nested page or layout — not in the page itself.`,\n )\n }\n if (!isInnermost && slot === null) {\n // Non-innermost layer didn't declare a slot — there's nowhere to\n // mount the remaining chain.\n handle.dispose()\n throw new Error(\n `[llui/vike] <${def.name}> is a layout layer at depth ${i} but did not ` +\n `call pageSlot() in its view(). There are ${chain.length - i - 1} more ` +\n `layer(s) to mount and no slot to mount them into. Add pageSlot() from ` +\n `@llui/vike/client to the view at the position where nested content renders.`,\n )\n }\n\n chainHandles.push({\n def,\n handle,\n slotAnchor: slot?.anchor ?? null,\n slotLifetime: slot?.slotLifetime ?? null,\n data: layerData,\n })\n\n if (slot !== null) {\n // Next layer mounts relative to the slot's comment anchor.\n mountTarget = slot.anchor\n parentLifetime = slot.slotLifetime\n }\n }\n}\n\n// Internal alias used by renderClient and mountOrHydrateChain.\n// The public-named export above carries the @internal doc.\nconst mountChainSuffix = _mountChainSuffix\n\n/**\n * Pull the per-layer state from the hydration envelope. Supports both\n * the new chain-aware shape (`{ layouts: [...], page: {...} }`) and the\n * legacy flat shape (`window.__LLUI_STATE__` is the state object itself)\n * for backward compatibility with pages written against 0.0.15 or earlier.\n *\n * Throws on envelope shape mismatch — missing entries, wrong component\n * name at a given index — so server/client drift fails loud instead of\n * silently binding the wrong state to the wrong instance.\n */\n/**\n * Shallow-key data diff for the persistent-layer prop-update path.\n * Returns true when `next` differs from `prev` enough to warrant\n * dispatching a `propsMsg`. Mirrors `child()`'s prop-diff semantics:\n *\n * - `Object.is(prev, next)` short-circuits identical references.\n * - For two plain-object records, walks the union of keys and returns\n * true on the first `Object.is` mismatch.\n * - For anything else (primitives, arrays, class instances), falls\n * back to the top-level `Object.is` result — covers the cases where\n * the host populates `lluiLayoutData[i]` with a primitive or a\n * referentially-stable object.\n */\nfunction hasDataChanged(prev: unknown, next: unknown): boolean {\n if (Object.is(prev, next)) return false\n // Both must be plain object records to do a key walk; otherwise the\n // Object.is above is the only signal.\n if (\n prev === null ||\n next === null ||\n typeof prev !== 'object' ||\n typeof next !== 'object' ||\n Array.isArray(prev) ||\n Array.isArray(next)\n ) {\n return true\n }\n const prevRec = prev as Record<string, unknown>\n const nextRec = next as Record<string, unknown>\n const seen = new Set<string>()\n for (const k of Object.keys(prevRec)) {\n seen.add(k)\n if (!Object.is(prevRec[k], nextRec[k])) return true\n }\n for (const k of Object.keys(nextRec)) {\n if (!seen.has(k)) return true\n }\n return false\n}\n\nfunction extractHydrationState(\n envelope: unknown,\n layerIndex: number,\n chainLength: number,\n def: AnyComponentDef,\n): unknown {\n // Legacy flat envelope — no layout chain at render time. Only valid\n // when the chain has a single layer (the page).\n const isLegacyFlat =\n envelope !== null &&\n typeof envelope === 'object' &&\n !('layouts' in (envelope as object)) &&\n !('page' in (envelope as object))\n\n if (isLegacyFlat) {\n if (chainLength !== 1) {\n throw new Error(\n `[llui/vike] Hydration envelope is in the legacy flat shape but the ` +\n `current render has ${chainLength} chain layers. The server must emit ` +\n `the chain-aware shape ({ layouts, page }) when rendering with a layout.`,\n )\n }\n return envelope\n }\n\n const chainEnvelope = envelope as\n | { layouts?: Array<{ name: string; state: unknown }>; page?: { name: string; state: unknown } }\n | undefined\n if (!chainEnvelope) {\n throw new Error(\n `[llui/vike] Hydration envelope is missing. Server-side onRenderHtml must ` +\n `populate window.__LLUI_STATE__ with the full chain before client hydration.`,\n )\n }\n\n const isPageLayer = layerIndex === chainLength - 1\n const layoutEntries = chainEnvelope.layouts ?? []\n const expected = isPageLayer ? chainEnvelope.page : layoutEntries[layerIndex]\n\n if (!expected) {\n throw new Error(\n `[llui/vike] Hydration envelope has no entry for chain layer ${layerIndex} ` +\n `(<${def.name}>). Server rendered ${layoutEntries.length} layouts + ${\n chainEnvelope.page ? 'a page' : 'no page'\n }, client expected ${chainLength} total entries.`,\n )\n }\n\n if (expected.name !== def.name) {\n throw new Error(\n `[llui/vike] Hydration mismatch at chain layer ${layerIndex}: server ` +\n `rendered <${expected.name}> but client is trying to hydrate <${def.name}>. ` +\n `This usually means the layout chain resolver returns different layouts ` +\n `on the server and the client for the same route.`,\n )\n }\n\n return expected.state\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"on-render-html.d.ts","sourceRoot":"","sources":["../src/on-render-html.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"on-render-html.d.ts","sourceRoot":"","sources":["../src/on-render-html.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAqB,MAAM,WAAW,CAAA;AAEnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAE9D,KAAK,WAAW,GAAG,aAAa,CAAC,eAAe,CAAC,CAAA;AAEjD;;;;;;;;;;GAUG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,eAAe,CAAA;IACrB,IAAI,CAAC,EAAE,mBAAmB,CAAA;IAC1B,cAAc,CAAC,EAAE,SAAS,OAAO,EAAE,CAAA;IACnC,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAA;IACZ,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAA;IACb,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAA;IACZ,yCAAyC;IACzC,WAAW,EAAE,WAAW,CAAA;CACzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;IAC3C,WAAW,EAAE;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,CAAA;CACpC;AAcD;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,mEAAmE;IACnE,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,MAAM,CAAA;IAE3C;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,EAAE,eAAe,GAAG,WAAW,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,KAAK,WAAW,CAAC,CAAA;CACrF;AAcD;;GAEG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAEtF;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,iBAAiB,GACzB,CAAC,WAAW,EAAE,WAAW,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAEzD;AAED;;;;GAIG;AACH,UAAU,iBAAiB;IACzB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IAChD,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAA;CACvC;AAiCD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,SAAS,OAAO,EAAE,GAC5B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,iBAAiB,CAAA;CAAE,CAgH/C"}
|
package/dist/on-render-html.js
CHANGED
|
@@ -162,7 +162,7 @@ export function _renderChain(chain, chainData) {
|
|
|
162
162
|
`layer(s) to mount and no slot to mount them into.`);
|
|
163
163
|
}
|
|
164
164
|
currentSlotAnchor = slot?.anchor ?? null;
|
|
165
|
-
currentSlotScope = slot?.
|
|
165
|
+
currentSlotScope = slot?.slotLifetime;
|
|
166
166
|
}
|
|
167
167
|
const html = serializeNodes(outermostNodes, allBindings);
|
|
168
168
|
if (envelopePage === null) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"on-render-html.js","sourceRoot":"","sources":["../src/on-render-html.ts"],"names":[],"mappings":"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","sourcesContent":["import { renderNodes, serializeNodes } from '@llui/dom'\nimport type { AnyComponentDef, Binding, Scope } from '@llui/dom'\nimport { _consumePendingSlot, _resetPendingSlot } from './page-slot.js'\nimport type { VikePageContextData } from './vike-namespace.js'\n\ntype LayoutChain = ReadonlyArray<AnyComponentDef>\n\n/**\n * Page context shape as seen by `@llui/vike`'s server hook. `Page` and\n * `data` are whichever `+Page.ts` and `+data.ts` Vike resolved for the\n * current route; `lluiLayoutData` is an optional array of per-layer\n * layout data matching the chain configured on `createOnRenderHtml`.\n *\n * `data` is derived from the global `Vike.PageContext` namespace so that\n * consumer-side augmentations (the Vike convention for typing data) flow\n * into this hook's callbacks without any cast. When the consumer hasn't\n * augmented the namespace, `data` falls back to `unknown`.\n */\nexport interface PageContext {\n Page: AnyComponentDef\n data?: VikePageContextData\n lluiLayoutData?: readonly unknown[]\n head?: string\n}\n\nexport interface DocumentContext {\n /** Rendered component HTML (layout + page composed if a Layout is configured) */\n html: string\n /** JSON-serialized hydration envelope (chain-aware when Layout is configured) */\n state: string\n /** Head content from pageContext.head (e.g. from +Head.ts) */\n head: string\n /** Full page context for custom logic */\n pageContext: PageContext\n}\n\nexport interface RenderHtmlResult {\n documentHtml: string | { _escaped: string }\n pageContext: { lluiState: unknown }\n}\n\nconst DEFAULT_DOCUMENT = ({ html, state, head }: DocumentContext): string => `<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n ${head}\n </head>\n <body>\n <div id=\"app\">${html}</div>\n <script>window.__LLUI_STATE__ = ${state}</script>\n </body>\n</html>`\n\n/**\n * Options for the customized `createOnRenderHtml` factory. Mirrors\n * `@llui/vike/client`'s `RenderClientOptions.Layout` — the same chain\n * shape is accepted for consistency between server and client render.\n */\nexport interface RenderHtmlOptions {\n /** Custom HTML document template. Defaults to a minimal layout. */\n document?: (ctx: DocumentContext) => string\n\n /**\n * Persistent layout chain. One of:\n *\n * - A single `ComponentDef` — becomes a one-layout chain.\n * - An array of `ComponentDef`s — outermost first, innermost last.\n * Every layer except the innermost must call `pageSlot()` in its view.\n * - A function that returns a chain from the current `pageContext` —\n * enables per-route chains (e.g. reading Vike's `urlPathname`).\n *\n * The server renders the full chain as one composed HTML tree. Client\n * hydration reads the matching envelope and reconstructs the chain\n * layer-by-layer.\n */\n Layout?: AnyComponentDef | LayoutChain | ((pageContext: PageContext) => LayoutChain)\n}\n\nfunction resolveLayoutChain(\n layoutOption: RenderHtmlOptions['Layout'],\n pageContext: PageContext,\n): LayoutChain {\n if (!layoutOption) return []\n if (typeof layoutOption === 'function') {\n return layoutOption(pageContext) ?? []\n }\n if (Array.isArray(layoutOption)) return layoutOption\n return [layoutOption as AnyComponentDef]\n}\n\n/**\n * Default onRenderHtml hook — no layout, minimal document template.\n */\nexport async function onRenderHtml(pageContext: PageContext): Promise<RenderHtmlResult> {\n return renderPage(pageContext, {})\n}\n\n/**\n * Factory to create a customized onRenderHtml hook.\n *\n * **Do not name your layout file `+Layout.ts`.** Vike reserves `+Layout`\n * for its own framework-adapter config (`vike-react` / `vike-vue` /\n * `vike-solid`) and will conflict with `@llui/vike`'s `Layout` option.\n * Name the file `Layout.ts`, `app-layout.ts`, or anywhere outside\n * `/pages` that Vike won't scan, and import it here by path.\n *\n * ```ts\n * // pages/+onRenderHtml.ts\n * import { createOnRenderHtml } from '@llui/vike/server'\n * import { AppLayout } from './Layout' // ← NOT './+Layout'\n *\n * export const onRenderHtml = createOnRenderHtml({\n * Layout: AppLayout,\n * document: ({ html, state, head }) => `<!DOCTYPE html>\n * <html><head>${head}<link rel=\"stylesheet\" href=\"/styles.css\" /></head>\n * <body><div id=\"app\">${html}</div>\n * <script>window.__LLUI_STATE__ = ${state}</script></body></html>`,\n * })\n * ```\n */\nexport function createOnRenderHtml(\n options: RenderHtmlOptions,\n): (pageContext: PageContext) => Promise<RenderHtmlResult> {\n return (pageContext) => renderPage(pageContext, options)\n}\n\n/**\n * Hydration envelope emitted into `window.__LLUI_STATE__`. Chain-aware\n * by default — every layer (layouts + page) is represented by its own\n * entry, keyed by component name so server/client mismatches fail loud.\n */\ninterface HydrationEnvelope {\n layouts: Array<{ name: string; state: unknown }>\n page: { name: string; state: unknown }\n}\n\nasync function renderPage(\n pageContext: PageContext,\n options: RenderHtmlOptions,\n): Promise<RenderHtmlResult> {\n // Lazy-import to keep jsdom out of the client bundle's dependency graph\n const { initSsrDom } = await import('@llui/dom/ssr')\n await initSsrDom()\n\n const layoutChain = resolveLayoutChain(options.Layout, pageContext)\n const layoutData = pageContext.lluiLayoutData ?? []\n\n // Full chain: every layout, then the page. Always at least one entry\n // (the page) since Vike's pageContext always has a Page.\n const chain: LayoutChain = [...layoutChain, pageContext.Page]\n const chainData: readonly unknown[] = [...layoutData, pageContext.data]\n\n const { html, envelope } = _renderChain(chain, chainData)\n\n const document = options.document ?? DEFAULT_DOCUMENT\n const head = pageContext.head ?? ''\n const state = JSON.stringify(envelope)\n const documentHtml = document({ html, state, head, pageContext })\n\n return {\n // Vike's dangerouslySkipEscape format — the document template is\n // trusted (authored by the developer, not user input)\n documentHtml: { _escaped: documentHtml },\n pageContext: { lluiState: envelope },\n }\n}\n\n/**\n * Render every layer of the chain into one composed DOM tree, then\n * serialize. At each non-innermost layer, consume the pending\n * `pageSlot()` registration and insert the next layer's nodes as\n * siblings after the anchor comment, bracketed by an end sentinel.\n * Scopes are threaded so inner layers inherit the outer layer's scope\n * tree for context lookups.\n *\n * @internal — exported for unit testing only (`_renderChain`).\n */\nexport function _renderChain(\n chain: LayoutChain,\n chainData: readonly unknown[],\n): { html: string; envelope: HydrationEnvelope } {\n if (chain.length === 0) {\n throw new Error('[llui/vike] renderChain called with empty chain')\n }\n\n // Defensive: ensure no stale slot leaks in from a prior failed render.\n _resetPendingSlot()\n\n // Accumulate bindings from every layer — serializeNodes needs the\n // full set so hydrate markers are correctly placed across the\n // composed tree.\n const allBindings: Binding[] = []\n const envelopeLayouts: HydrationEnvelope['layouts'] = []\n let envelopePage: HydrationEnvelope['page'] | null = null\n\n let outermostNodes: Node[] = []\n let currentSlotAnchor: Comment | null = null\n let currentSlotScope: Scope | undefined = undefined\n\n for (let i = 0; i < chain.length; i++) {\n const def = chain[i]!\n const layerData = chainData[i]\n const isInnermost = i === chain.length - 1\n\n // Resolve the initial state from the layer's own init() applied to\n // its data slice, same as client-side mountApp(). renderNodes takes\n // the state post-init so that the view sees the right fields.\n const [initialState] = def.init(layerData)\n\n // Cross from type-erased AnyComponentDef into the concrete signature\n // renderNodes expects. Same pattern as the client mount path —\n // renderNodes is generic but the runtime doesn't use the type params.\n const { nodes, inst } = renderNodes(\n def as unknown as Parameters<typeof renderNodes>[0],\n initialState,\n currentSlotScope,\n )\n allBindings.push(...inst.allBindings)\n\n if (i === 0) {\n outermostNodes = nodes\n } else {\n // Insert this layer's nodes as siblings immediately after the\n // anchor comment, then place an end sentinel after them.\n // The anchor is already attached to the composed DOM tree (it was\n // produced by the previous layer's pageSlot() call). We insert\n // before the anchor's next sibling so nodes land right after the\n // anchor, preserving any trailing siblings that may exist.\n if (!currentSlotAnchor) {\n // Unreachable given the error checks below, but defensive.\n throw new Error(`[llui/vike] internal: chain layer ${i} (<${def.name}>) has no slot anchor`)\n }\n const parentNode = currentSlotAnchor.parentNode\n if (!parentNode) {\n throw new Error(\n `[llui/vike] internal: slot anchor for layer ${i} (<${def.name}>) is detached`,\n )\n }\n // insertPoint is the node currently after the anchor; inserting\n // before it keeps all new nodes in order immediately after anchor.\n const insertPoint = currentSlotAnchor.nextSibling\n for (const node of nodes) {\n parentNode.insertBefore(node, insertPoint)\n }\n // Synthesize an end sentinel that brackets the owned region.\n const endSentinel = document.createComment('llui-mount-end')\n parentNode.insertBefore(endSentinel, insertPoint)\n }\n\n // Record this layer's state in the envelope. Page goes under\n // `page`, everything else under `layouts[]` ordered outer-to-inner.\n if (isInnermost) {\n envelopePage = { name: def.name, state: initialState }\n } else {\n envelopeLayouts.push({ name: def.name, state: initialState })\n }\n\n // Consume this layer's pending slot registration (if any). Every\n // non-innermost layer MUST declare a slot; the innermost MUST NOT.\n const slot = _consumePendingSlot()\n if (isInnermost && slot !== null) {\n throw new Error(\n `[llui/vike] <${def.name}> is the innermost component in the chain ` +\n `but called pageSlot(). pageSlot() only belongs in layout components.`,\n )\n }\n if (!isInnermost && slot === null) {\n throw new Error(\n `[llui/vike] <${def.name}> is a layout layer at depth ${i} but did not ` +\n `call pageSlot() in its view(). There are ${chain.length - i - 1} more ` +\n `layer(s) to mount and no slot to mount them into.`,\n )\n }\n\n currentSlotAnchor = slot?.anchor ?? null\n currentSlotScope = slot?.slotScope\n }\n\n const html = serializeNodes(outermostNodes, allBindings)\n\n if (envelopePage === null) {\n // Unreachable — chain is non-empty so the last iteration sets this.\n throw new Error('[llui/vike] internal: renderChain produced no page entry')\n }\n\n return {\n html,\n envelope: {\n layouts: envelopeLayouts,\n page: envelopePage,\n },\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"on-render-html.js","sourceRoot":"","sources":["../src/on-render-html.ts"],"names":[],"mappings":"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","sourcesContent":["import { renderNodes, serializeNodes } from '@llui/dom'\nimport type { AnyComponentDef, Binding, Lifetime } from '@llui/dom'\nimport { _consumePendingSlot, _resetPendingSlot } from './page-slot.js'\nimport type { VikePageContextData } from './vike-namespace.js'\n\ntype LayoutChain = ReadonlyArray<AnyComponentDef>\n\n/**\n * Page context shape as seen by `@llui/vike`'s server hook. `Page` and\n * `data` are whichever `+Page.ts` and `+data.ts` Vike resolved for the\n * current route; `lluiLayoutData` is an optional array of per-layer\n * layout data matching the chain configured on `createOnRenderHtml`.\n *\n * `data` is derived from the global `Vike.PageContext` namespace so that\n * consumer-side augmentations (the Vike convention for typing data) flow\n * into this hook's callbacks without any cast. When the consumer hasn't\n * augmented the namespace, `data` falls back to `unknown`.\n */\nexport interface PageContext {\n Page: AnyComponentDef\n data?: VikePageContextData\n lluiLayoutData?: readonly unknown[]\n head?: string\n}\n\nexport interface DocumentContext {\n /** Rendered component HTML (layout + page composed if a Layout is configured) */\n html: string\n /** JSON-serialized hydration envelope (chain-aware when Layout is configured) */\n state: string\n /** Head content from pageContext.head (e.g. from +Head.ts) */\n head: string\n /** Full page context for custom logic */\n pageContext: PageContext\n}\n\nexport interface RenderHtmlResult {\n documentHtml: string | { _escaped: string }\n pageContext: { lluiState: unknown }\n}\n\nconst DEFAULT_DOCUMENT = ({ html, state, head }: DocumentContext): string => `<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n ${head}\n </head>\n <body>\n <div id=\"app\">${html}</div>\n <script>window.__LLUI_STATE__ = ${state}</script>\n </body>\n</html>`\n\n/**\n * Options for the customized `createOnRenderHtml` factory. Mirrors\n * `@llui/vike/client`'s `RenderClientOptions.Layout` — the same chain\n * shape is accepted for consistency between server and client render.\n */\nexport interface RenderHtmlOptions {\n /** Custom HTML document template. Defaults to a minimal layout. */\n document?: (ctx: DocumentContext) => string\n\n /**\n * Persistent layout chain. One of:\n *\n * - A single `ComponentDef` — becomes a one-layout chain.\n * - An array of `ComponentDef`s — outermost first, innermost last.\n * Every layer except the innermost must call `pageSlot()` in its view.\n * - A function that returns a chain from the current `pageContext` —\n * enables per-route chains (e.g. reading Vike's `urlPathname`).\n *\n * The server renders the full chain as one composed HTML tree. Client\n * hydration reads the matching envelope and reconstructs the chain\n * layer-by-layer.\n */\n Layout?: AnyComponentDef | LayoutChain | ((pageContext: PageContext) => LayoutChain)\n}\n\nfunction resolveLayoutChain(\n layoutOption: RenderHtmlOptions['Layout'],\n pageContext: PageContext,\n): LayoutChain {\n if (!layoutOption) return []\n if (typeof layoutOption === 'function') {\n return layoutOption(pageContext) ?? []\n }\n if (Array.isArray(layoutOption)) return layoutOption\n return [layoutOption as AnyComponentDef]\n}\n\n/**\n * Default onRenderHtml hook — no layout, minimal document template.\n */\nexport async function onRenderHtml(pageContext: PageContext): Promise<RenderHtmlResult> {\n return renderPage(pageContext, {})\n}\n\n/**\n * Factory to create a customized onRenderHtml hook.\n *\n * **Do not name your layout file `+Layout.ts`.** Vike reserves `+Layout`\n * for its own framework-adapter config (`vike-react` / `vike-vue` /\n * `vike-solid`) and will conflict with `@llui/vike`'s `Layout` option.\n * Name the file `Layout.ts`, `app-layout.ts`, or anywhere outside\n * `/pages` that Vike won't scan, and import it here by path.\n *\n * ```ts\n * // pages/+onRenderHtml.ts\n * import { createOnRenderHtml } from '@llui/vike/server'\n * import { AppLayout } from './Layout' // ← NOT './+Layout'\n *\n * export const onRenderHtml = createOnRenderHtml({\n * Layout: AppLayout,\n * document: ({ html, state, head }) => `<!DOCTYPE html>\n * <html><head>${head}<link rel=\"stylesheet\" href=\"/styles.css\" /></head>\n * <body><div id=\"app\">${html}</div>\n * <script>window.__LLUI_STATE__ = ${state}</script></body></html>`,\n * })\n * ```\n */\nexport function createOnRenderHtml(\n options: RenderHtmlOptions,\n): (pageContext: PageContext) => Promise<RenderHtmlResult> {\n return (pageContext) => renderPage(pageContext, options)\n}\n\n/**\n * Hydration envelope emitted into `window.__LLUI_STATE__`. Chain-aware\n * by default — every layer (layouts + page) is represented by its own\n * entry, keyed by component name so server/client mismatches fail loud.\n */\ninterface HydrationEnvelope {\n layouts: Array<{ name: string; state: unknown }>\n page: { name: string; state: unknown }\n}\n\nasync function renderPage(\n pageContext: PageContext,\n options: RenderHtmlOptions,\n): Promise<RenderHtmlResult> {\n // Lazy-import to keep jsdom out of the client bundle's dependency graph\n const { initSsrDom } = await import('@llui/dom/ssr')\n await initSsrDom()\n\n const layoutChain = resolveLayoutChain(options.Layout, pageContext)\n const layoutData = pageContext.lluiLayoutData ?? []\n\n // Full chain: every layout, then the page. Always at least one entry\n // (the page) since Vike's pageContext always has a Page.\n const chain: LayoutChain = [...layoutChain, pageContext.Page]\n const chainData: readonly unknown[] = [...layoutData, pageContext.data]\n\n const { html, envelope } = _renderChain(chain, chainData)\n\n const document = options.document ?? DEFAULT_DOCUMENT\n const head = pageContext.head ?? ''\n const state = JSON.stringify(envelope)\n const documentHtml = document({ html, state, head, pageContext })\n\n return {\n // Vike's dangerouslySkipEscape format — the document template is\n // trusted (authored by the developer, not user input)\n documentHtml: { _escaped: documentHtml },\n pageContext: { lluiState: envelope },\n }\n}\n\n/**\n * Render every layer of the chain into one composed DOM tree, then\n * serialize. At each non-innermost layer, consume the pending\n * `pageSlot()` registration and insert the next layer's nodes as\n * siblings after the anchor comment, bracketed by an end sentinel.\n * Scopes are threaded so inner layers inherit the outer layer's scope\n * tree for context lookups.\n *\n * @internal — exported for unit testing only (`_renderChain`).\n */\nexport function _renderChain(\n chain: LayoutChain,\n chainData: readonly unknown[],\n): { html: string; envelope: HydrationEnvelope } {\n if (chain.length === 0) {\n throw new Error('[llui/vike] renderChain called with empty chain')\n }\n\n // Defensive: ensure no stale slot leaks in from a prior failed render.\n _resetPendingSlot()\n\n // Accumulate bindings from every layer — serializeNodes needs the\n // full set so hydrate markers are correctly placed across the\n // composed tree.\n const allBindings: Binding[] = []\n const envelopeLayouts: HydrationEnvelope['layouts'] = []\n let envelopePage: HydrationEnvelope['page'] | null = null\n\n let outermostNodes: Node[] = []\n let currentSlotAnchor: Comment | null = null\n let currentSlotScope: Lifetime | undefined = undefined\n\n for (let i = 0; i < chain.length; i++) {\n const def = chain[i]!\n const layerData = chainData[i]\n const isInnermost = i === chain.length - 1\n\n // Resolve the initial state from the layer's own init() applied to\n // its data slice, same as client-side mountApp(). renderNodes takes\n // the state post-init so that the view sees the right fields.\n const [initialState] = def.init(layerData)\n\n // Cross from type-erased AnyComponentDef into the concrete signature\n // renderNodes expects. Same pattern as the client mount path —\n // renderNodes is generic but the runtime doesn't use the type params.\n const { nodes, inst } = renderNodes(\n def as unknown as Parameters<typeof renderNodes>[0],\n initialState,\n currentSlotScope,\n )\n allBindings.push(...inst.allBindings)\n\n if (i === 0) {\n outermostNodes = nodes\n } else {\n // Insert this layer's nodes as siblings immediately after the\n // anchor comment, then place an end sentinel after them.\n // The anchor is already attached to the composed DOM tree (it was\n // produced by the previous layer's pageSlot() call). We insert\n // before the anchor's next sibling so nodes land right after the\n // anchor, preserving any trailing siblings that may exist.\n if (!currentSlotAnchor) {\n // Unreachable given the error checks below, but defensive.\n throw new Error(`[llui/vike] internal: chain layer ${i} (<${def.name}>) has no slot anchor`)\n }\n const parentNode = currentSlotAnchor.parentNode\n if (!parentNode) {\n throw new Error(\n `[llui/vike] internal: slot anchor for layer ${i} (<${def.name}>) is detached`,\n )\n }\n // insertPoint is the node currently after the anchor; inserting\n // before it keeps all new nodes in order immediately after anchor.\n const insertPoint = currentSlotAnchor.nextSibling\n for (const node of nodes) {\n parentNode.insertBefore(node, insertPoint)\n }\n // Synthesize an end sentinel that brackets the owned region.\n const endSentinel = document.createComment('llui-mount-end')\n parentNode.insertBefore(endSentinel, insertPoint)\n }\n\n // Record this layer's state in the envelope. Page goes under\n // `page`, everything else under `layouts[]` ordered outer-to-inner.\n if (isInnermost) {\n envelopePage = { name: def.name, state: initialState }\n } else {\n envelopeLayouts.push({ name: def.name, state: initialState })\n }\n\n // Consume this layer's pending slot registration (if any). Every\n // non-innermost layer MUST declare a slot; the innermost MUST NOT.\n const slot = _consumePendingSlot()\n if (isInnermost && slot !== null) {\n throw new Error(\n `[llui/vike] <${def.name}> is the innermost component in the chain ` +\n `but called pageSlot(). pageSlot() only belongs in layout components.`,\n )\n }\n if (!isInnermost && slot === null) {\n throw new Error(\n `[llui/vike] <${def.name}> is a layout layer at depth ${i} but did not ` +\n `call pageSlot() in its view(). There are ${chain.length - i - 1} more ` +\n `layer(s) to mount and no slot to mount them into.`,\n )\n }\n\n currentSlotAnchor = slot?.anchor ?? null\n currentSlotScope = slot?.slotLifetime\n }\n\n const html = serializeNodes(outermostNodes, allBindings)\n\n if (envelopePage === null) {\n // Unreachable — chain is non-empty so the last iteration sets this.\n throw new Error('[llui/vike] internal: renderChain produced no page entry')\n }\n\n return {\n html,\n envelope: {\n layouts: envelopeLayouts,\n page: envelopePage,\n },\n }\n}\n"]}
|
package/dist/page-slot.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Lifetime } from '@llui/dom';
|
|
2
2
|
/**
|
|
3
3
|
* Transient handoff between a layout layer's render pass and the vike
|
|
4
4
|
* adapter that's mounting the chain. `pageSlot()` populates this during
|
|
@@ -7,7 +7,7 @@ import type { Scope } from '@llui/dom';
|
|
|
7
7
|
* `pageSlot()` twice in the same layout is a bug the primitive reports.
|
|
8
8
|
*/
|
|
9
9
|
interface PendingSlot {
|
|
10
|
-
|
|
10
|
+
slotLifetime: Lifetime;
|
|
11
11
|
anchor: Comment;
|
|
12
12
|
}
|
|
13
13
|
/**
|
package/dist/page-slot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-slot.d.ts","sourceRoot":"","sources":["../src/page-slot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"page-slot.d.ts","sourceRoot":"","sources":["../src/page-slot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAQzC;;;;;;GAMG;AACH,UAAU,WAAW;IACnB,YAAY,EAAE,QAAQ,CAAA;IACtB,MAAM,EAAE,OAAO,CAAA;CAChB;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,QAAQ,IAAI,IAAI,EAAE,CAoBjC;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,WAAW,GAAG,IAAI,CAIxD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
|
package/dist/page-slot.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getRenderContext,
|
|
1
|
+
import { getRenderContext, createLifetime } from '@llui/dom/internal';
|
|
2
2
|
let pendingSlot = null;
|
|
3
3
|
/**
|
|
4
4
|
* Declare where a persistent layout renders its nested content — either
|
|
@@ -55,9 +55,9 @@ export function pageSlot() {
|
|
|
55
55
|
'the Vike routing tree and use context to share state between them.');
|
|
56
56
|
}
|
|
57
57
|
const ctx = getRenderContext('pageSlot');
|
|
58
|
-
const
|
|
58
|
+
const slotLifetime = createLifetime(ctx.rootLifetime);
|
|
59
59
|
const anchor = document.createComment('llui-page-slot');
|
|
60
|
-
pendingSlot = {
|
|
60
|
+
pendingSlot = { slotLifetime, anchor };
|
|
61
61
|
return [anchor];
|
|
62
62
|
}
|
|
63
63
|
/**
|
package/dist/page-slot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-slot.js","sourceRoot":"","sources":["../src/page-slot.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,
|
|
1
|
+
{"version":3,"file":"page-slot.js","sourceRoot":"","sources":["../src/page-slot.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAmBrE,IAAI,WAAW,GAAuB,IAAI,CAAA;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,UAAU,QAAQ;IACtB,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CACb,2DAA2D;YACzD,8EAA8E,CACjF,CAAA;IACH,CAAC;IACD,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,uEAAuE;YACrE,6EAA6E;YAC7E,2EAA2E;YAC3E,oEAAoE,CACvE,CAAA;IACH,CAAC;IACD,MAAM,GAAG,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAA;IACxC,MAAM,YAAY,GAAG,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;IACrD,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAA;IACvD,WAAW,GAAG,EAAE,YAAY,EAAE,MAAM,EAAE,CAAA;IACtC,OAAO,CAAC,MAAM,CAAC,CAAA;AACjB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,IAAI,GAAG,WAAW,CAAA;IACxB,WAAW,GAAG,IAAI,CAAA;IAClB,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB;IAC/B,WAAW,GAAG,IAAI,CAAA;AACpB,CAAC","sourcesContent":["import type { Lifetime } from '@llui/dom'\nimport { getRenderContext, createLifetime } from '@llui/dom/internal'\n\n// @llui/dom/internal is the adapter-layer surface: low-level primitives\n// (render-context access, scope creation, disposer registration) that\n// framework adapters like @llui/vike need to build structural primitives\n// on top of. Not part of the public app-author API.\n\n/**\n * Transient handoff between a layout layer's render pass and the vike\n * adapter that's mounting the chain. `pageSlot()` populates this during\n * the layout's view() call; `consumePendingSlot()` reads and clears it\n * immediately after the mount returns. One slot per mount pass — calling\n * `pageSlot()` twice in the same layout is a bug the primitive reports.\n */\ninterface PendingSlot {\n slotLifetime: Lifetime\n anchor: Comment\n}\n\nlet pendingSlot: PendingSlot | null = null\n\n/**\n * Declare where a persistent layout renders its nested content — either\n * a nested layout or the route's page component. The vike adapter's\n * client and server render paths walk the layout chain, and each layer's\n * `pageSlot()` call records the position where the next layer mounts.\n *\n * Emits a single `<!-- llui-page-slot -->` comment as an insertion\n * anchor. The nested layer's DOM lives as siblings of this comment\n * within the layout's own parent element; a synthesized end sentinel\n * (`<!-- llui-mount-end -->`) brackets the owned region.\n *\n * The slot is a real scope-tree node: the scope it creates is a child\n * of the current render scope, so contexts provided by the layout (via\n * `provide()`) above the slot are reachable from inside the nested\n * page. That's how patterns like a layout-owned toast dispatcher work —\n * the page does `useContext(ToastContext)` and walks up through the\n * slot into the layout's providers.\n *\n * Do NOT name the file `+Layout.ts` — Vike reserves the `+` prefix for\n * its own framework config conventions. Use `Layout.ts`, `app-layout.ts`,\n * or anywhere outside `/pages` that Vike won't scan.\n *\n * ```ts\n * // pages/Layout.ts ← not +Layout.ts\n * import { component, div, main, header } from '@llui/dom'\n * import { pageSlot } from '@llui/vike/client'\n *\n * export const AppLayout = component<LayoutState, LayoutMsg>({\n * name: 'AppLayout',\n * init: () => [{ ... }, []],\n * update: layoutUpdate,\n * view: (h) => [\n * div({ class: 'app-shell' }, [\n * header([...]),\n * main([pageSlot()]), // ← here the page goes (no wrapper div)\n * ]),\n * ],\n * })\n * ```\n *\n * Call exactly once per layout. Calling more than once in a single\n * view throws.\n */\nexport function pageSlot(): Node[] {\n if (typeof document === 'undefined') {\n throw new Error(\n '[llui/vike] pageSlot() called without a DOM environment. ' +\n 'Call from inside a component view() that runs during mount, hydrate, or SSR.',\n )\n }\n if (pendingSlot !== null) {\n throw new Error(\n '[llui/vike] pageSlot() was called more than once in the same layout. ' +\n 'A layout has exactly one nested-content slot — if you need two independent ' +\n 'regions that swap on navigation, build them as sibling nested layouts in ' +\n 'the Vike routing tree and use context to share state between them.',\n )\n }\n const ctx = getRenderContext('pageSlot')\n const slotLifetime = createLifetime(ctx.rootLifetime)\n const anchor = document.createComment('llui-page-slot')\n pendingSlot = { slotLifetime, anchor }\n return [anchor]\n}\n\n/**\n * @internal — vike adapter only. Read and clear the slot registered by\n * the most recent `pageSlot()` call. Returns null if the layer being\n * mounted didn't call `pageSlot()` (meaning it's the innermost layer\n * and owns no nested content).\n */\nexport function _consumePendingSlot(): PendingSlot | null {\n const slot = pendingSlot\n pendingSlot = null\n return slot\n}\n\n/**\n * @internal — vike adapter only. Reset the pending slot without reading\n * it. Used defensively in error paths to avoid leaking a pending slot\n * registration into a subsequent mount attempt.\n */\nexport function _resetPendingSlot(): void {\n pendingSlot = null\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llui/vike",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"exports": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"jsdom": "^26.1.0",
|
|
25
|
-
"@llui/dom": "0.0.
|
|
25
|
+
"@llui/dom": "0.0.21"
|
|
26
26
|
},
|
|
27
27
|
"description": "LLui Vike SSR adapter — onRenderHtml, onRenderClient hooks",
|
|
28
28
|
"keywords": [
|