@llui/vike 0.0.31 → 0.0.32

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 CHANGED
@@ -260,6 +260,54 @@ On the initial hydration render, `onLeave` and `onEnter` are both skipped — th
260
260
 
261
261
  **Scroll position is the host's problem.** Vike controls scroll-to-top behavior via `scrollToTop` in `+config.ts`. `@llui/vike` doesn't touch scroll — if you need custom scroll handling, configure it on the Vike side.
262
262
 
263
+ ## Cloudflare Workers
264
+
265
+ Two things differ from a Node deploy:
266
+
267
+ 1. **Pick `linkedomEnv`** for SSR — jsdom's transitive deps (`whatwg-url`, `tr46`, `punycode`) don't resolve under workerd. Pass it to `createOnRenderHtml`:
268
+
269
+ ```ts
270
+ // pages/+onRenderHtml.ts
271
+ import { createOnRenderHtml } from '@llui/vike/server'
272
+ import { linkedomEnv } from '@llui/dom/ssr/linkedom'
273
+
274
+ export const onRenderHtml = createOnRenderHtml({ domEnv: linkedomEnv })
275
+ ```
276
+
277
+ 2. **Guard the manual server-entry import in `worker.ts`.** `@brillout/vite-plugin-server-entry`'s auto-importer doesn't reach into workerd — its generated `loadServerEntry()` resolves a deeply-nested filesystem path that workerd refuses to bind. The fix is to import `dist/server/entry.mjs` yourself, but you **must** guard that import with `import.meta.env.PROD`:
278
+
279
+ ```ts
280
+ // worker.ts
281
+ if (import.meta.env.PROD) {
282
+ // @ts-expect-error — generated by `vite build`, absent in dev
283
+ await import('../dist/server/entry.mjs')
284
+ }
285
+
286
+ import { renderPage } from 'vike/server'
287
+
288
+ export default {
289
+ async fetch(req: Request): Promise<Response> {
290
+ const { httpResponse } = await renderPage({ urlOriginal: req.url })
291
+ if (!httpResponse) return new Response('Not Found', { status: 404 })
292
+ return new Response(httpResponse.body, {
293
+ status: httpResponse.statusCode,
294
+ headers: httpResponse.headers,
295
+ })
296
+ },
297
+ }
298
+ ```
299
+
300
+ Use `import.meta.env.PROD`, **not** `process.env.NODE_ENV` — workerd has no Node `process`, so the brillout README's `if (process.env.NODE_ENV === 'production')` snippet evaluates falsy at runtime and silently skips the import, leaving Vike with no registered hooks. Vite substitutes `import.meta.env.PROD` at build time (`true` in `vite build`, `false` in `vite dev`), so the guarded branch is correctly elided in dev.
301
+
302
+ **Why the guard matters.** Without it, this sequence breaks dev:
303
+
304
+ ```
305
+ pnpm build # writes dist/server/entry.mjs
306
+ pnpm dev # workerd resolves the literal '../dist/server/entry.mjs'
307
+ ```
308
+
309
+ `dist/server/entry.mjs` calls `setGlobalContext_prodBuildEntry()` at module top level. Vike correctly detects a prod initializer running in dev and throws `[vike@…][Bug] You stumbled upon a Vike bug`. If you've already hit this, `rm -rf dist && pnpm dev` recovers; adding the guard prevents recurrence.
310
+
263
311
  ## How It Works
264
312
 
265
313
  ### Server (`onRenderHtml`)
@@ -174,7 +174,7 @@ export declare function onRenderClient(pageContext: ClientPageContext): Promise<
174
174
  * // pages/+onRenderClient.ts
175
175
  * import { createOnRenderClient, fromTransition } from '@llui/vike/client'
176
176
  * import { routeTransition } from '@llui/transitions'
177
- * import { AppLayout } from './Layout' // ← NOT './+Layout'
177
+ * import { AppLayout } from './Layout.js' // ← NOT './+Layout'
178
178
  *
179
179
  * export const onRenderClient = createOnRenderClient({
180
180
  * Layout: AppLayout,
@@ -107,7 +107,7 @@ export async function onRenderClient(pageContext) {
107
107
  * // pages/+onRenderClient.ts
108
108
  * import { createOnRenderClient, fromTransition } from '@llui/vike/client'
109
109
  * import { routeTransition } from '@llui/transitions'
110
- * import { AppLayout } from './Layout' // ← NOT './+Layout'
110
+ * import { AppLayout } from './Layout.js' // ← NOT './+Layout'
111
111
  *
112
112
  * export const onRenderClient = createOnRenderClient({
113
113
  * Layout: AppLayout,
@@ -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, 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 layout chain to find the divergent suffix.\n //\n // The page (innermost entry, always stored at chainHandles[length-1])\n // is NEVER considered a surviving layer: every client navigation\n // disposes the current page and mounts fresh, even when the incoming\n // `pageContext.Page` happens to resolve to the same `ComponentDef`\n // reference as the outgoing one.\n //\n // Rationale: the persistent-layout feature is about keeping app\n // *chrome* alive across navigation — headers, sidebars, focus traps,\n // session state. The page, by definition, is the thing that changes\n // per route. Content-driven sites routinely share one `ComponentDef`\n // across many routes (e.g. a docs site where every `+Page.ts`\n // re-exports the same `DocPage` and per-route `+data.ts` supplies\n // the content). Treating same-def page navs as no-ops would freeze\n // those sites visually while the URL advances — a regression that\n // shipped in 0.0.26 and was reported against the llui.dev site.\n //\n // `propsMsg` is still honored for *layouts* that want to react to\n // nav-scoped data (pathname, session, breadcrumbs) without\n // remounting — see the loop below. The page is deliberately excluded\n // from that path because `init(data)` always re-runs for it.\n let firstMismatch = 0\n // `chainHandles` stores `[...layouts, page]`, so the layout prefix\n // length is `chainHandles.length - 1` (or 0 on first fresh mount, when\n // the chain is still empty). Bounding `minLen` by this length keeps\n // `firstMismatch` from ever advancing into the page slot.\n const prevLayoutLen = chainHandles.length === 0 ? 0 : chainHandles.length - 1\n const minLen = Math.min(prevLayoutLen, layoutChain.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 //\n // This loop is layouts-only by construction: `firstMismatch` is\n // bounded by `layoutChain.length` above, so indices [0, firstMismatch)\n // never reach the page slot.\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. `firstMismatch\n // === 0` covers two cases: no layouts configured (page-only chain,\n // every nav is a root swap) and all layouts diverging (full re-render).\n const isRootSwap = firstMismatch === 0\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
+ {"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.js' // ← 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 layout chain to find the divergent suffix.\n //\n // The page (innermost entry, always stored at chainHandles[length-1])\n // is NEVER considered a surviving layer: every client navigation\n // disposes the current page and mounts fresh, even when the incoming\n // `pageContext.Page` happens to resolve to the same `ComponentDef`\n // reference as the outgoing one.\n //\n // Rationale: the persistent-layout feature is about keeping app\n // *chrome* alive across navigation — headers, sidebars, focus traps,\n // session state. The page, by definition, is the thing that changes\n // per route. Content-driven sites routinely share one `ComponentDef`\n // across many routes (e.g. a docs site where every `+Page.ts`\n // re-exports the same `DocPage` and per-route `+data.ts` supplies\n // the content). Treating same-def page navs as no-ops would freeze\n // those sites visually while the URL advances — a regression that\n // shipped in 0.0.26 and was reported against the llui.dev site.\n //\n // `propsMsg` is still honored for *layouts* that want to react to\n // nav-scoped data (pathname, session, breadcrumbs) without\n // remounting — see the loop below. The page is deliberately excluded\n // from that path because `init(data)` always re-runs for it.\n let firstMismatch = 0\n // `chainHandles` stores `[...layouts, page]`, so the layout prefix\n // length is `chainHandles.length - 1` (or 0 on first fresh mount, when\n // the chain is still empty). Bounding `minLen` by this length keeps\n // `firstMismatch` from ever advancing into the page slot.\n const prevLayoutLen = chainHandles.length === 0 ? 0 : chainHandles.length - 1\n const minLen = Math.min(prevLayoutLen, layoutChain.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 //\n // This loop is layouts-only by construction: `firstMismatch` is\n // bounded by `layoutChain.length` above, so indices [0, firstMismatch)\n // never reach the page slot.\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. `firstMismatch\n // === 0` covers two cases: no layouts configured (page-only chain,\n // every nav is a root swap) and all layouts diverging (full re-render).\n const isRootSwap = firstMismatch === 0\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"]}
@@ -98,7 +98,7 @@ export declare function onRenderHtml(pageContext: PageContext): Promise<RenderHt
98
98
  * ```ts
99
99
  * // pages/+onRenderHtml.ts
100
100
  * import { createOnRenderHtml } from '@llui/vike/server'
101
- * import { AppLayout } from './Layout' // ← NOT './+Layout'
101
+ * import { AppLayout } from './Layout.js' // ← NOT './+Layout'
102
102
  *
103
103
  * export const onRenderHtml = createOnRenderHtml({
104
104
  * Layout: AppLayout,
@@ -46,7 +46,7 @@ export async function onRenderHtml(pageContext) {
46
46
  * ```ts
47
47
  * // pages/+onRenderHtml.ts
48
48
  * import { createOnRenderHtml } from '@llui/vike/server'
49
- * import { AppLayout } from './Layout' // ← NOT './+Layout'
49
+ * import { AppLayout } from './Layout.js' // ← NOT './+Layout'
50
50
  *
51
51
  * export const onRenderHtml = createOnRenderHtml({
52
52
  * Layout: AppLayout,
@@ -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, Lifetime, DomEnv } 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 /**\n * Factory that returns the `DomEnv` backing SSR render. Call with\n * either `jsdomEnv` (from `@llui/dom/ssr/jsdom`) or `linkedomEnv`\n * (from `@llui/dom/ssr/linkedom`). The factory is invoked once per\n * page render, so each request gets a fresh DOM — safe under\n * concurrency, no `globalThis` mutation.\n *\n * On Cloudflare Workers use `linkedomEnv` — jsdom's transitive deps\n * (whatwg-url, tr46, punycode) don't resolve under workerd.\n *\n * @example\n * ```ts\n * import { jsdomEnv } from '@llui/dom/ssr/jsdom'\n * createOnRenderHtml({ Layout: MyLayout, domEnv: jsdomEnv })\n * ```\n */\n domEnv: () => DomEnv | Promise<DomEnv>\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 * jsdom-backed DOM env. For Cloudflare Workers (no jsdom support) or\n * a custom layout / document, use `createOnRenderHtml({ domEnv, … })`\n * with `linkedomEnv` from `@llui/dom/ssr/linkedom`.\n *\n * The lazy import below keeps jsdom out of the client bundle —\n * Rollup's graph walker only pulls it when this server hook executes.\n */\nexport async function onRenderHtml(pageContext: PageContext): Promise<RenderHtmlResult> {\n const { jsdomEnv } = await import('@llui/dom/ssr/jsdom')\n return renderPage(pageContext, { domEnv: jsdomEnv })\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 const env = await options.domEnv()\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, env)\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 env: DomEnv,\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 env,\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 = env.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"]}
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, DomEnv } 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 /**\n * Factory that returns the `DomEnv` backing SSR render. Call with\n * either `jsdomEnv` (from `@llui/dom/ssr/jsdom`) or `linkedomEnv`\n * (from `@llui/dom/ssr/linkedom`). The factory is invoked once per\n * page render, so each request gets a fresh DOM — safe under\n * concurrency, no `globalThis` mutation.\n *\n * On Cloudflare Workers use `linkedomEnv` — jsdom's transitive deps\n * (whatwg-url, tr46, punycode) don't resolve under workerd.\n *\n * @example\n * ```ts\n * import { jsdomEnv } from '@llui/dom/ssr/jsdom'\n * createOnRenderHtml({ Layout: MyLayout, domEnv: jsdomEnv })\n * ```\n */\n domEnv: () => DomEnv | Promise<DomEnv>\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 * jsdom-backed DOM env. For Cloudflare Workers (no jsdom support) or\n * a custom layout / document, use `createOnRenderHtml({ domEnv, … })`\n * with `linkedomEnv` from `@llui/dom/ssr/linkedom`.\n *\n * The lazy import below keeps jsdom out of the client bundle —\n * Rollup's graph walker only pulls it when this server hook executes.\n */\nexport async function onRenderHtml(pageContext: PageContext): Promise<RenderHtmlResult> {\n const { jsdomEnv } = await import('@llui/dom/ssr/jsdom')\n return renderPage(pageContext, { domEnv: jsdomEnv })\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.js' // ← 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 const env = await options.domEnv()\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, env)\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 env: DomEnv,\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 env,\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 = env.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llui/vike",
3
- "version": "0.0.31",
3
+ "version": "0.0.32",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {
@@ -20,10 +20,8 @@
20
20
  "files": [
21
21
  "dist"
22
22
  ],
23
- "dependencies": {
24
- "@llui/dom": "0.0.30"
25
- },
26
23
  "peerDependencies": {
24
+ "@llui/dom": "^0.0.30",
27
25
  "jsdom": "*"
28
26
  },
29
27
  "peerDependenciesMeta": {
@@ -31,6 +29,9 @@
31
29
  "optional": true
32
30
  }
33
31
  },
32
+ "devDependencies": {
33
+ "@llui/dom": "0.0.30"
34
+ },
34
35
  "description": "LLui Vike SSR adapter — onRenderHtml, onRenderClient hooks",
35
36
  "keywords": [
36
37
  "llui",