@llui/vike 0.4.10 → 0.5.0
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/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/on-render-client.d.ts +80 -189
- package/dist/on-render-client.d.ts.map +1 -1
- package/dist/on-render-client.js +101 -223
- package/dist/on-render-client.js.map +1 -1
- package/dist/on-render-html.d.ts +36 -8
- package/dist/on-render-html.d.ts.map +1 -1
- package/dist/on-render-html.js +36 -31
- package/dist/on-render-html.js.map +1 -1
- package/dist/page-slot.d.ts +22 -12
- package/dist/page-slot.d.ts.map +1 -1
- package/dist/page-slot.js +20 -14
- package/dist/page-slot.js.map +1 -1
- package/package.json +3 -3
package/dist/on-render-client.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mountSignalComponent, hydrateSignalApp } from '@llui/dom';
|
|
2
2
|
import { _consumePendingSlot, _resetPendingSlot } from './page-slot.js';
|
|
3
3
|
// Re-exported so `@llui/vike/client` is a one-stop-shop for everything
|
|
4
|
-
// a pages/+onRenderClient.ts /
|
|
4
|
+
// a pages/+onRenderClient.ts / Layout.ts file needs.
|
|
5
5
|
export { pageSlot } from './page-slot.js';
|
|
6
6
|
/**
|
|
7
7
|
* Resolves the layout chain for a given pageContext. A single layout
|
|
8
8
|
* becomes a one-element chain; a function resolver gives callers full
|
|
9
|
-
* control to return different chains for different routes
|
|
10
|
-
* layouts keyed on Vike's `pageContext.urlPathname`).
|
|
9
|
+
* control to return different chains for different routes.
|
|
11
10
|
*/
|
|
12
11
|
function resolveLayoutChain(layoutOption, pageContext) {
|
|
13
12
|
if (!layoutOption)
|
|
@@ -19,11 +18,14 @@ function resolveLayoutChain(layoutOption, pageContext) {
|
|
|
19
18
|
return layoutOption;
|
|
20
19
|
return [layoutOption];
|
|
21
20
|
}
|
|
21
|
+
/** Resolve a layer's seed state — a present data slice IS the seed state
|
|
22
|
+
* (signal init() takes no data); an absent slice falls back to init(). */
|
|
23
|
+
function seedFor(data) {
|
|
24
|
+
return data === undefined ? undefined : data;
|
|
25
|
+
}
|
|
22
26
|
/**
|
|
23
|
-
* Adapt a `TransitionOptions` object
|
|
24
|
-
*
|
|
25
|
-
* / `slide`) into the `onLeave` / `onEnter` pair expected by
|
|
26
|
-
* `createOnRenderClient`.
|
|
27
|
+
* Adapt a `TransitionOptions` object into the `onLeave` / `onEnter` pair
|
|
28
|
+
* expected by `createOnRenderClient`.
|
|
27
29
|
*
|
|
28
30
|
* ```ts
|
|
29
31
|
* import { createOnRenderClient, fromTransition } from '@llui/vike/client'
|
|
@@ -36,9 +38,8 @@ function resolveLayoutChain(layoutOption, pageContext) {
|
|
|
36
38
|
* ```
|
|
37
39
|
*
|
|
38
40
|
* The transition operates on the slot element — in a no-layout setup,
|
|
39
|
-
* the root container; in a layout setup, the innermost surviving
|
|
40
|
-
*
|
|
41
|
-
* outgoing page content, then the new page fades in.
|
|
41
|
+
* the root container; in a layout setup, the innermost surviving layer's
|
|
42
|
+
* `pageSlot()` element.
|
|
42
43
|
*/
|
|
43
44
|
export function fromTransition(t) {
|
|
44
45
|
return {
|
|
@@ -58,33 +59,21 @@ export function fromTransition(t) {
|
|
|
58
59
|
};
|
|
59
60
|
}
|
|
60
61
|
/**
|
|
61
|
-
* Live chain of mounted layers — module-level singleton.
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* stay live, divergent suffix layers dispose, new layers append.
|
|
62
|
+
* Live chain of mounted layers — module-level singleton. Vike runs one
|
|
63
|
+
* client-side adapter per browser tab; within one tab a single
|
|
64
|
+
* `chainHandles` array holds the handle for every active layer, indexed
|
|
65
|
+
* `[outermostLayout, ..., innerLayout, page]`. It mutates in place across
|
|
66
|
+
* navigations: shared layout layers stay live, divergent suffix layers
|
|
67
|
+
* dispose, new layers append.
|
|
68
68
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* imports `@llui/vike/client` and tries to render multiple requests
|
|
73
|
-
* concurrently — every request would clobber the same array. That
|
|
74
|
-
* usage isn't supported today (the client adapter assumes a browser
|
|
75
|
-
* runtime; the SSR side lives in `@llui/vike/server`'s `_renderChain`
|
|
76
|
-
* which keeps state per-call), but the constraint should be made
|
|
77
|
-
* explicit if the adapter ever grows a Node SSR consumer. If you're
|
|
78
|
-
* here to add such a consumer: convert `chainHandles` and the
|
|
79
|
-
* pending-slot register to per-call locals threaded through the
|
|
80
|
-
* adapter API instead of module state, and audit `getLayoutChain`
|
|
81
|
-
* and `_resetChainForTest` for the same change.
|
|
69
|
+
* Module-level scope is correct for the browser (one consumer per page
|
|
70
|
+
* load); a multi-tenant Node SSR worker importing the client adapter
|
|
71
|
+
* would clobber it — that usage isn't supported.
|
|
82
72
|
*/
|
|
83
73
|
let chainHandles = [];
|
|
84
74
|
/**
|
|
85
|
-
* @internal — test helper. Disposes every layer in the current chain
|
|
86
|
-
*
|
|
87
|
-
* mount. Not part of the public API; subject to change without notice.
|
|
75
|
+
* @internal — test helper. Disposes every layer in the current chain and
|
|
76
|
+
* clears the module state so subsequent calls behave as a first mount.
|
|
88
77
|
*/
|
|
89
78
|
export function _resetChainForTest() {
|
|
90
79
|
// Dispose innermost-first to match the normal teardown path.
|
|
@@ -104,38 +93,19 @@ export function _resetCurrentHandleForTest() {
|
|
|
104
93
|
}
|
|
105
94
|
/**
|
|
106
95
|
* Default onRenderClient hook — no layout, no animation hooks. Hydrates
|
|
107
|
-
* on first load, mounts fresh on subsequent navs.
|
|
108
|
-
* for the customizable factory form.
|
|
96
|
+
* on first load, mounts fresh on subsequent navs.
|
|
109
97
|
*/
|
|
110
98
|
export async function onRenderClient(pageContext) {
|
|
111
99
|
await renderClient(pageContext, {});
|
|
112
100
|
}
|
|
113
101
|
/**
|
|
114
|
-
* Factory to create a customized onRenderClient hook. See
|
|
115
|
-
* for the full option surface
|
|
116
|
-
* layouts, route transitions, and lifecycle hooks.
|
|
102
|
+
* Factory to create a customized onRenderClient hook. See
|
|
103
|
+
* `RenderClientOptions` for the full option surface.
|
|
117
104
|
*
|
|
118
105
|
* **Do not name your layout file `+Layout.ts`.** Vike reserves the `+`
|
|
119
|
-
* prefix for its own framework config conventions
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* adapter in that sense — it's a render adapter, and `createOnRenderClient`
|
|
123
|
-
* consumes the layout component directly via the `Layout` option. Name
|
|
124
|
-
* the file `Layout.ts`, `app-layout.ts`, or anywhere outside `/pages`
|
|
125
|
-
* that Vike won't scan, and import it here by path.
|
|
126
|
-
*
|
|
127
|
-
* ```ts
|
|
128
|
-
* // pages/+onRenderClient.ts
|
|
129
|
-
* import { createOnRenderClient, fromTransition } from '@llui/vike/client'
|
|
130
|
-
* import { routeTransition } from '@llui/transitions'
|
|
131
|
-
* import { AppLayout } from './Layout.js' // ← NOT './+Layout'
|
|
132
|
-
*
|
|
133
|
-
* export const onRenderClient = createOnRenderClient({
|
|
134
|
-
* Layout: AppLayout,
|
|
135
|
-
* ...fromTransition(routeTransition({ duration: 200 })),
|
|
136
|
-
* onMount: () => console.log('page rendered'),
|
|
137
|
-
* })
|
|
138
|
-
* ```
|
|
106
|
+
* prefix for its own framework config conventions. Name the file
|
|
107
|
+
* `Layout.ts`, `app-layout.ts`, or anywhere outside `/pages` that Vike
|
|
108
|
+
* won't scan, and import it here by path.
|
|
139
109
|
*/
|
|
140
110
|
export function createOnRenderClient(options) {
|
|
141
111
|
return (pageContext) => renderClient(pageContext, options);
|
|
@@ -147,16 +117,14 @@ async function renderClient(pageContext, options) {
|
|
|
147
117
|
throw new Error(`@llui/vike: container "${selector}" not found in DOM`);
|
|
148
118
|
}
|
|
149
119
|
const rootEl = container;
|
|
150
|
-
// Resolve the chain for this render. The page
|
|
151
|
-
// the innermost entry, regardless of layout configuration.
|
|
120
|
+
// Resolve the chain for this render. The page is always the innermost entry.
|
|
152
121
|
const layoutChain = resolveLayoutChain(options.Layout, pageContext);
|
|
153
122
|
const layoutData = pageContext.lluiLayoutData ?? [];
|
|
154
123
|
const newChain = [...layoutChain, pageContext.Page];
|
|
155
124
|
const newChainData = [...layoutData, pageContext.data];
|
|
156
125
|
if (pageContext.isHydration) {
|
|
157
|
-
// First load —
|
|
158
|
-
|
|
159
|
-
await mountOrHydrateChain(newChain, newChainData, rootEl, {
|
|
126
|
+
// First load — hydrate every layer against server-rendered HTML.
|
|
127
|
+
mountChainSuffix(newChain, newChainData, 0, rootEl, undefined, {
|
|
160
128
|
mode: 'hydrate',
|
|
161
129
|
serverStateEnvelope: window.__LLUI_STATE__,
|
|
162
130
|
runInitEffectsOnHydrate: options.runInitEffectsOnHydrate,
|
|
@@ -166,53 +134,22 @@ async function renderClient(pageContext, options) {
|
|
|
166
134
|
}
|
|
167
135
|
// Subsequent nav — diff the layout chain to find the divergent suffix.
|
|
168
136
|
//
|
|
169
|
-
// The page (innermost entry
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
// Rationale: the persistent-layout feature is about keeping app
|
|
176
|
-
// *chrome* alive across navigation — headers, sidebars, focus traps,
|
|
177
|
-
// session state. The page, by definition, is the thing that changes
|
|
178
|
-
// per route. Content-driven sites routinely share one `ComponentDef`
|
|
179
|
-
// across many routes (e.g. a docs site where every `+Page.ts`
|
|
180
|
-
// re-exports the same `DocPage` and per-route `+data.ts` supplies
|
|
181
|
-
// the content). Treating same-def page navs as no-ops would freeze
|
|
182
|
-
// those sites visually while the URL advances — a regression that
|
|
183
|
-
// shipped in 0.0.26 and was reported against the llui.dev site.
|
|
184
|
-
//
|
|
185
|
-
// The user-supplied `onLayerDataChange` callback fires for *layouts*
|
|
186
|
-
// that want to react to nav-scoped data (pathname, session,
|
|
187
|
-
// breadcrumbs) without remounting — see the loop below. The page is
|
|
188
|
-
// deliberately excluded from that path because `init(data)` always
|
|
189
|
-
// re-runs for it.
|
|
137
|
+
// The page (innermost entry) is NEVER a surviving layer: every client
|
|
138
|
+
// navigation disposes the current page and mounts fresh, even when the
|
|
139
|
+
// incoming Page resolves to the same def reference. The persistent-layout
|
|
140
|
+
// feature is about keeping app chrome alive; the page is the thing that
|
|
141
|
+
// changes per route.
|
|
190
142
|
let firstMismatch = 0;
|
|
191
|
-
// `chainHandles` stores `[...layouts, page]`, so the layout prefix
|
|
192
|
-
// length is `chainHandles.length - 1` (or 0 on first fresh mount, when
|
|
193
|
-
// the chain is still empty). Bounding `minLen` by this length keeps
|
|
194
|
-
// `firstMismatch` from ever advancing into the page slot.
|
|
195
143
|
const prevLayoutLen = chainHandles.length === 0 ? 0 : chainHandles.length - 1;
|
|
196
144
|
const minLen = Math.min(prevLayoutLen, layoutChain.length);
|
|
197
145
|
while (firstMismatch < minLen && chainHandles[firstMismatch].def === newChain[firstMismatch]) {
|
|
198
146
|
firstMismatch++;
|
|
199
147
|
}
|
|
200
|
-
// Push fresh data into surviving layers (
|
|
201
|
-
//
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
// The user-supplied `onLayerDataChange` callback receives the layer
|
|
206
|
-
// def, its AppHandle, the new data slice, and the previously-seen
|
|
207
|
-
// data slice. The user typically dispatches a state-update message
|
|
208
|
-
// through `handle.send`. Layers whose data slice is unchanged
|
|
209
|
-
// (shallow-key Object.is on records, whole-value Object.is for
|
|
210
|
-
// primitives) are skipped without calling the hook.
|
|
211
|
-
//
|
|
212
|
-
// This loop is layouts-only by construction: `firstMismatch` is
|
|
213
|
-
// bounded by `layoutChain.length` above, so indices [0, firstMismatch)
|
|
214
|
-
// never reach the page slot. If `onLayerDataChange` is undefined,
|
|
215
|
-
// surviving layers retain their existing state — opt-in.
|
|
148
|
+
// Push fresh data into surviving layers (the shared prefix). The
|
|
149
|
+
// user-supplied `onLayerDataChange` receives the layer def, its handle,
|
|
150
|
+
// and the new + previous data slices; it typically dispatches a message
|
|
151
|
+
// through `handle.send`. Unchanged slices are skipped. Layouts-only by
|
|
152
|
+
// construction (firstMismatch is bounded by layoutChain.length).
|
|
216
153
|
for (let i = 0; i < firstMismatch; i++) {
|
|
217
154
|
const entry = chainHandles[i];
|
|
218
155
|
const newData = newChainData[i];
|
|
@@ -221,23 +158,14 @@ async function renderClient(pageContext, options) {
|
|
|
221
158
|
const prevData = entry.data;
|
|
222
159
|
entry.data = newData;
|
|
223
160
|
if (options.onLayerDataChange) {
|
|
224
|
-
options.onLayerDataChange({
|
|
225
|
-
def: entry.def,
|
|
226
|
-
handle: entry.handle,
|
|
227
|
-
newData,
|
|
228
|
-
prevData,
|
|
229
|
-
});
|
|
161
|
+
options.onLayerDataChange({ def: entry.def, handle: entry.handle, newData, prevData });
|
|
230
162
|
}
|
|
231
163
|
}
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
// hydrateApp on rootEl. For a deeper swap, the mount target is an
|
|
235
|
-
// anchor comment owned by the surviving layer's slot. `firstMismatch
|
|
236
|
-
// === 0` covers two cases: no layouts configured (page-only chain,
|
|
237
|
-
// every nav is a root swap) and all layouts diverging (full re-render).
|
|
164
|
+
// `firstMismatch === 0` means a root swap (no layouts, or all diverging);
|
|
165
|
+
// otherwise the surviving layer at firstMismatch-1 owns the slot we mount into.
|
|
238
166
|
const isRootSwap = firstMismatch === 0;
|
|
239
|
-
// onLeave runs BEFORE any teardown
|
|
240
|
-
//
|
|
167
|
+
// onLeave runs BEFORE any teardown — outgoing DOM still mounted. Skip on the
|
|
168
|
+
// very first mount (no outgoing page to leave).
|
|
241
169
|
const isFirstMount = chainHandles.length === 0;
|
|
242
170
|
if (options.onLeave && !isFirstMount) {
|
|
243
171
|
const leaveTargetEl = isRootSwap
|
|
@@ -245,23 +173,20 @@ async function renderClient(pageContext, options) {
|
|
|
245
173
|
: (chainHandles[firstMismatch - 1].slotAnchor?.parentElement ?? rootEl);
|
|
246
174
|
await options.onLeave(leaveTargetEl);
|
|
247
175
|
}
|
|
248
|
-
// Dispose the divergent suffix, innermost first. Each handle.dispose()
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
// untouched because their scopes live above the disposal roots.
|
|
253
|
-
// For anchor-based mounts, dispose() also removes the owned DOM region
|
|
254
|
-
// between the anchor and end sentinel — no additional textContent clear needed.
|
|
176
|
+
// Dispose the divergent suffix, innermost first. Each handle.dispose() runs
|
|
177
|
+
// the layer's teardowns; anchor-mounted layers also remove their owned DOM
|
|
178
|
+
// region (anchor → end sentinel). For a root swap the container is cleared
|
|
179
|
+
// explicitly below since a container mount's dispose doesn't remove DOM.
|
|
255
180
|
for (let i = chainHandles.length - 1; i >= firstMismatch; i--) {
|
|
256
181
|
chainHandles[i].handle.dispose();
|
|
257
182
|
}
|
|
258
183
|
chainHandles = chainHandles.slice(0, firstMismatch);
|
|
184
|
+
if (isRootSwap && !isFirstMount)
|
|
185
|
+
rootEl.replaceChildren();
|
|
259
186
|
// Mount the new suffix starting at firstMismatch.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const mountTargetArg = firstMismatch === 0 ? rootEl : chainHandles[firstMismatch - 1].slotAnchor;
|
|
264
|
-
mountChainSuffix(newChain, newChainData, firstMismatch, mountTargetArg, parentLifetime, {
|
|
187
|
+
const mountTarget = firstMismatch === 0 ? rootEl : chainHandles[firstMismatch - 1].slotAnchor;
|
|
188
|
+
const mountContexts = firstMismatch === 0 ? undefined : (chainHandles[firstMismatch - 1].slotContexts ?? undefined);
|
|
189
|
+
mountChainSuffix(newChain, newChainData, firstMismatch, mountTarget, mountContexts, {
|
|
265
190
|
mode: 'mount',
|
|
266
191
|
});
|
|
267
192
|
// onEnter fires after the new suffix is in place. Fire-and-forget.
|
|
@@ -274,18 +199,8 @@ async function renderClient(pageContext, options) {
|
|
|
274
199
|
options.onMount?.(snapshotLayoutChain());
|
|
275
200
|
}
|
|
276
201
|
/**
|
|
277
|
-
* Public read of the current layout chain
|
|
278
|
-
* `
|
|
279
|
-
* before the first mount; updates after every navigation.
|
|
280
|
-
*
|
|
281
|
-
* Returns a fresh array each call, but the AppHandle references are
|
|
282
|
-
* shared with the live chain — calling `.send()` / `.dispose()` /
|
|
283
|
-
* `.subscribe()` operates on the same instance the framework manages.
|
|
284
|
-
*
|
|
285
|
-
* Prefer the `onMount(chain)` callback for lifecycle-coupled wiring
|
|
286
|
-
* (the framework guarantees the chain is fully populated when it
|
|
287
|
-
* fires); use this getter for ad-hoc reads where the caller can't
|
|
288
|
-
* thread state through `onMount`.
|
|
202
|
+
* Public read of the current layout chain — live `LayerHandle`s for
|
|
203
|
+
* `[...layouts, page]`, outermost first. Empty before the first mount.
|
|
289
204
|
*/
|
|
290
205
|
export function getLayoutChain() {
|
|
291
206
|
return snapshotLayoutChain();
|
|
@@ -294,81 +209,57 @@ function snapshotLayoutChain() {
|
|
|
294
209
|
return chainHandles.map((entry) => entry.handle);
|
|
295
210
|
}
|
|
296
211
|
/**
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
* next layer's
|
|
300
|
-
*/
|
|
301
|
-
async function mountOrHydrateChain(chain, chainData, rootEl, opts) {
|
|
302
|
-
mountChainSuffix(chain, chainData, 0, rootEl, undefined, opts);
|
|
303
|
-
}
|
|
304
|
-
/**
|
|
305
|
-
* Mount (or hydrate) `chain[startAt..end]` into `initialTarget`, with
|
|
306
|
-
* the initial layer's rootLifetime parented at `initialParentLifetime`.
|
|
307
|
-
* Threads slot → next-target → next-parentLifetime through the chain.
|
|
212
|
+
* Mount (or hydrate) `chain[startAt..end]` into `initialTarget`, replaying
|
|
213
|
+
* `initialContexts` into the first layer's build. Threads each layer's slot
|
|
214
|
+
* (anchor + captured contexts) into the next layer's target + contexts.
|
|
308
215
|
*
|
|
309
|
-
* `initialTarget` is `HTMLElement` for the outermost layer (container
|
|
310
|
-
*
|
|
311
|
-
*
|
|
216
|
+
* `initialTarget` is an `HTMLElement` for the outermost layer (container mount/
|
|
217
|
+
* hydrate) and a `Comment` for inner layers mounting relative to a `pageSlot()`
|
|
218
|
+
* anchor.
|
|
312
219
|
*
|
|
313
|
-
* Fails loudly if a non-innermost layer forgot to call `pageSlot()`,
|
|
314
|
-
*
|
|
220
|
+
* Fails loudly if a non-innermost layer forgot to call `pageSlot()`, or if the
|
|
221
|
+
* innermost layer called `pageSlot()` unnecessarily.
|
|
315
222
|
*
|
|
316
|
-
* @internal — test helper. Exported so `client-page-slot.test.ts` can
|
|
317
|
-
*
|
|
318
|
-
* Not part of the public API.
|
|
223
|
+
* @internal — test helper. Exported so `client-page-slot.test.ts` can exercise
|
|
224
|
+
* anchor-mount/dispose contracts directly with hand-built DOM.
|
|
319
225
|
*/
|
|
320
|
-
export function _mountChainSuffix(chain, chainData, startAt, initialTarget,
|
|
226
|
+
export function _mountChainSuffix(chain, chainData, startAt, initialTarget, initialContexts, opts) {
|
|
321
227
|
let mountTarget = initialTarget;
|
|
322
|
-
let
|
|
228
|
+
let contexts = initialContexts;
|
|
323
229
|
for (let i = startAt; i < chain.length; i++) {
|
|
324
230
|
const def = chain[i];
|
|
325
231
|
const layerData = chainData[i];
|
|
326
232
|
const isInnermost = i === chain.length - 1;
|
|
327
233
|
// Defensive: clear any stale slot from a prior failed mount.
|
|
328
234
|
_resetPendingSlot();
|
|
235
|
+
const isContainer = mountTarget.nodeType === 1;
|
|
236
|
+
const target = isContainer
|
|
237
|
+
? mountTarget
|
|
238
|
+
: { anchor: mountTarget, mode: opts.mode === 'hydrate' ? 'replace' : 'append' };
|
|
329
239
|
let handle;
|
|
330
240
|
if (opts.mode === 'hydrate') {
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
// entry carrying `{ name, state }`. We match by name so a server/
|
|
334
|
-
// client mismatch throws with a clear error instead of silently
|
|
335
|
-
// hydrating the wrong state into the wrong instance.
|
|
241
|
+
// Each layer pulls its own state slice from the envelope, matched by name
|
|
242
|
+
// so a server/client mismatch throws clearly instead of binding wrong state.
|
|
336
243
|
const layerState = extractHydrationState(opts.serverStateEnvelope, i, chain.length, def);
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
// primitive's signature. The cast is safe — mountApp / hydrateApp
|
|
342
|
-
// don't use the type parameters at runtime.
|
|
343
|
-
handle = hydrateApp(mountTarget, def, layerState, { parentLifetime, runInitEffectsOnHydrate: opts.runInitEffectsOnHydrate });
|
|
344
|
-
}
|
|
345
|
-
else {
|
|
346
|
-
// Comment anchor — inner layer, use hydrateAtAnchor.
|
|
347
|
-
handle = hydrateAtAnchor(mountTarget, def, layerState, { parentLifetime, runInitEffectsOnHydrate: opts.runInitEffectsOnHydrate });
|
|
348
|
-
}
|
|
244
|
+
handle = hydrateSignalApp(target, def, layerState, {
|
|
245
|
+
runInitEffects: opts.runInitEffectsOnHydrate,
|
|
246
|
+
contexts,
|
|
247
|
+
});
|
|
349
248
|
}
|
|
350
249
|
else {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
else {
|
|
356
|
-
// Comment anchor — inner layer, use mountAtAnchor.
|
|
357
|
-
handle = mountAtAnchor(mountTarget, def, layerData, { parentLifetime });
|
|
358
|
-
}
|
|
250
|
+
handle = mountSignalComponent(target, def, {
|
|
251
|
+
initialState: seedFor(layerData),
|
|
252
|
+
contexts,
|
|
253
|
+
});
|
|
359
254
|
}
|
|
360
255
|
const slot = _consumePendingSlot();
|
|
361
256
|
if (isInnermost && slot !== null) {
|
|
362
|
-
// Innermost layer declared a slot with nothing to fill it —
|
|
363
|
-
// probably a misuse of pageSlot() in the page component itself.
|
|
364
257
|
handle.dispose();
|
|
365
258
|
throw new Error(`[llui/vike] <${def.name}> is the innermost component in the chain ` +
|
|
366
259
|
`but called pageSlot(). pageSlot() only belongs in layout components ` +
|
|
367
260
|
`that wrap a nested page or layout — not in the page itself.`);
|
|
368
261
|
}
|
|
369
262
|
if (!isInnermost && slot === null) {
|
|
370
|
-
// Non-innermost layer didn't declare a slot — there's nowhere to
|
|
371
|
-
// mount the remaining chain.
|
|
372
263
|
handle.dispose();
|
|
373
264
|
throw new Error(`[llui/vike] <${def.name}> is a layout layer at depth ${i} but did not ` +
|
|
374
265
|
`call pageSlot() in its view(). There are ${chain.length - i - 1} more ` +
|
|
@@ -379,47 +270,28 @@ export function _mountChainSuffix(chain, chainData, startAt, initialTarget, init
|
|
|
379
270
|
def,
|
|
380
271
|
handle,
|
|
381
272
|
slotAnchor: slot?.anchor ?? null,
|
|
382
|
-
|
|
273
|
+
slotContexts: slot?.contexts ?? null,
|
|
383
274
|
data: layerData,
|
|
384
275
|
});
|
|
385
276
|
if (slot !== null) {
|
|
386
|
-
// Next layer mounts relative to
|
|
277
|
+
// Next layer mounts relative to this slot's anchor, replaying the
|
|
278
|
+
// contexts captured at the slot so providers above it stay reachable.
|
|
387
279
|
mountTarget = slot.anchor;
|
|
388
|
-
|
|
280
|
+
contexts = slot.contexts;
|
|
389
281
|
}
|
|
390
282
|
}
|
|
391
283
|
}
|
|
392
|
-
// Internal alias used by renderClient
|
|
393
|
-
//
|
|
284
|
+
// Internal alias used by renderClient. The public-named export above carries
|
|
285
|
+
// the @internal doc.
|
|
394
286
|
const mountChainSuffix = _mountChainSuffix;
|
|
395
287
|
/**
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
*
|
|
399
|
-
* for backward compatibility with pages written against 0.0.15 or earlier.
|
|
400
|
-
*
|
|
401
|
-
* Throws on envelope shape mismatch — missing entries, wrong component
|
|
402
|
-
* name at a given index — so server/client drift fails loud instead of
|
|
403
|
-
* silently binding the wrong state to the wrong instance.
|
|
404
|
-
*/
|
|
405
|
-
/**
|
|
406
|
-
* Shallow-key data diff for the persistent-layer prop-update path.
|
|
407
|
-
* Returns true when `next` differs from `prev` enough to warrant
|
|
408
|
-
* dispatching the user's `onLayerDataChange` hook:
|
|
409
|
-
*
|
|
410
|
-
* - `Object.is(prev, next)` short-circuits identical references.
|
|
411
|
-
* - For two plain-object records, walks the union of keys and returns
|
|
412
|
-
* true on the first `Object.is` mismatch.
|
|
413
|
-
* - For anything else (primitives, arrays, class instances), falls
|
|
414
|
-
* back to the top-level `Object.is` result — covers the cases where
|
|
415
|
-
* the host populates `lluiLayoutData[i]` with a primitive or a
|
|
416
|
-
* referentially-stable object.
|
|
288
|
+
* Shallow-key data diff for the surviving-layer prop-update path. Returns true
|
|
289
|
+
* when `next` differs from `prev` enough to warrant dispatching the user's
|
|
290
|
+
* `onLayerDataChange` hook.
|
|
417
291
|
*/
|
|
418
292
|
function hasDataChanged(prev, next) {
|
|
419
293
|
if (Object.is(prev, next))
|
|
420
294
|
return false;
|
|
421
|
-
// Both must be plain object records to do a key walk; otherwise the
|
|
422
|
-
// Object.is above is the only signal.
|
|
423
295
|
if (prev === null ||
|
|
424
296
|
next === null ||
|
|
425
297
|
typeof prev !== 'object' ||
|
|
@@ -442,9 +314,15 @@ function hasDataChanged(prev, next) {
|
|
|
442
314
|
}
|
|
443
315
|
return false;
|
|
444
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
* Pull the per-layer state from the hydration envelope. Supports the chain-aware
|
|
319
|
+
* shape (`{ layouts: [...], page: {...} }`) and the legacy flat shape (the state
|
|
320
|
+
* object itself) for a single-layer page-only render.
|
|
321
|
+
*
|
|
322
|
+
* Throws on envelope shape mismatch — missing entries, wrong component name at a
|
|
323
|
+
* given index — so server/client drift fails loud.
|
|
324
|
+
*/
|
|
445
325
|
function extractHydrationState(envelope, layerIndex, chainLength, def) {
|
|
446
|
-
// Legacy flat envelope — no layout chain at render time. Only valid
|
|
447
|
-
// when the chain has a single layer (the page).
|
|
448
326
|
const isLegacyFlat = envelope !== null &&
|
|
449
327
|
typeof envelope === 'object' &&
|
|
450
328
|
!('layouts' in envelope) &&
|