@llui/vike 0.0.15 → 0.0.16
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 +162 -7
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/on-render-client.d.ts +97 -51
- package/dist/on-render-client.d.ts.map +1 -1
- package/dist/on-render-client.js +235 -52
- package/dist/on-render-client.js.map +1 -1
- package/dist/on-render-html.d.ts +45 -15
- package/dist/on-render-html.d.ts.map +1 -1
- package/dist/on-render-html.js +119 -20
- package/dist/on-render-html.js.map +1 -1
- package/dist/page-slot.d.ts +63 -0
- package/dist/page-slot.d.ts.map +1 -0
- package/dist/page-slot.js +77 -0
- package/dist/page-slot.js.map +1 -0
- package/package.json +2 -2
package/dist/on-render-html.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { renderNodes, serializeNodes } from '@llui/dom';
|
|
2
|
+
import { _consumePendingSlot, _resetPendingSlot } from './page-slot.js';
|
|
2
3
|
const DEFAULT_DOCUMENT = ({ html, state, head }) => `<!DOCTYPE html>
|
|
3
4
|
<html>
|
|
4
5
|
<head>
|
|
@@ -10,48 +11,146 @@ const DEFAULT_DOCUMENT = ({ html, state, head }) => `<!DOCTYPE html>
|
|
|
10
11
|
<script>window.__LLUI_STATE__ = ${state}</script>
|
|
11
12
|
</body>
|
|
12
13
|
</html>`;
|
|
14
|
+
function resolveLayoutChain(layoutOption, pageContext) {
|
|
15
|
+
if (!layoutOption)
|
|
16
|
+
return [];
|
|
17
|
+
if (typeof layoutOption === 'function') {
|
|
18
|
+
return layoutOption(pageContext) ?? [];
|
|
19
|
+
}
|
|
20
|
+
if (Array.isArray(layoutOption))
|
|
21
|
+
return layoutOption;
|
|
22
|
+
return [layoutOption];
|
|
23
|
+
}
|
|
13
24
|
/**
|
|
14
|
-
* Default onRenderHtml hook
|
|
15
|
-
* Uses a minimal HTML document template.
|
|
25
|
+
* Default onRenderHtml hook — no layout, minimal document template.
|
|
16
26
|
*/
|
|
17
27
|
export async function onRenderHtml(pageContext) {
|
|
18
|
-
return renderPage(pageContext,
|
|
28
|
+
return renderPage(pageContext, {});
|
|
19
29
|
}
|
|
20
30
|
/**
|
|
21
31
|
* Factory to create a customized onRenderHtml hook.
|
|
22
32
|
*
|
|
23
|
-
* ```
|
|
33
|
+
* ```ts
|
|
24
34
|
* // pages/+onRenderHtml.ts
|
|
25
|
-
* import { createOnRenderHtml } from '@llui/vike'
|
|
35
|
+
* import { createOnRenderHtml } from '@llui/vike/server'
|
|
36
|
+
* import { AppLayout } from './+Layout'
|
|
26
37
|
*
|
|
27
38
|
* export const onRenderHtml = createOnRenderHtml({
|
|
39
|
+
* Layout: AppLayout,
|
|
28
40
|
* document: ({ html, state, head }) => `<!DOCTYPE html>
|
|
29
|
-
* <html>
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* <script>window.__LLUI_STATE__ = ${state}</script></body>
|
|
33
|
-
* </html>`,
|
|
41
|
+
* <html><head>${head}<link rel="stylesheet" href="/styles.css" /></head>
|
|
42
|
+
* <body><div id="app">${html}</div>
|
|
43
|
+
* <script>window.__LLUI_STATE__ = ${state}</script></body></html>`,
|
|
34
44
|
* })
|
|
35
45
|
* ```
|
|
36
46
|
*/
|
|
37
47
|
export function createOnRenderHtml(options) {
|
|
38
|
-
return (pageContext) => renderPage(pageContext, options
|
|
48
|
+
return (pageContext) => renderPage(pageContext, options);
|
|
39
49
|
}
|
|
40
|
-
async function renderPage(pageContext,
|
|
50
|
+
async function renderPage(pageContext, options) {
|
|
41
51
|
// Lazy-import to keep jsdom out of the client bundle's dependency graph
|
|
42
52
|
const { initSsrDom } = await import('@llui/dom/ssr');
|
|
43
53
|
await initSsrDom();
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
const layoutChain = resolveLayoutChain(options.Layout, pageContext);
|
|
55
|
+
const layoutData = pageContext.lluiLayoutData ?? [];
|
|
56
|
+
// Full chain: every layout, then the page. Always at least one entry
|
|
57
|
+
// (the page) since Vike's pageContext always has a Page.
|
|
58
|
+
const chain = [...layoutChain, pageContext.Page];
|
|
59
|
+
const chainData = [...layoutData, pageContext.data];
|
|
60
|
+
const { html, envelope } = renderChain(chain, chainData);
|
|
61
|
+
const document = options.document ?? DEFAULT_DOCUMENT;
|
|
48
62
|
const head = pageContext.head ?? '';
|
|
63
|
+
const state = JSON.stringify(envelope);
|
|
49
64
|
const documentHtml = document({ html, state, head, pageContext });
|
|
50
65
|
return {
|
|
51
|
-
//
|
|
52
|
-
//
|
|
66
|
+
// Vike's dangerouslySkipEscape format — the document template is
|
|
67
|
+
// trusted (authored by the developer, not user input)
|
|
53
68
|
documentHtml: { _escaped: documentHtml },
|
|
54
|
-
pageContext: { lluiState:
|
|
69
|
+
pageContext: { lluiState: envelope },
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Render every layer of the chain into one composed DOM tree, then
|
|
74
|
+
* serialize. At each non-innermost layer, consume the pending
|
|
75
|
+
* `pageSlot()` registration and append the next layer's nodes into
|
|
76
|
+
* the slot marker. Scopes are threaded so inner layers inherit the
|
|
77
|
+
* outer layer's scope tree for context lookups.
|
|
78
|
+
*/
|
|
79
|
+
function renderChain(chain, chainData) {
|
|
80
|
+
if (chain.length === 0) {
|
|
81
|
+
throw new Error('[llui/vike] renderChain called with empty chain');
|
|
82
|
+
}
|
|
83
|
+
// Defensive: ensure no stale slot leaks in from a prior failed render.
|
|
84
|
+
_resetPendingSlot();
|
|
85
|
+
// Accumulate bindings from every layer — serializeNodes needs the
|
|
86
|
+
// full set so hydrate markers are correctly placed across the
|
|
87
|
+
// composed tree.
|
|
88
|
+
const allBindings = [];
|
|
89
|
+
const envelopeLayouts = [];
|
|
90
|
+
let envelopePage = null;
|
|
91
|
+
let outermostNodes = [];
|
|
92
|
+
let currentSlotMarker = null;
|
|
93
|
+
let currentSlotScope = undefined;
|
|
94
|
+
for (let i = 0; i < chain.length; i++) {
|
|
95
|
+
const def = chain[i];
|
|
96
|
+
const layerData = chainData[i];
|
|
97
|
+
const isInnermost = i === chain.length - 1;
|
|
98
|
+
// Resolve the initial state from the layer's own init() applied to
|
|
99
|
+
// its data slice, same as client-side mountApp(). renderNodes takes
|
|
100
|
+
// the state post-init so that the view sees the right fields.
|
|
101
|
+
const [initialState] = def.init(layerData);
|
|
102
|
+
const { nodes, inst } = renderNodes(def, initialState, currentSlotScope);
|
|
103
|
+
allBindings.push(...inst.allBindings);
|
|
104
|
+
if (i === 0) {
|
|
105
|
+
outermostNodes = nodes;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// Append this layer's nodes into the previous layer's slot marker.
|
|
109
|
+
// The slot marker is an element owned by the previous layer's DOM;
|
|
110
|
+
// appending here stitches this layer into the composed tree so
|
|
111
|
+
// the final serialization pass emits one integrated HTML string.
|
|
112
|
+
if (!currentSlotMarker) {
|
|
113
|
+
// Unreachable given the error checks below, but defensive.
|
|
114
|
+
throw new Error(`[llui/vike] internal: chain layer ${i} (<${def.name}>) has no slot marker`);
|
|
115
|
+
}
|
|
116
|
+
for (const node of nodes) {
|
|
117
|
+
currentSlotMarker.appendChild(node);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Record this layer's state in the envelope. Page goes under
|
|
121
|
+
// `page`, everything else under `layouts[]` ordered outer-to-inner.
|
|
122
|
+
if (isInnermost) {
|
|
123
|
+
envelopePage = { name: def.name, state: initialState };
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
envelopeLayouts.push({ name: def.name, state: initialState });
|
|
127
|
+
}
|
|
128
|
+
// Consume this layer's pending slot registration (if any). Every
|
|
129
|
+
// non-innermost layer MUST declare a slot; the innermost MUST NOT.
|
|
130
|
+
const slot = _consumePendingSlot();
|
|
131
|
+
if (isInnermost && slot !== null) {
|
|
132
|
+
throw new Error(`[llui/vike] <${def.name}> is the innermost component in the chain ` +
|
|
133
|
+
`but called pageSlot(). pageSlot() only belongs in layout components.`);
|
|
134
|
+
}
|
|
135
|
+
if (!isInnermost && slot === null) {
|
|
136
|
+
throw new Error(`[llui/vike] <${def.name}> is a layout layer at depth ${i} but did not ` +
|
|
137
|
+
`call pageSlot() in its view(). There are ${chain.length - i - 1} more ` +
|
|
138
|
+
`layer(s) to mount and no slot to mount them into.`);
|
|
139
|
+
}
|
|
140
|
+
currentSlotMarker = slot?.marker ?? null;
|
|
141
|
+
currentSlotScope = slot?.slotScope;
|
|
142
|
+
}
|
|
143
|
+
const html = serializeNodes(outermostNodes, allBindings);
|
|
144
|
+
if (envelopePage === null) {
|
|
145
|
+
// Unreachable — chain is non-empty so the last iteration sets this.
|
|
146
|
+
throw new Error('[llui/vike] internal: renderChain produced no page entry');
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
html,
|
|
150
|
+
envelope: {
|
|
151
|
+
layouts: envelopeLayouts,
|
|
152
|
+
page: envelopePage,
|
|
153
|
+
},
|
|
55
154
|
};
|
|
56
155
|
}
|
|
57
156
|
//# sourceMappingURL=on-render-html.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"on-render-html.js","sourceRoot":"","sources":["../src/on-render-html.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAyB1C,MAAM,gBAAgB,GAAG,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAmB,EAAU,EAAE,CAAC;;;;MAIvE,IAAI;;;oBAGU,IAAI;sCACc,KAAK;;QAEnC,CAAA;AAER;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,WAAwB;IACzD,OAAO,UAAU,CAAC,WAAW,EAAE,gBAAgB,CAAC,CAAA;AAClD,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAElC;IACC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,UAAU,CAAC,WAAW,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;AACnE,CAAC;AAED,KAAK,UAAU,UAAU,CACvB,WAAwB,EACxB,QAA0C;IAE1C,wEAAwE;IACxE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAA;IACpD,MAAM,UAAU,EAAE,CAAA;IAElB,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,WAAW,CAAA;IAClC,MAAM,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACtC,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,EAAE,YAAY,CAAC,CAAA;IAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAA;IAC1C,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,IAAI,EAAE,CAAA;IAEnC,MAAM,YAAY,GAAG,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAA;IAEjE,OAAO;QACL,kEAAkE;QAClE,yDAAyD;QACzD,YAAY,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE;QACxC,WAAW,EAAE,EAAE,SAAS,EAAE,YAAY,EAAE;KACzC,CAAA;AACH,CAAC","sourcesContent":["import { renderToString } from '@llui/dom'\nimport type { ComponentDef } from '@llui/dom'\n\nexport interface PageContext {\n Page: ComponentDef<unknown, unknown, unknown, unknown>\n data?: unknown\n head?: string\n}\n\nexport interface DocumentContext {\n /** Rendered component HTML */\n html: string\n /** JSON-serialized initial state */\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 * Default onRenderHtml hook for simple cases.\n * Uses a minimal HTML document template.\n */\nexport async function onRenderHtml(pageContext: PageContext): Promise<RenderHtmlResult> {\n return renderPage(pageContext, DEFAULT_DOCUMENT)\n}\n\n/**\n * Factory to create a customized onRenderHtml hook.\n *\n * ```typescript\n * // pages/+onRenderHtml.ts\n * import { createOnRenderHtml } from '@llui/vike'\n *\n * export const onRenderHtml = createOnRenderHtml({\n * document: ({ html, state, head }) => `<!DOCTYPE html>\n * <html>\n * <head>${head}<link rel=\"stylesheet\" href=\"/styles.css\" /></head>\n * <body><div id=\"app\">${html}</div>\n * <script>window.__LLUI_STATE__ = ${state}</script></body>\n * </html>`,\n * })\n * ```\n */\nexport function createOnRenderHtml(options: {\n document: (ctx: DocumentContext) => string\n}): (pageContext: PageContext) => Promise<RenderHtmlResult> {\n return (pageContext) => renderPage(pageContext, options.document)\n}\n\nasync function renderPage(\n pageContext: PageContext,\n document: (ctx: DocumentContext) => string,\n): Promise<RenderHtmlResult> {\n // Lazy-import to keep jsdom out of the client bundle's dependency graph\n const { initSsrDom } = await import('@llui/dom/ssr')\n await initSsrDom()\n\n const { Page, data } = pageContext\n const [initialState] = Page.init(data)\n const html = renderToString(Page, initialState)\n const state = JSON.stringify(initialState)\n const head = pageContext.head ?? ''\n\n const documentHtml = document({ html, state, head, pageContext })\n\n return {\n // Use Vike's dangerouslySkipEscape format — the document template\n // is trusted (authored by the developer, not user input)\n documentHtml: { _escaped: documentHtml },\n pageContext: { lluiState: initialState },\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"on-render-html.js","sourceRoot":"","sources":["../src/on-render-html.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAEvD,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAkCvE,MAAM,gBAAgB,GAAG,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAmB,EAAU,EAAE,CAAC;;;;MAIvE,IAAI;;;oBAGU,IAAI;sCACc,KAAK;;QAEnC,CAAA;AA2BR,SAAS,kBAAkB,CACzB,YAAyC,EACzC,WAAwB;IAExB,IAAI,CAAC,YAAY;QAAE,OAAO,EAAE,CAAA;IAC5B,IAAI,OAAO,YAAY,KAAK,UAAU,EAAE,CAAC;QACvC,OAAO,YAAY,CAAC,WAAW,CAAC,IAAI,EAAE,CAAA;IACxC,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC;QAAE,OAAO,YAAY,CAAA;IACpD,OAAO,CAAC,YAA+B,CAAC,CAAA;AAC1C,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,WAAwB;IACzD,OAAO,UAAU,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;AACpC,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAA0B;IAE1B,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,UAAU,CAAC,WAAW,EAAE,OAAO,CAAC,CAAA;AAC1D,CAAC;AAYD,KAAK,UAAU,UAAU,CACvB,WAAwB,EACxB,OAA0B;IAE1B,wEAAwE;IACxE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAA;IACpD,MAAM,UAAU,EAAE,CAAA;IAElB,MAAM,WAAW,GAAG,kBAAkB,CAAC,OAAO,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IACnE,MAAM,UAAU,GAAG,WAAW,CAAC,cAAc,IAAI,EAAE,CAAA;IAEnD,qEAAqE;IACrE,yDAAyD;IACzD,MAAM,KAAK,GAAgB,CAAC,GAAG,WAAW,EAAE,WAAW,CAAC,IAAI,CAAC,CAAA;IAC7D,MAAM,SAAS,GAAuB,CAAC,GAAG,UAAU,EAAE,WAAW,CAAC,IAAI,CAAC,CAAA;IAEvE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,WAAW,CAAC,KAAK,EAAE,SAAS,CAAC,CAAA;IAExD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,gBAAgB,CAAA;IACrD,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,IAAI,EAAE,CAAA;IACnC,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;IACtC,MAAM,YAAY,GAAG,QAAQ,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAA;IAEjE,OAAO;QACL,iEAAiE;QACjE,sDAAsD;QACtD,YAAY,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE;QACxC,WAAW,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE;KACrC,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,WAAW,CAClB,KAAkB,EAClB,SAA6B;IAE7B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAA;IACpE,CAAC;IAED,uEAAuE;IACvE,iBAAiB,EAAE,CAAA;IAEnB,kEAAkE;IAClE,8DAA8D;IAC9D,iBAAiB;IACjB,MAAM,WAAW,GAAc,EAAE,CAAA;IACjC,MAAM,eAAe,GAAiC,EAAE,CAAA;IACxD,IAAI,YAAY,GAAqC,IAAI,CAAA;IAEzD,IAAI,cAAc,GAAW,EAAE,CAAA;IAC/B,IAAI,iBAAiB,GAAuB,IAAI,CAAA;IAChD,IAAI,gBAAgB,GAAsB,SAAS,CAAA;IAEnD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAE,CAAA;QACrB,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,CAAA;QAC9B,MAAM,WAAW,GAAG,CAAC,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;QAE1C,mEAAmE;QACnE,oEAAoE;QACpE,8DAA8D;QAC9D,MAAM,CAAC,YAAY,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAE1C,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,YAAY,EAAE,gBAAgB,CAAC,CAAA;QACxE,WAAW,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAAA;QAErC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACZ,cAAc,GAAG,KAAK,CAAA;QACxB,CAAC;aAAM,CAAC;YACN,mEAAmE;YACnE,mEAAmE;YACnE,+DAA+D;YAC/D,iEAAiE;YACjE,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBACvB,2DAA2D;gBAC3D,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,MAAM,GAAG,CAAC,IAAI,uBAAuB,CAAC,CAAA;YAC9F,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,iBAAiB,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;YACrC,CAAC;QACH,CAAC;QAED,6DAA6D;QAC7D,oEAAoE;QACpE,IAAI,WAAW,EAAE,CAAC;YAChB,YAAY,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,CAAA;QACxD,CAAC;aAAM,CAAC;YACN,eAAe,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAA;QAC/D,CAAC;QAED,iEAAiE;QACjE,mEAAmE;QACnE,MAAM,IAAI,GAAG,mBAAmB,EAAE,CAAA;QAClC,IAAI,WAAW,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CACb,gBAAgB,GAAG,CAAC,IAAI,4CAA4C;gBAClE,sEAAsE,CACzE,CAAA;QACH,CAAC;QACD,IAAI,CAAC,WAAW,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CACb,gBAAgB,GAAG,CAAC,IAAI,gCAAgC,CAAC,eAAe;gBACtE,4CAA4C,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,QAAQ;gBACxE,mDAAmD,CACtD,CAAA;QACH,CAAC;QAED,iBAAiB,GAAG,IAAI,EAAE,MAAM,IAAI,IAAI,CAAA;QACxC,gBAAgB,GAAG,IAAI,EAAE,SAAS,CAAA;IACpC,CAAC;IAED,MAAM,IAAI,GAAG,cAAc,CAAC,cAAc,EAAE,WAAW,CAAC,CAAA;IAExD,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;QAC1B,oEAAoE;QACpE,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAA;IAC7E,CAAC;IAED,OAAO;QACL,IAAI;QACJ,QAAQ,EAAE;YACR,OAAO,EAAE,eAAe;YACxB,IAAI,EAAE,YAAY;SACnB;KACF,CAAA;AACH,CAAC","sourcesContent":["import { renderNodes, serializeNodes } from '@llui/dom'\nimport type { ComponentDef, Binding, Scope } from '@llui/dom'\nimport { _consumePendingSlot, _resetPendingSlot } from './page-slot.js'\n\ntype AnyComponentDef = ComponentDef<unknown, unknown, unknown, unknown>\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 */\nexport interface PageContext {\n Page: AnyComponentDef\n data?: unknown\n lluiLayoutData?: readonly unknown[]\n head?: string\n}\n\nexport interface DocumentContext {\n /** Rendered component HTML (layout + page composed if a Layout is configured) */\n html: string\n /** JSON-serialized hydration envelope (chain-aware when Layout is configured) */\n state: string\n /** Head content from pageContext.head (e.g. from +Head.ts) */\n head: string\n /** Full page context for custom logic */\n pageContext: PageContext\n}\n\nexport interface RenderHtmlResult {\n documentHtml: string | { _escaped: string }\n pageContext: { lluiState: unknown }\n}\n\nconst DEFAULT_DOCUMENT = ({ html, state, head }: DocumentContext): string => `<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n ${head}\n </head>\n <body>\n <div id=\"app\">${html}</div>\n <script>window.__LLUI_STATE__ = ${state}</script>\n </body>\n</html>`\n\n/**\n * Options for the customized `createOnRenderHtml` factory. Mirrors\n * `@llui/vike/client`'s `RenderClientOptions.Layout` — the same chain\n * shape is accepted for consistency between server and client render.\n */\nexport interface RenderHtmlOptions {\n /** Custom HTML document template. Defaults to a minimal layout. */\n document?: (ctx: DocumentContext) => string\n\n /**\n * Persistent layout chain. One of:\n *\n * - A single `ComponentDef` — becomes a one-layout chain.\n * - An array of `ComponentDef`s — outermost first, innermost last.\n * Every layer except the innermost must call `pageSlot()` in its view.\n * - A function that returns a chain from the current `pageContext` —\n * enables per-route chains (e.g. reading Vike's `urlPathname`).\n *\n * The server renders the full chain as one composed HTML tree. Client\n * hydration reads the matching envelope and reconstructs the chain\n * layer-by-layer.\n */\n Layout?: AnyComponentDef | LayoutChain | ((pageContext: PageContext) => LayoutChain)\n}\n\nfunction resolveLayoutChain(\n layoutOption: RenderHtmlOptions['Layout'],\n pageContext: PageContext,\n): LayoutChain {\n if (!layoutOption) return []\n if (typeof layoutOption === 'function') {\n return layoutOption(pageContext) ?? []\n }\n if (Array.isArray(layoutOption)) return layoutOption\n return [layoutOption as AnyComponentDef]\n}\n\n/**\n * Default onRenderHtml hook — no layout, minimal document template.\n */\nexport async function onRenderHtml(pageContext: PageContext): Promise<RenderHtmlResult> {\n return renderPage(pageContext, {})\n}\n\n/**\n * Factory to create a customized onRenderHtml hook.\n *\n * ```ts\n * // pages/+onRenderHtml.ts\n * import { createOnRenderHtml } from '@llui/vike/server'\n * import { AppLayout } from './+Layout'\n *\n * export const onRenderHtml = createOnRenderHtml({\n * Layout: AppLayout,\n * document: ({ html, state, head }) => `<!DOCTYPE html>\n * <html><head>${head}<link rel=\"stylesheet\" href=\"/styles.css\" /></head>\n * <body><div id=\"app\">${html}</div>\n * <script>window.__LLUI_STATE__ = ${state}</script></body></html>`,\n * })\n * ```\n */\nexport function createOnRenderHtml(\n options: RenderHtmlOptions,\n): (pageContext: PageContext) => Promise<RenderHtmlResult> {\n return (pageContext) => renderPage(pageContext, options)\n}\n\n/**\n * Hydration envelope emitted into `window.__LLUI_STATE__`. Chain-aware\n * by default — every layer (layouts + page) is represented by its own\n * entry, keyed by component name so server/client mismatches fail loud.\n */\ninterface HydrationEnvelope {\n layouts: Array<{ name: string; state: unknown }>\n page: { name: string; state: unknown }\n}\n\nasync function renderPage(\n pageContext: PageContext,\n options: RenderHtmlOptions,\n): Promise<RenderHtmlResult> {\n // Lazy-import to keep jsdom out of the client bundle's dependency graph\n const { initSsrDom } = await import('@llui/dom/ssr')\n await initSsrDom()\n\n const layoutChain = resolveLayoutChain(options.Layout, pageContext)\n const layoutData = pageContext.lluiLayoutData ?? []\n\n // Full chain: every layout, then the page. Always at least one entry\n // (the page) since Vike's pageContext always has a Page.\n const chain: LayoutChain = [...layoutChain, pageContext.Page]\n const chainData: readonly unknown[] = [...layoutData, pageContext.data]\n\n const { html, envelope } = renderChain(chain, chainData)\n\n const document = options.document ?? DEFAULT_DOCUMENT\n const head = pageContext.head ?? ''\n const state = JSON.stringify(envelope)\n const documentHtml = document({ html, state, head, pageContext })\n\n return {\n // Vike's dangerouslySkipEscape format — the document template is\n // trusted (authored by the developer, not user input)\n documentHtml: { _escaped: documentHtml },\n pageContext: { lluiState: envelope },\n }\n}\n\n/**\n * Render every layer of the chain into one composed DOM tree, then\n * serialize. At each non-innermost layer, consume the pending\n * `pageSlot()` registration and append the next layer's nodes into\n * the slot marker. Scopes are threaded so inner layers inherit the\n * outer layer's scope tree for context lookups.\n */\nfunction renderChain(\n chain: LayoutChain,\n chainData: readonly unknown[],\n): { html: string; envelope: HydrationEnvelope } {\n if (chain.length === 0) {\n throw new Error('[llui/vike] renderChain called with empty chain')\n }\n\n // Defensive: ensure no stale slot leaks in from a prior failed render.\n _resetPendingSlot()\n\n // Accumulate bindings from every layer — serializeNodes needs the\n // full set so hydrate markers are correctly placed across the\n // composed tree.\n const allBindings: Binding[] = []\n const envelopeLayouts: HydrationEnvelope['layouts'] = []\n let envelopePage: HydrationEnvelope['page'] | null = null\n\n let outermostNodes: Node[] = []\n let currentSlotMarker: HTMLElement | null = null\n let currentSlotScope: Scope | undefined = undefined\n\n for (let i = 0; i < chain.length; i++) {\n const def = chain[i]!\n const layerData = chainData[i]\n const isInnermost = i === chain.length - 1\n\n // Resolve the initial state from the layer's own init() applied to\n // its data slice, same as client-side mountApp(). renderNodes takes\n // the state post-init so that the view sees the right fields.\n const [initialState] = def.init(layerData)\n\n const { nodes, inst } = renderNodes(def, initialState, currentSlotScope)\n allBindings.push(...inst.allBindings)\n\n if (i === 0) {\n outermostNodes = nodes\n } else {\n // Append this layer's nodes into the previous layer's slot marker.\n // The slot marker is an element owned by the previous layer's DOM;\n // appending here stitches this layer into the composed tree so\n // the final serialization pass emits one integrated HTML string.\n if (!currentSlotMarker) {\n // Unreachable given the error checks below, but defensive.\n throw new Error(`[llui/vike] internal: chain layer ${i} (<${def.name}>) has no slot marker`)\n }\n for (const node of nodes) {\n currentSlotMarker.appendChild(node)\n }\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 currentSlotMarker = slot?.marker ?? null\n currentSlotScope = slot?.slotScope\n }\n\n const html = serializeNodes(outermostNodes, allBindings)\n\n if (envelopePage === null) {\n // Unreachable — chain is non-empty so the last iteration sets this.\n throw new Error('[llui/vike] internal: renderChain produced no page entry')\n }\n\n return {\n html,\n envelope: {\n layouts: envelopeLayouts,\n page: envelopePage,\n },\n }\n}\n"]}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { Scope } from '@llui/dom';
|
|
2
|
+
/**
|
|
3
|
+
* Transient handoff between a layout layer's render pass and the vike
|
|
4
|
+
* adapter that's mounting the chain. `pageSlot()` populates this during
|
|
5
|
+
* the layout's view() call; `consumePendingSlot()` reads and clears it
|
|
6
|
+
* immediately after the mount returns. One slot per mount pass — calling
|
|
7
|
+
* `pageSlot()` twice in the same layout is a bug the primitive reports.
|
|
8
|
+
*/
|
|
9
|
+
interface PendingSlot {
|
|
10
|
+
slotScope: Scope;
|
|
11
|
+
marker: HTMLElement;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Declare where a persistent layout renders its nested content — either
|
|
15
|
+
* a nested layout or the route's page component. The vike adapter's
|
|
16
|
+
* client and server render paths walk the layout chain, and each layer's
|
|
17
|
+
* `pageSlot()` call records the position where the next layer mounts.
|
|
18
|
+
*
|
|
19
|
+
* The slot is a real scope-tree node: the scope it creates is a child
|
|
20
|
+
* of the current render scope, so contexts provided by the layout (via
|
|
21
|
+
* `provide()`) above the slot are reachable from inside the nested
|
|
22
|
+
* page. That's how patterns like a layout-owned toast dispatcher work —
|
|
23
|
+
* the page does `useContext(ToastContext)` and walks up through the
|
|
24
|
+
* slot into the layout's providers.
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* // pages/+Layout.ts
|
|
28
|
+
* import { component, div, main, header } from '@llui/dom'
|
|
29
|
+
* import { pageSlot } from '@llui/vike/client'
|
|
30
|
+
*
|
|
31
|
+
* export const AppLayout = component<LayoutState, LayoutMsg>({
|
|
32
|
+
* name: 'AppLayout',
|
|
33
|
+
* init: () => [{ ... }, []],
|
|
34
|
+
* update: layoutUpdate,
|
|
35
|
+
* view: (h) => [
|
|
36
|
+
* div({ class: 'app-shell' }, [
|
|
37
|
+
* header([... persistent chrome ...]),
|
|
38
|
+
* main([pageSlot()]), // ← here the page goes
|
|
39
|
+
* ]),
|
|
40
|
+
* ],
|
|
41
|
+
* })
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* Call exactly once per layout. Calling more than once in a single
|
|
45
|
+
* view throws — a layout with two slots would be ambiguous about which
|
|
46
|
+
* one receives the nested content.
|
|
47
|
+
*/
|
|
48
|
+
export declare function pageSlot(): Node[];
|
|
49
|
+
/**
|
|
50
|
+
* @internal — vike adapter only. Read and clear the slot registered by
|
|
51
|
+
* the most recent `pageSlot()` call. Returns null if the layer being
|
|
52
|
+
* mounted didn't call `pageSlot()` (meaning it's the innermost layer
|
|
53
|
+
* and owns no nested content).
|
|
54
|
+
*/
|
|
55
|
+
export declare function _consumePendingSlot(): PendingSlot | null;
|
|
56
|
+
/**
|
|
57
|
+
* @internal — vike adapter only. Reset the pending slot without reading
|
|
58
|
+
* it. Used defensively in error paths to avoid leaking a pending slot
|
|
59
|
+
* registration into a subsequent mount attempt.
|
|
60
|
+
*/
|
|
61
|
+
export declare function _resetPendingSlot(): void;
|
|
62
|
+
export {};
|
|
63
|
+
//# sourceMappingURL=page-slot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-slot.d.ts","sourceRoot":"","sources":["../src/page-slot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AAQtC;;;;;;GAMG;AACH,UAAU,WAAW;IACnB,SAAS,EAAE,KAAK,CAAA;IAChB,MAAM,EAAE,WAAW,CAAA;CACpB;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,QAAQ,IAAI,IAAI,EAAE,CAuBjC;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,IAAI,WAAW,GAAG,IAAI,CAIxD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAExC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { getRenderContext, createScope } from '@llui/dom/internal';
|
|
2
|
+
let pendingSlot = null;
|
|
3
|
+
/**
|
|
4
|
+
* Declare where a persistent layout renders its nested content — either
|
|
5
|
+
* a nested layout or the route's page component. The vike adapter's
|
|
6
|
+
* client and server render paths walk the layout chain, and each layer's
|
|
7
|
+
* `pageSlot()` call records the position where the next layer mounts.
|
|
8
|
+
*
|
|
9
|
+
* The slot is a real scope-tree node: the scope it creates is a child
|
|
10
|
+
* of the current render scope, so contexts provided by the layout (via
|
|
11
|
+
* `provide()`) above the slot are reachable from inside the nested
|
|
12
|
+
* page. That's how patterns like a layout-owned toast dispatcher work —
|
|
13
|
+
* the page does `useContext(ToastContext)` and walks up through the
|
|
14
|
+
* slot into the layout's providers.
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* // pages/+Layout.ts
|
|
18
|
+
* import { component, div, main, header } from '@llui/dom'
|
|
19
|
+
* import { pageSlot } from '@llui/vike/client'
|
|
20
|
+
*
|
|
21
|
+
* export const AppLayout = component<LayoutState, LayoutMsg>({
|
|
22
|
+
* name: 'AppLayout',
|
|
23
|
+
* init: () => [{ ... }, []],
|
|
24
|
+
* update: layoutUpdate,
|
|
25
|
+
* view: (h) => [
|
|
26
|
+
* div({ class: 'app-shell' }, [
|
|
27
|
+
* header([... persistent chrome ...]),
|
|
28
|
+
* main([pageSlot()]), // ← here the page goes
|
|
29
|
+
* ]),
|
|
30
|
+
* ],
|
|
31
|
+
* })
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* Call exactly once per layout. Calling more than once in a single
|
|
35
|
+
* view throws — a layout with two slots would be ambiguous about which
|
|
36
|
+
* one receives the nested content.
|
|
37
|
+
*/
|
|
38
|
+
export function pageSlot() {
|
|
39
|
+
if (typeof document === 'undefined') {
|
|
40
|
+
// Server path — @llui/dom's renderToString uses jsdom, so document
|
|
41
|
+
// IS defined during SSR, but we guard anyway for safety.
|
|
42
|
+
throw new Error('[llui/vike] pageSlot() called without a DOM environment. ' +
|
|
43
|
+
'Call from inside a component view() that runs during mount, hydrate, or SSR.');
|
|
44
|
+
}
|
|
45
|
+
if (pendingSlot !== null) {
|
|
46
|
+
throw new Error('[llui/vike] pageSlot() was called more than once in the same layout. ' +
|
|
47
|
+
'A layout has exactly one nested-content slot — if you need two independent ' +
|
|
48
|
+
'regions that swap on navigation, build them as sibling nested layouts in ' +
|
|
49
|
+
'the Vike routing tree and use context to share state between them.');
|
|
50
|
+
}
|
|
51
|
+
const ctx = getRenderContext('pageSlot');
|
|
52
|
+
const slotScope = createScope(ctx.rootScope);
|
|
53
|
+
const marker = document.createElement('div');
|
|
54
|
+
marker.dataset.lluiPageSlot = '';
|
|
55
|
+
pendingSlot = { slotScope, marker };
|
|
56
|
+
return [marker];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* @internal — vike adapter only. Read and clear the slot registered by
|
|
60
|
+
* the most recent `pageSlot()` call. Returns null if the layer being
|
|
61
|
+
* mounted didn't call `pageSlot()` (meaning it's the innermost layer
|
|
62
|
+
* and owns no nested content).
|
|
63
|
+
*/
|
|
64
|
+
export function _consumePendingSlot() {
|
|
65
|
+
const slot = pendingSlot;
|
|
66
|
+
pendingSlot = null;
|
|
67
|
+
return slot;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* @internal — vike adapter only. Reset the pending slot without reading
|
|
71
|
+
* it. Used defensively in error paths to avoid leaking a pending slot
|
|
72
|
+
* registration into a subsequent mount attempt.
|
|
73
|
+
*/
|
|
74
|
+
export function _resetPendingSlot() {
|
|
75
|
+
pendingSlot = null;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=page-slot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-slot.js","sourceRoot":"","sources":["../src/page-slot.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAmBlE,IAAI,WAAW,GAAuB,IAAI,CAAA;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,MAAM,UAAU,QAAQ;IACtB,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,CAAC;QACpC,mEAAmE;QACnE,yDAAyD;QACzD,MAAM,IAAI,KAAK,CACb,2DAA2D;YACzD,8EAA8E,CACjF,CAAA;IACH,CAAC;IACD,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,uEAAuE;YACrE,6EAA6E;YAC7E,2EAA2E;YAC3E,oEAAoE,CACvE,CAAA;IACH,CAAC;IACD,MAAM,GAAG,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAA;IACxC,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IAC5C,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IAC5C,MAAM,CAAC,OAAO,CAAC,YAAY,GAAG,EAAE,CAAA;IAChC,WAAW,GAAG,EAAE,SAAS,EAAE,MAAM,EAAE,CAAA;IACnC,OAAO,CAAC,MAAM,CAAC,CAAA;AACjB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,IAAI,GAAG,WAAW,CAAA;IACxB,WAAW,GAAG,IAAI,CAAA;IAClB,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB;IAC/B,WAAW,GAAG,IAAI,CAAA;AACpB,CAAC","sourcesContent":["import type { Scope } from '@llui/dom'\nimport { getRenderContext, createScope } from '@llui/dom/internal'\n\n// @llui/dom/internal is the adapter-layer surface: low-level primitives\n// (render-context access, scope creation, disposer registration) that\n// framework adapters like @llui/vike need to build structural primitives\n// on top of. Not part of the public app-author API.\n\n/**\n * Transient handoff between a layout layer's render pass and the vike\n * adapter that's mounting the chain. `pageSlot()` populates this during\n * the layout's view() call; `consumePendingSlot()` reads and clears it\n * immediately after the mount returns. One slot per mount pass — calling\n * `pageSlot()` twice in the same layout is a bug the primitive reports.\n */\ninterface PendingSlot {\n slotScope: Scope\n marker: HTMLElement\n}\n\nlet pendingSlot: PendingSlot | null = null\n\n/**\n * Declare where a persistent layout renders its nested content — either\n * a nested layout or the route's page component. The vike adapter's\n * client and server render paths walk the layout chain, and each layer's\n * `pageSlot()` call records the position where the next layer mounts.\n *\n * The slot is a real scope-tree node: the scope it creates is a child\n * of the current render scope, so contexts provided by the layout (via\n * `provide()`) above the slot are reachable from inside the nested\n * page. That's how patterns like a layout-owned toast dispatcher work —\n * the page does `useContext(ToastContext)` and walks up through the\n * slot into the layout's providers.\n *\n * ```ts\n * // pages/+Layout.ts\n * import { component, div, main, header } from '@llui/dom'\n * import { pageSlot } from '@llui/vike/client'\n *\n * export const AppLayout = component<LayoutState, LayoutMsg>({\n * name: 'AppLayout',\n * init: () => [{ ... }, []],\n * update: layoutUpdate,\n * view: (h) => [\n * div({ class: 'app-shell' }, [\n * header([... persistent chrome ...]),\n * main([pageSlot()]), // ← here the page goes\n * ]),\n * ],\n * })\n * ```\n *\n * Call exactly once per layout. Calling more than once in a single\n * view throws — a layout with two slots would be ambiguous about which\n * one receives the nested content.\n */\nexport function pageSlot(): Node[] {\n if (typeof document === 'undefined') {\n // Server path — @llui/dom's renderToString uses jsdom, so document\n // IS defined during SSR, but we guard anyway for safety.\n throw new Error(\n '[llui/vike] pageSlot() called without a DOM environment. ' +\n 'Call from inside a component view() that runs during mount, hydrate, or SSR.',\n )\n }\n if (pendingSlot !== null) {\n throw new Error(\n '[llui/vike] pageSlot() was called more than once in the same layout. ' +\n 'A layout has exactly one nested-content slot — if you need two independent ' +\n 'regions that swap on navigation, build them as sibling nested layouts in ' +\n 'the Vike routing tree and use context to share state between them.',\n )\n }\n const ctx = getRenderContext('pageSlot')\n const slotScope = createScope(ctx.rootScope)\n const marker = document.createElement('div')\n marker.dataset.lluiPageSlot = ''\n pendingSlot = { slotScope, marker }\n return [marker]\n}\n\n/**\n * @internal — vike adapter only. Read and clear the slot registered by\n * the most recent `pageSlot()` call. Returns null if the layer being\n * mounted didn't call `pageSlot()` (meaning it's the innermost layer\n * and owns no nested content).\n */\nexport function _consumePendingSlot(): PendingSlot | null {\n const slot = pendingSlot\n pendingSlot = null\n return slot\n}\n\n/**\n * @internal — vike adapter only. Reset the pending slot without reading\n * it. Used defensively in error paths to avoid leaking a pending slot\n * registration into a subsequent mount attempt.\n */\nexport function _resetPendingSlot(): void {\n pendingSlot = null\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llui/vike",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"exports": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"jsdom": "^26.1.0",
|
|
25
|
-
"@llui/dom": "0.0.
|
|
25
|
+
"@llui/dom": "0.0.16"
|
|
26
26
|
},
|
|
27
27
|
"description": "LLui Vike SSR adapter — onRenderHtml, onRenderClient hooks",
|
|
28
28
|
"keywords": [
|