@uniweb/runtime 0.8.14 → 0.8.15
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/app/_importmap/@uniweb-core.js +1 -1
- package/dist/app/_importmap/@uniweb-core.js.map +1 -1
- package/dist/app/assets/index-Bxvdtdsc.js +133 -0
- package/dist/app/assets/index-Bxvdtdsc.js.map +1 -0
- package/dist/app/assets/{index-BnewW1wW.js → index-CsqlosR8.js} +7 -7
- package/dist/app/assets/{index-BnewW1wW.js.map → index-CsqlosR8.js.map} +1 -1
- package/dist/app/index.html +2 -2
- package/dist/app/manifest.json +3 -3
- package/dist/ssr.js +11 -31
- package/dist/ssr.js.map +1 -1
- package/package.json +5 -5
- package/src/prepare-props.js +18 -75
- package/src/shell/main.js +0 -2
- package/dist/app/assets/index-B-mKk2Z1.js +0 -133
- package/dist/app/assets/index-B-mKk2Z1.js.map +0 -1
- package/dist/worker-runtime.js +0 -28157
package/dist/app/index.html
CHANGED
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
</script>
|
|
21
|
-
<script type="module" crossorigin src="/assets/index-
|
|
21
|
+
<script type="module" crossorigin src="/assets/index-CsqlosR8.js"></script>
|
|
22
22
|
<link rel="modulepreload" crossorigin href="/assets/_commonjsHelpers-CqkleIqs.js">
|
|
23
23
|
<link rel="modulepreload" crossorigin href="/assets/jsx-runtime-0PlsXv6K.js">
|
|
24
24
|
<link rel="modulepreload" crossorigin href="/assets/index-CxUeIbEw.js">
|
|
25
25
|
<link rel="modulepreload" crossorigin href="/assets/_importmap_react-CrAkajDz.js">
|
|
26
26
|
<link rel="modulepreload" crossorigin href="/assets/index-VvOM7xKK.js">
|
|
27
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
27
|
+
<link rel="modulepreload" crossorigin href="/assets/index-Bxvdtdsc.js">
|
|
28
28
|
</head>
|
|
29
29
|
<body>
|
|
30
30
|
<div id="root"></div>
|
package/dist/app/manifest.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.8.
|
|
3
|
-
"entry": "assets/index-
|
|
2
|
+
"version": "0.8.14",
|
|
3
|
+
"entry": "assets/index-CsqlosR8.js",
|
|
4
4
|
"preloads": [
|
|
5
5
|
"assets/_commonjsHelpers-CqkleIqs.js",
|
|
6
6
|
"assets/index-VvOM7xKK.js",
|
|
7
7
|
"assets/index-CxUeIbEw.js",
|
|
8
|
-
"assets/index-
|
|
8
|
+
"assets/index-Bxvdtdsc.js",
|
|
9
9
|
"assets/_importmap_react-CrAkajDz.js",
|
|
10
10
|
"assets/jsx-runtime-0PlsXv6K.js"
|
|
11
11
|
],
|
package/dist/ssr.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isRichSchema,
|
|
1
|
+
import { isRichSchema, deriveCacheKey, createUniweb } from "@uniweb/core";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { renderToString } from "react-dom/server";
|
|
4
4
|
import { buildSectionOverrides } from "@uniweb/theming";
|
|
@@ -71,19 +71,19 @@ function applySchemaToObject(obj, schema) {
|
|
|
71
71
|
if (result[field] === void 0 && defaultValue !== void 0) {
|
|
72
72
|
result[field] = defaultValue;
|
|
73
73
|
}
|
|
74
|
-
if (typeof fieldDef
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
74
|
+
if (typeof fieldDef !== "object") continue;
|
|
75
|
+
if (Array.isArray(fieldDef.enum)) {
|
|
76
|
+
if (result[field] !== void 0 && !fieldDef.enum.includes(result[field]) && defaultValue !== void 0) {
|
|
77
|
+
result[field] = defaultValue;
|
|
79
78
|
}
|
|
80
79
|
}
|
|
81
|
-
if (
|
|
82
|
-
result[field] = applySchemaToObject(result[field], fieldDef.
|
|
80
|
+
if (fieldDef.type === "object" && fieldDef.fields && result[field]) {
|
|
81
|
+
result[field] = applySchemaToObject(result[field], fieldDef.fields);
|
|
83
82
|
}
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
if (fieldDef.type === "array" && fieldDef.items && Array.isArray(result[field])) {
|
|
84
|
+
const items = fieldDef.items;
|
|
85
|
+
if (items && typeof items === "object" && items.type === "object" && items.fields) {
|
|
86
|
+
result[field] = result[field].map((item) => applySchemaToObject(item, items.fields));
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
}
|
|
@@ -149,21 +149,6 @@ function applySchemas(data, schemas) {
|
|
|
149
149
|
}
|
|
150
150
|
return result;
|
|
151
151
|
}
|
|
152
|
-
function applyEntityShape(data, entityMeta, dynamicContext) {
|
|
153
|
-
if (!entityMeta?.type || !data) return data || {};
|
|
154
|
-
const plural = entityMeta.type;
|
|
155
|
-
const result = { ...data };
|
|
156
|
-
if (plural in result && !Array.isArray(result[plural]) && result[plural] != null) {
|
|
157
|
-
result[plural] = [result[plural]];
|
|
158
|
-
}
|
|
159
|
-
if (dynamicContext) {
|
|
160
|
-
const singular = singularize(plural) || plural;
|
|
161
|
-
if (singular !== plural && !(singular in result)) {
|
|
162
|
-
result[singular] = null;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return result;
|
|
166
|
-
}
|
|
167
152
|
function applyDefaults(params, defaults) {
|
|
168
153
|
if (!defaults || Object.keys(defaults).length === 0) {
|
|
169
154
|
return params || {};
|
|
@@ -235,11 +220,6 @@ function prepareProps(block, meta, entityData = null) {
|
|
|
235
220
|
const defaults = meta?.defaults || {};
|
|
236
221
|
const params = applyDefaults(block.properties, defaults);
|
|
237
222
|
let content = guaranteeContentStructure(block.parsedContent);
|
|
238
|
-
const entityMeta = meta?.data || null;
|
|
239
|
-
if (entityMeta && content.data) {
|
|
240
|
-
const dynamicContext = block.dynamicContext || block.page?.dynamicContext || null;
|
|
241
|
-
content.data = applyEntityShape(content.data, entityMeta, dynamicContext);
|
|
242
|
-
}
|
|
243
223
|
const schemas = meta?.schemas || null;
|
|
244
224
|
if (schemas && content.data) {
|
|
245
225
|
content.data = applySchemas(content.data, schemas);
|
package/dist/ssr.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr.js","sources":["../src/prepare-props.js","../src/default-404.js","../src/wire-foundation.js","../src/ssr-renderer.js"],"sourcesContent":["/**\n * Props Preparation for Runtime Guarantees\n *\n * Prepares props for foundation components with:\n * - Param defaults from runtime schema\n * - Guaranteed content structure (no null checks needed)\n * - Entity-shape guarantees on `content.data` when `data.entity` is declared\n * and a cascade match exists (see applyEntityShape below)\n *\n * This enables simpler component code by ensuring predictable prop shapes.\n */\n\nimport { singularize, isRichSchema } from '@uniweb/core'\n\n/**\n * Guarantee item has flat content structure\n *\n * @param {Object} item - Raw item from parser\n * @returns {Object} Item with guaranteed flat structure\n */\nfunction guaranteeItemStructure(item) {\n return {\n title: item.title || '',\n pretitle: item.pretitle || '',\n subtitle: item.subtitle || '',\n paragraphs: item.paragraphs || [],\n links: item.links || [],\n images: item.images || [],\n lists: item.lists || [],\n icons: item.icons || [],\n videos: item.videos || [],\n snippets: item.snippets || [],\n buttons: item.buttons || [],\n data: item.data || {},\n cards: item.cards || [],\n documents: item.documents || [],\n forms: item.forms || [],\n quotes: item.quotes || [],\n headings: item.headings || [],\n ...(item.math && item.math.length ? { math: item.math } : {}),\n }\n}\n\n/**\n * Guarantee content structure exists\n * Returns a flat content object with all standard fields guaranteed to exist\n *\n * @param {Object} parsedContent - Raw parsed content from semantic parser (flat structure)\n * @returns {Object} Content with guaranteed flat structure\n */\nexport function guaranteeContentStructure(parsedContent) {\n const content = parsedContent || {}\n\n return {\n // Flat header fields\n title: content.title || '',\n pretitle: content.pretitle || '',\n subtitle: content.subtitle || '',\n alignment: content.alignment || null,\n\n // Flat body fields\n paragraphs: content.paragraphs || [],\n links: content.links || [],\n images: content.images || [],\n lists: content.lists || [],\n icons: content.icons || [],\n videos: content.videos || [],\n insets: content.insets || [],\n snippets: content.snippets || [],\n buttons: content.buttons || [],\n data: content.data || {},\n cards: content.cards || [],\n documents: content.documents || [],\n forms: content.forms || [],\n quotes: content.quotes || [],\n headings: content.headings || [],\n\n // Rare collections — surfaced only when present so pages that don't\n // use them don't pay the allocation cost. Foundations that need them\n // should check for presence (content.math?.length) or use\n // content.sequence for in-order rendering.\n ...(content.math && content.math.length ? { math: content.math } : {}),\n\n // Items with guaranteed structure\n items: (content.items || []).map(guaranteeItemStructure),\n\n // Sequence for ordered rendering\n sequence: content.sequence || [],\n\n // Preserve raw content if present\n raw: content.raw,\n }\n}\n\n/**\n * Apply a schema to a single object\n * Only processes fields defined in the schema, preserves unknown fields\n *\n * @param {Object} obj - The object to process\n * @param {Object} schema - Schema definition (fieldName -> fieldDef)\n * @returns {Object} Object with schema defaults applied\n */\nfunction applySchemaToObject(obj, schema) {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {\n return obj\n }\n\n const result = { ...obj }\n\n for (const [field, fieldDef] of Object.entries(schema)) {\n // Get the default value - handle both shorthand and full form\n const defaultValue = typeof fieldDef === 'object' ? fieldDef.default : undefined\n\n // Apply default if field is missing and default exists\n if (result[field] === undefined && defaultValue !== undefined) {\n result[field] = defaultValue\n }\n\n // For select fields with options, apply default if value is not among valid options\n if (typeof fieldDef === 'object' && fieldDef.options && Array.isArray(fieldDef.options)) {\n if (result[field] !== undefined && !fieldDef.options.includes(result[field])) {\n // Value exists but is not valid - apply default if available\n if (defaultValue !== undefined) {\n result[field] = defaultValue\n }\n }\n }\n\n // Handle nested object schema\n if (typeof fieldDef === 'object' && fieldDef.type === 'object' && fieldDef.schema && result[field]) {\n result[field] = applySchemaToObject(result[field], fieldDef.schema)\n }\n\n // Handle array with inline schema\n if (typeof fieldDef === 'object' && fieldDef.type === 'array' && fieldDef.of && result[field]) {\n if (typeof fieldDef.of === 'object') {\n result[field] = result[field].map(item => applySchemaToObject(item, fieldDef.of))\n }\n }\n }\n\n return result\n}\n\n/**\n * Apply a schema to a value (object or array of objects)\n *\n * @param {Object|Array} value - The value to process\n * @param {Object} schema - Schema definition\n * @returns {Object|Array} Value with schema defaults applied\n */\nfunction applySchemaToValue(value, schema) {\n if (Array.isArray(value)) {\n return value.map(item => applySchemaToObject(item, schema))\n }\n return applySchemaToObject(value, schema)\n}\n\n/**\n * Apply field defaults from a rich form `fields` array to an object.\n *\n * Recurses into `type: 'form'` (composite arrays with childSchema) and\n * `type: 'nestedObject'` / `type: 'object'` (single nested objects).\n *\n * Conditional visibility (`field.condition`) is not yet applied here —\n * components receive all fields the author filled plus defaults; hiding\n * is a later pass that requires the shared evaluateCondition util.\n *\n * @param {Object} obj - Row data (object keyed by field id)\n * @param {Array} fields - Rich field definitions\n * @returns {Object} - obj with defaults filled in\n */\nfunction applyRichFieldDefaults(obj, fields) {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return obj\n if (!Array.isArray(fields)) return obj\n\n const result = { ...obj }\n\n for (const field of fields) {\n if (!field || typeof field !== 'object' || !field.id) continue\n const id = field.id\n\n if (result[id] === undefined && field.default !== undefined) {\n result[id] = field.default\n }\n\n if (field.type === 'form' && field.childSchema && Array.isArray(result[id])) {\n result[id] = result[id].map(item =>\n applyRichFieldDefaults(item, field.childSchema.fields)\n )\n } else if (\n (field.type === 'nestedObject' || field.type === 'object') &&\n Array.isArray(field.fields) &&\n result[id] &&\n typeof result[id] === 'object'\n ) {\n result[id] = applyRichFieldDefaults(result[id], field.fields)\n }\n }\n\n return result\n}\n\n/**\n * Apply a rich form schema to its stored value.\n *\n * Shape rules:\n * - composite (isComposite=true) → value is array of childSchema rows\n * - when `childCollection` is set, value may be `{ [childCollection]: [...] }`\n * - non-composite → value is a single object keyed by field id\n */\nfunction applyRichSchemaToValue(value, schema) {\n if (value == null) return value\n\n if (schema.isComposite && schema.childSchema) {\n const childFields = schema.childSchema.fields\n const collectionKey = schema.childCollection\n\n if (collectionKey && value && typeof value === 'object' && !Array.isArray(value)) {\n const arr = Array.isArray(value[collectionKey]) ? value[collectionKey] : []\n return {\n ...value,\n [collectionKey]: arr.map(row => applyRichFieldDefaults(row, childFields)),\n }\n }\n\n if (Array.isArray(value)) {\n return value.map(row => applyRichFieldDefaults(row, childFields))\n }\n\n return value\n }\n\n if (Array.isArray(schema.fields)) {\n return applyRichFieldDefaults(value, schema.fields)\n }\n\n return value\n}\n\n/**\n * Apply schemas to content.data\n * Only processes tags that have a matching schema, leaves others untouched\n *\n * @param {Object} data - The data object from content\n * @param {Object} schemas - Schema definitions from runtime meta\n * @returns {Object} Data with schemas applied\n */\nexport function applySchemas(data, schemas) {\n if (!schemas || !data || typeof data !== 'object') {\n return data || {}\n }\n\n const result = { ...data }\n\n for (const [tag, rawValue] of Object.entries(data)) {\n const schema = schemas[tag]\n if (!schema) continue // No schema for this tag - leave as-is\n\n result[tag] = isRichSchema(schema)\n ? applyRichSchemaToValue(rawValue, schema)\n : applySchemaToValue(rawValue, schema)\n }\n\n return result\n}\n\n/**\n * Apply entity-shape guarantees based on `data.entity` declaration.\n *\n * When a component declares `data: { entity: 'articles' }` **and** the\n * cascade produced a match for that schema (`content.data.articles` is\n * present), normalize:\n * - `content.data.articles` to an array (missing → `[]` is *not* added;\n * absence is preserved as a signal of \"no source\"). This only shapes\n * when a cascade match exists — if the key is missing entirely, it\n * stays missing.\n * - On template pages, `content.data[singular(entity)]` is guaranteed\n * to exist (defaulting to `null`) so components can do\n * `if (!article) return <NotFound />` without a `?.` chain.\n *\n * The `undefined` vs `[]` vs `null` distinctions are load-bearing:\n * - `content.data.articles === undefined` → no query for this entity\n * - `content.data.articles === []` → query ran, returned empty\n * - `content.data.article === null` → on template page, item not found\n * - `content.data.article === {...}` → on template page, item resolved\n *\n * @param {Object} data - content.data (already merged with entity data)\n * @param {Object|null} entityMeta - runtime data meta (`{ type, limit }`)\n * @param {Object|null} dynamicContext - set when block is on a template page\n * @returns {Object} data with entity-shape guarantees applied\n */\nexport function applyEntityShape(data, entityMeta, dynamicContext) {\n if (!entityMeta?.type || !data) return data || {}\n\n const plural = entityMeta.type\n const result = { ...data }\n\n // Only shape the collection when it was delivered. Absence stays absence.\n if (plural in result && !Array.isArray(result[plural]) && result[plural] != null) {\n // Delivered but wrong shape — coerce to array (single item → [item]).\n result[plural] = [result[plural]]\n }\n\n // On template pages, guarantee the singular key exists (null = not found).\n if (dynamicContext) {\n const singular = singularize(plural) || plural\n if (singular !== plural && !(singular in result)) {\n result[singular] = null\n }\n }\n\n return result\n}\n\n/**\n * Apply param defaults from runtime schema\n *\n * @param {Object} params - Params from frontmatter\n * @param {Object} defaults - Default values from runtime schema\n * @returns {Object} Merged params with defaults applied\n */\nexport function applyDefaults(params, defaults) {\n if (!defaults || Object.keys(defaults).length === 0) {\n return params || {}\n }\n\n return {\n ...defaults,\n ...(params || {}),\n }\n}\n\n/**\n * Merge entity data onto a block's parsedContent.data.\n *\n * Section-level data already on the block (from prerender fetches via\n * blockData.parsedContent.data in the Block constructor) takes priority;\n * entity data only fills missing keys. Mutates `block.parsedContent.data`\n * in place so the vanilla JS layer holds the assembled data and\n * subsequent reads see the same shape.\n */\nfunction mergeEntityData(block, entityData) {\n if (!entityData) return\n const current = block.parsedContent.data || {}\n let changed = false\n const merged = { ...current }\n for (const key of Object.keys(entityData)) {\n if (merged[key] === undefined) {\n merged[key] = entityData[key]\n changed = true\n }\n }\n if (changed) {\n block.parsedContent.data = merged\n }\n}\n\n/**\n * Run the foundation-level data handler on a block, if one is\n * registered. Runs after entity data merge and before the content\n * handler — the handler sees the fully assembled data and can filter,\n * reshape, or augment it before Loom (or any content transform) runs.\n *\n * The handler receives `(data, block)` where data is\n * `block.parsedContent.data`. It returns a new data object, or\n * null/undefined for no change. The returned data replaces\n * `block.parsedContent.data` for all downstream processing — both\n * the content handler and the component see the transformed data.\n *\n * Skipped when the block is still waiting on async data\n * (`block.dataLoading`), or when no handler is registered.\n * Errors are logged and the original data is preserved.\n */\nfunction runDataHandler(block) {\n if (block.dataLoading) return\n const handler = globalThis.uniweb?.foundationConfig?.handlers?.data\n if (typeof handler !== 'function') return\n\n try {\n const result = handler(block.parsedContent.data, block)\n if (result != null && result !== block.parsedContent.data) {\n block.parsedContent.data = result\n }\n } catch (err) {\n console.error('Foundation data handler failed:', err)\n }\n}\n\n/**\n * Run the foundation-level content handler on a block, if one is\n * registered. Runs at prop-preparation time — after the data handler\n * has had a chance to filter/reshape the data — so the handler sees\n * the fully assembled (and possibly filtered) data. Replaces\n * `block.parsedContent` in place with the re-parsed, instantiated\n * form. The handler receives `(data, block)` and reads raw\n * ProseMirror from `block.rawContent`.\n *\n * Skipped when the block is still waiting on async data\n * (`block.dataLoading`), when no handler is registered, when the\n * block has no raw content, when the handler returns a no-change\n * signal (undefined, null, or the same reference as rawContent), or\n * when the handler throws. Errors are logged via `console.error`.\n */\nfunction runContentHandler(block) {\n if (block.dataLoading) return\n const handler = globalThis.uniweb?.foundationConfig?.handlers?.content\n if (typeof handler !== 'function') return\n if (!block.rawContent || Object.keys(block.rawContent).length === 0) return\n\n try {\n const transformed = handler(block.parsedContent.data, block)\n if (!transformed || transformed === block.rawContent) return\n const reparsed = block.parseContent(transformed)\n reparsed.data = block.parsedContent.data\n block.parsedContent = reparsed\n block.items = reparsed.items || []\n } catch (err) {\n console.error('Foundation content handler failed:', err)\n }\n}\n\n/**\n * Run the foundation-level props handler on the final { content, params }\n * before they reach the component. Runs after content parsing, param\n * defaults, content guarantees, and schema application — the handler\n * sees the exact shape the component would receive and can modify it.\n *\n * The handler receives `(content, params, block)` and returns a new\n * `{ content, params }` object, or null/undefined for no change.\n *\n * Use cases: post-parse content reshaping, computed fields derived\n * from both content and params, param-driven content reorganization.\n * Errors are logged and the original props are preserved.\n */\nfunction runPropsHandler(content, params, block) {\n const handler = globalThis.uniweb?.foundationConfig?.handlers?.props\n if (typeof handler !== 'function') return null\n\n try {\n const result = handler(content, params, block)\n if (result && typeof result === 'object') return result\n } catch (err) {\n console.error('Foundation props handler failed:', err)\n }\n return null\n}\n\n/**\n * Prepare props for a component with runtime guarantees.\n *\n * Does the full content-assembly pipeline in one place so both\n * renderers (`BlockRenderer.jsx` CSR and `ssr-renderer.js` SSG) share\n * the same code path:\n *\n * 1. Merge entity data (resolved by EntityStore) onto\n * `block.parsedContent.data`.\n * 2. Run the foundation data handler (if registered) to filter or\n * reshape the assembled data.\n * 3. Run the foundation content handler (if registered) on the\n * block. This may replace `block.parsedContent` with a re-parsed,\n * instantiated version.\n * 4. Apply param defaults from meta.\n * 5. Build the guaranteed content structure.\n * 6. Apply schemas to content.data.\n * 7. Run the foundation props handler (if registered) for\n * post-processing of the final { content, params }.\n *\n * Steps 1–3 mutate the block (vanilla JS layer). Steps 4–7 are\n * pure derivations of the block's now-assembled state.\n *\n * @param {Object} block - The block instance\n * @param {Object} meta - Runtime metadata for the component (from meta[componentName])\n * @param {Object|null} [entityData] - Entity data resolved by EntityStore (null if none)\n * @returns {Object} Prepared props: { content, params }\n */\nexport function prepareProps(block, meta, entityData = null) {\n mergeEntityData(block, entityData)\n runDataHandler(block)\n runContentHandler(block)\n\n // Apply param defaults\n const defaults = meta?.defaults || {}\n const params = applyDefaults(block.properties, defaults)\n\n // Guarantee content structure\n let content = guaranteeContentStructure(block.parsedContent)\n\n // Apply entity-shape guarantees when the component declared `data.entity`\n // and a cascade match exists. Preserves `undefined` vs `[]` vs `null`\n // distinctions so components can differentiate \"no source\" from\n // \"empty source\" from \"template item not found.\"\n const entityMeta = meta?.data || null\n if (entityMeta && content.data) {\n const dynamicContext = block.dynamicContext || block.page?.dynamicContext || null\n content.data = applyEntityShape(content.data, entityMeta, dynamicContext)\n }\n\n // Apply schemas to content.data\n const schemas = meta?.schemas || null\n if (schemas && content.data) {\n content.data = applySchemas(content.data, schemas)\n }\n\n // Post-process hook\n const adjusted = runPropsHandler(content, params, block)\n if (adjusted) {\n return {\n content: adjusted.content || content,\n params: adjusted.params || params,\n }\n }\n\n return { content, params }\n}\n\n/**\n * Get runtime metadata for a component from the global uniweb instance\n *\n * @param {string} componentName\n * @returns {Object|null}\n */\nexport function getComponentMeta(componentName) {\n return globalThis.uniweb?.getComponentMeta?.(componentName) || null\n}\n\n/**\n * Get default param values for a component\n *\n * @param {string} componentName\n * @returns {Object}\n */\nexport function getComponentDefaults(componentName) {\n return globalThis.uniweb?.getComponentDefaults?.(componentName) || {}\n}\n","/**\n * Default 404 Page Content\n *\n * Single source of truth for the fallback 404 page shown when a site\n * has no custom 404 page defined. Used by:\n * - PageRenderer.jsx (client-side, as React elements)\n * - ssr-renderer.js generate404Html (build-time, as HTML string)\n *\n * The wrapper uses min-height + flex centering so the 404 content\n * renders at the same position regardless of parent layout context.\n * This prevents a visible flash when React hydrates over the SSR content.\n */\n\nimport React from 'react'\n\nconst styles = {\n wrapper: {\n minHeight: '80vh',\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '2rem',\n textAlign: 'center',\n },\n heading: { fontSize: '3rem', fontWeight: 'bold', color: '#1f2937', marginBottom: '1rem' },\n message: { color: '#64748b', marginBottom: '2rem' },\n link: { color: '#3b82f6', textDecoration: 'underline' },\n}\n\n/**\n * React element for client-side rendering (PageRenderer).\n * Reads basePath from the runtime so the homepage link works\n * in subdirectory deployments (e.g., /sites/testproject).\n */\nexport function Default404() {\n const basePath = globalThis.uniweb?.activeWebsite?.basePath || ''\n const homeHref = basePath ? `${basePath}/` : '/'\n return React.createElement('div', { className: 'page-not-found', style: styles.wrapper },\n React.createElement('h1', { style: styles.heading }, '404'),\n React.createElement('p', { style: styles.message }, 'Page not found'),\n React.createElement('a', { href: homeHref, style: styles.link }, 'Go to homepage')\n )\n}\n\n/**\n * Static HTML string for SSR injection (generate404Html).\n *\n * @param {string} [basePath] - Base path prefix for the homepage link (e.g., '/sites/testproject')\n */\nexport function default404Html(basePath = '') {\n const homeHref = basePath ? `${basePath}/` : '/'\n return (\n `<div class=\"page-not-found\" style=\"min-height:80vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem;text-align:center\">` +\n `<h1 style=\"font-size:3rem;font-weight:bold;color:#1f2937;margin-bottom:1rem\">404</h1>` +\n `<p style=\"color:#64748b;margin-bottom:2rem\">Page not found</p>` +\n `<a href=\"${homeHref}\" style=\"color:#3b82f6;text-decoration:underline\">Go to homepage</a>` +\n `</div>`\n )\n}\n","/**\n * Layer-2 wiring helpers: runtime ↔ Uniweb singleton.\n *\n * After `createUniweb()` constructs the singleton, the runtime fills a\n * few declared slots on it before the first render — foundation\n * capabilities (`defaultInsets`, `xref.build()`), per-request data\n * hydration into `website.dataStore`, locale-scoped content slicing.\n * This step is identical in every environment (browser SPA, SSG\n * prerender, cloud SSR) because it's plain data manipulation on a JS\n * object: no React rendering happens here, no hooks are called, no DOM\n * is touched, no `react-dom/server` is needed.\n *\n * That's why these helpers live in one file imported by both\n * `setup.js` (browser boot) and `ssr-renderer.js` (SSG/cloud-SSR boot),\n * instead of being duplicated into each. Things that genuinely differ\n * between environments — routing components, icon-cache hydration from\n * the DOM, the per-page render loop — stay in the per-environment\n * entries; these helpers cover only the environment-agnostic L2 work.\n *\n * Keeping this file React-free matters for the SPA bundle: `setup.js`\n * pulls `wire-foundation.js` directly, but it must NOT transitively\n * pull `ssr-renderer.js` (which imports `react-dom/server`). The L2\n * helpers therefore live here, while the L3-composing\n * `initPrerenderForLocale` lives in `ssr-renderer.js`.\n *\n * Adding a new framework-level capability:\n * 1. Read the foundation declaration via `foundation.default.capabilities.<name>`.\n * 2. Apply it to the uniweb singleton (set a slot, call a build hook,\n * register something on `activeWebsite`).\n * 3. Provide a runtime fallback if the capability is one foundations\n * may legitimately not declare (see `FallbackRef`).\n *\n * Foundation export shape contract: the runtime always loads the\n * **built** foundation artifact (`dist/foundation.js`) via\n * `loadFoundation()` in `foundation-loader.js`, which does `import(url)`\n * and returns a module namespace. The build pipeline\n * (`framework/build/src/generate-entry.js`) wraps the foundation's\n * source default export under `default.capabilities.*`, so the runtime\n * sees a single canonical shape with no need for fallback chains. See\n * `framework/CLAUDE.md` \"Three-Layer Runtime Model\" for the rationale\n * and for how this differs from `@uniweb/press` / `@uniweb/unipress`,\n * which DO need to handle a second shape because they're sometimes\n * called from inside a foundation bundle (where the foundation imports\n * its own source as a bare default object).\n */\n\nimport React from 'react'\nimport { deriveCacheKey } from '@uniweb/core'\n\n/**\n * Renders unhandled `[#id]` cross-reference markers as plain text. Used\n * when the active foundation didn't declare its own `<Ref>` via\n * `defaultInsets`. Pure `React.createElement` — safe in every\n * environment, including the hook-free SSR pipeline.\n *\n * Foundations that support cross-references override this by exporting\n * `defaultInsets: { Ref }` (with kit's xref-aware Ref) from their\n * source — the build pipeline carries it through into\n * `default.capabilities.defaultInsets`.\n */\nexport function FallbackRef({ params }) {\n return React.createElement(\n 'span',\n { className: 'xref xref--unhandled' },\n `[${params?.key || '?'}]`,\n )\n}\n\n/**\n * Wire foundation-declared capabilities onto a freshly constructed\n * Uniweb singleton. Called once, after `createUniweb()`, before any\n * rendering. Identical for SPA, SSG, and cloud SSR.\n *\n * @param {import('@uniweb/core').default} uniweb - From createUniweb(...).\n * @param {object} foundation - Loaded foundation module (built shape).\n */\nexport function wireFoundationCapabilities(uniweb, foundation) {\n const caps = foundation?.default?.capabilities || {}\n\n // defaultInsets: framework provides FallbackRef as the floor;\n // foundation overrides win. `getComponent()` on the Uniweb singleton\n // (core/uniweb.js) falls back to defaultInsets[name] when no\n // foundation/extension component matches — that's how `<Ref>` becomes\n // available to every foundation without each one having to register\n // it explicitly.\n uniweb.defaultInsets = { Ref: FallbackRef, ...(caps.defaultInsets || {}) }\n\n // xref: foundations supporting cross-references export\n // `xref.build(website, { foundationKinds })`. The runtime can't\n // import kit directly (kit is bundled into each foundation, not into\n // runtime — see CLAUDE.md gotcha #9 on tree-shaking), so it\n // dispatches through the foundation's reference. Foundations without\n // xref skip this entirely; kit's xref module never enters their\n // bundle thanks to tree-shaking at foundation-build time.\n if (caps.xref?.build && uniweb.activeWebsite) {\n caps.xref.build(uniweb.activeWebsite, {\n foundationKinds: caps.xref.kinds || {},\n })\n }\n}\n\n/**\n * Slice a multi-locale site-content payload to one locale.\n *\n * Sites published through the editor ship a single payload that carries\n * all locales nested under `content.locales[locale]` — `pages`, optional\n * `layouts`, and a `config` overlay. The default locale lives at the\n * top level (no nesting). This helper extracts the requested locale's\n * view as a fresh content object the rest of the runtime can consume\n * unchanged.\n *\n * Returns `content` as-is when `locale` is the default, missing, or not\n * present in `content.locales` — callers that already hand us locale-\n * scoped content (e.g., the framework's per-locale SSG path that loads\n * each `dist/{locale}/site-content.json` separately) get pass-through\n * behavior.\n *\n * The shape comes from the editor's publish payload, which is the\n * production canonical for multi-locale content (the Cloudflare Worker\n * SSR path consumes it directly). Build-time SSG pre-flattens to one\n * file per locale and so falls into the pass-through case.\n *\n * @param {Object} content - Site content payload, possibly multi-locale.\n * @param {string} locale - Requested locale code.\n * @returns {Object} Content scoped to the requested locale.\n */\nexport function sliceContentForLocale(content, locale) {\n const defaultLang = content?.config?.defaultLanguage || 'en'\n const locData = content?.locales?.[locale]\n if (!locale || locale === defaultLang || !locData) return content\n return {\n pages: locData.pages,\n layouts: locData.layouts || content.layouts,\n config: {\n ...locData.config,\n i18n: content.config?.i18n,\n activeLocale: locale,\n },\n }\n}\n\n/**\n * Pre-populate a Website's DataStore from build-time / publish-time\n * fetched data so the dispatcher's first probe hits the cache instead\n * of refetching.\n *\n * The cache key MUST go through `deriveCacheKey(entry.config)` and the\n * value MUST be wrapped as `{ data }` — otherwise the dispatcher's\n * lookup at `_dataStore.get(deriveCacheKey(request))` misses every\n * time and `cached.data` reads `undefined`. Three call sites used to\n * inline this loop independently (browser SPA, Node SSG, Cloudflare\n * Worker SSR); the Cloudflare one was using the wrong shape, silently\n * killing prefetched-data reuse in production. This helper is the one\n * canonical implementation.\n *\n * @param {import('@uniweb/core').Website} website\n * @param {Array<{config: Object, data: any}>} fetchedData\n */\nexport function hydrateDataStore(website, fetchedData) {\n if (!website?.dataStore || !fetchedData?.length) return\n for (const entry of fetchedData) {\n website.dataStore.set(deriveCacheKey(entry.config), { data: entry.data })\n }\n}\n","/**\n * SSR Renderer\n *\n * Hook-free rendering pipeline for SSG (build) and cloud SSR (unicloud).\n * Mirrors BlockRenderer.jsx + Background.jsx using React.createElement\n * directly — no hooks, no JSX, no browser APIs.\n *\n * This is the single source of truth for how blocks render during prerender.\n * When modifying BlockRenderer.jsx or Background.jsx, update this file to match.\n *\n * Exports three layers:\n * 1. Rendering functions (renderBlock, renderBlocks, renderLayout, renderBackground)\n * 2. Initialization (initPrerender, prefetchIcons)\n * 3. Per-page rendering (renderPage, classifyRenderError, injectPageContent, escapeHtml)\n */\n\nimport React from 'react'\nimport { renderToString } from 'react-dom/server'\nimport { createUniweb } from '@uniweb/core'\nimport { buildSectionOverrides } from '@uniweb/theming'\nimport { prepareProps, getComponentMeta } from './prepare-props.js'\nimport { default404Html } from './default-404.js'\nimport {\n wireFoundationCapabilities,\n sliceContentForLocale,\n hydrateDataStore,\n} from './wire-foundation.js'\n\n// Re-export L2 helpers so the public `@uniweb/runtime/ssr` surface\n// carries everything an SSR consumer needs from one entry point.\nexport { sliceContentForLocale, hydrateDataStore }\n\n// ============================================================================\n// Layer 1: Rendering functions\n// ============================================================================\n\n/**\n * Valid color contexts for section theming\n */\nconst VALID_CONTEXTS = ['light', 'medium', 'dark']\n\n/**\n * Build wrapper props from block configuration.\n * Mirrors getWrapperProps in BlockRenderer.jsx.\n */\nexport function getWrapperProps(block) {\n const theme = block.themeName\n const blockClassName = block.state?.className || ''\n\n // Empty themeName = Auto → no context class → inherits tokens from :root\n // Non-empty = Pinned → context class sets tokens directly on the element\n let contextClass = ''\n if (theme && VALID_CONTEXTS.includes(theme)) {\n contextClass = `context-${theme}`\n }\n\n let className = contextClass\n if (blockClassName) {\n className = className ? `${className} ${blockClassName}` : blockClassName\n }\n\n const { background = {} } = block.standardOptions\n const style = {}\n\n // If background has content, ensure relative positioning and a stacking context\n // so the background's z-index stays contained within this section.\n if (background.mode) {\n style.position = 'relative'\n style.isolation = 'isolate'\n }\n\n // Apply context overrides as inline CSS custom properties\n if (block.contextOverrides) {\n for (const [key, value] of Object.entries(block.contextOverrides)) {\n style[`--${key}`] = value\n }\n }\n\n // Use stableId for DOM ID if available (stable across reordering)\n const sectionId = block.stableId || block.id\n\n return { id: `section-${sectionId}`, style, className, background }\n}\n\n/**\n * Convert hex/rgb color to rgba with opacity.\n * Mirrors withOpacity() in Background.jsx.\n */\nfunction withOpacity(color, opacity) {\n if (color.startsWith('#')) {\n const r = parseInt(color.slice(1, 3), 16)\n const g = parseInt(color.slice(3, 5), 16)\n const b = parseInt(color.slice(5, 7), 16)\n return `rgba(${r}, ${g}, ${b}, ${opacity})`\n }\n if (color.startsWith('rgb')) {\n const match = color.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/)\n if (match) {\n return `rgba(${match[1]}, ${match[2]}, ${match[3]}, ${opacity})`\n }\n }\n return color\n}\n\n/**\n * Resolve a URL against the site's base path.\n * Mirrors resolveUrl() in Background.jsx.\n */\nfunction resolveUrl(url) {\n if (!url || !url.startsWith('/')) return url\n const basePath = globalThis.uniweb?.activeWebsite?.basePath || ''\n if (!basePath) return url\n if (url.startsWith(basePath + '/') || url === basePath) return url\n return basePath + url\n}\n\n/**\n * Render a background element for SSR.\n * Mirrors Background.jsx (color, gradient, image — not video).\n * Video backgrounds require JS for autoplay and are skipped during SSR.\n */\nexport function renderBackground(background) {\n if (!background?.mode) return null\n\n const containerStyle = {\n position: 'absolute',\n inset: '0',\n overflow: 'hidden',\n zIndex: 0,\n }\n\n const children = []\n\n // Color background\n if (background.mode === 'color' && background.color) {\n children.push(\n React.createElement('div', {\n key: 'bg-color',\n className: 'background-color',\n style: { position: 'absolute', inset: '0', backgroundColor: background.color },\n 'aria-hidden': 'true',\n })\n )\n }\n\n // Gradient background (supports string or object with opacity)\n if (background.mode === 'gradient' && background.gradient) {\n const g = background.gradient\n\n let bgValue\n if (typeof g === 'string') {\n bgValue = g\n } else {\n const {\n start = 'transparent',\n end = 'transparent',\n angle = 0,\n startPosition = 0,\n endPosition = 100,\n startOpacity = 1,\n endOpacity = 1,\n } = g\n const startColor = startOpacity < 1 ? withOpacity(start, startOpacity) : start\n const endColor = endOpacity < 1 ? withOpacity(end, endOpacity) : end\n bgValue = `linear-gradient(${angle}deg, ${startColor} ${startPosition}%, ${endColor} ${endPosition}%)`\n }\n\n children.push(\n React.createElement('div', {\n key: 'bg-gradient',\n className: 'background-gradient',\n style: { position: 'absolute', inset: '0', background: bgValue },\n 'aria-hidden': 'true',\n })\n )\n }\n\n // Image background\n if (background.mode === 'image' && background.image?.src) {\n const img = background.image\n children.push(\n React.createElement('div', {\n key: 'bg-image',\n className: 'background-image',\n style: {\n position: 'absolute',\n inset: '0',\n backgroundImage: `url(${resolveUrl(img.src)})`,\n backgroundPosition: img.position || 'center',\n backgroundSize: img.size || 'cover',\n backgroundRepeat: 'no-repeat',\n },\n 'aria-hidden': 'true',\n })\n )\n }\n\n // Overlay (gradient or solid)\n if (background.overlay?.enabled) {\n const ov = background.overlay\n let overlayStyle\n\n if (ov.gradient) {\n const g = ov.gradient\n overlayStyle = {\n position: 'absolute', inset: '0', pointerEvents: 'none',\n background: `linear-gradient(${g.angle || 180}deg, ${g.start || 'rgba(0,0,0,0.7)'} ${g.startPosition || 0}%, ${g.end || 'rgba(0,0,0,0)'} ${g.endPosition || 100}%)`,\n opacity: ov.opacity ?? 0.5,\n }\n } else {\n const baseColor = ov.type === 'light' ? '255, 255, 255' : '0, 0, 0'\n overlayStyle = {\n position: 'absolute', inset: '0', pointerEvents: 'none',\n backgroundColor: `rgba(${baseColor}, ${ov.opacity ?? 0.5})`,\n }\n }\n\n children.push(\n React.createElement('div', {\n key: 'bg-overlay',\n className: ov.gradient ? 'background-overlay background-overlay--gradient' : 'background-overlay background-overlay--solid',\n style: overlayStyle,\n 'aria-hidden': 'true',\n })\n )\n }\n\n if (children.length === 0) return null\n\n return React.createElement('div', {\n className: `background background--${background.mode}`,\n style: containerStyle,\n 'aria-hidden': 'true',\n }, ...children)\n}\n\n/**\n * Render a single block for SSR.\n * Mirrors BlockRenderer.jsx but without hooks (no runtime data fetching).\n *\n * Two modes (mirrors client BlockRenderer):\n * - Bare (as=null/false): component only, no wrapper\n * - Section (as='section'/'div'/etc.): full treatment with wrapper, context, background\n *\n * @param {Block} block - Block instance to render\n * @param {Object} [options]\n * @param {string|null} [options.as='section'] - Wrapper element tag, or null/false for bare mode\n * @returns {React.ReactElement}\n */\nexport function renderBlock(block, { as = 'section' } = {}) {\n const Component = block.initComponent()\n\n if (!Component) {\n return React.createElement('div', {\n className: 'block-error',\n style: { padding: '1rem', background: '#fef2f2', color: '#dc2626' },\n }, `Component not found: ${block.type}`)\n }\n\n // Resolve inherited entity data synchronously (SSG has no async).\n // EntityStore walks page/site hierarchy to find data matching meta.inheritData.\n const meta = getComponentMeta(block.type)\n const entityStore = block.website?.entityStore\n let entityData = null\n if (entityStore) {\n const resolved = entityStore.resolve(block, meta)\n if (resolved.status === 'ready') entityData = resolved.data\n }\n\n // Build content and params with runtime guarantees.\n // prepareProps handles the full pipeline: entity data merge,\n // foundation content handler invocation, guaranteed content\n // structure, schema application, and param defaults.\n // See prepare-props.js for the pipeline details.\n const prepared = prepareProps(block, meta, entityData)\n const params = prepared.params\n const content = { ...prepared.content, ...block.properties }\n\n const componentProps = { content, params, block }\n\n // Bare mode: component only, no wrapper or section chrome.\n // Used by ChildBlocks for grid cells, tab panels, inline children, insets.\n if (!as) {\n return React.createElement(Component, componentProps)\n }\n\n // Section mode: full treatment with wrapper, context classes, background.\n const { background, ...wrapperProps } = getWrapperProps(block)\n\n // Merge Component.className (static classes declared on the component function)\n const componentClassName = Component.className\n if (componentClassName) {\n wrapperProps.className = wrapperProps.className\n ? `${wrapperProps.className} ${componentClassName}`\n : componentClassName\n }\n\n // Check if component handles its own background\n const hasBackground = background?.mode && meta?.background !== 'self'\n block.hasBackground = hasBackground\n\n // Determine wrapper element:\n // - Explicit as (not 'section') → use as prop directly\n // - Component.as → use component's declared tag (e.g., Header.as = 'header')\n // - fallback → 'section'\n const wrapperTag = as !== 'section' ? as : (Component.as || 'section')\n\n if (hasBackground) {\n return React.createElement(wrapperTag, wrapperProps,\n renderBackground(background),\n React.createElement('div', { style: { position: 'relative', zIndex: 10 } },\n React.createElement(Component, componentProps)\n )\n )\n }\n\n return React.createElement(wrapperTag, wrapperProps,\n React.createElement(Component, componentProps)\n )\n}\n\n/**\n * Render an array of blocks for SSR.\n */\nexport function renderBlocks(blocks) {\n if (!blocks || blocks.length === 0) return null\n return blocks.map((block, index) =>\n React.createElement(React.Fragment, { key: block.id || index },\n renderBlock(block)\n )\n )\n}\n\n/**\n * Render page layout for SSR.\n * Mirrors Layout.jsx but without hooks.\n */\nexport function renderLayout(page, website) {\n const layoutName = page.getLayoutName()\n const RemoteLayout = website.getRemoteLayout(layoutName)\n const layoutMeta = website.getLayoutMeta(layoutName)\n\n const bodyBlocks = page.getBodyBlocks()\n const areas = page.getLayoutAreas()\n\n const bodyElement = bodyBlocks ? renderBlocks(bodyBlocks) : null\n const areaElements = {}\n for (const [name, blocks] of Object.entries(areas)) {\n areaElements[name] = renderBlocks(blocks)\n }\n\n if (RemoteLayout) {\n const params = { ...(layoutMeta?.defaults || {}), ...(page.getLayoutParams() || {}) }\n return React.createElement(RemoteLayout, {\n page, website, params,\n body: bodyElement,\n ...areaElements,\n })\n }\n\n // Default layout\n return React.createElement(React.Fragment, null,\n areaElements.header && React.createElement('header', null, areaElements.header),\n bodyElement && React.createElement('main', null, bodyElement),\n areaElements.footer && React.createElement('footer', null, areaElements.footer)\n )\n}\n\n// ============================================================================\n// Layer 2: Initialization\n// ============================================================================\n\n/**\n * Construct a Uniweb singleton scoped to a single locale.\n *\n * Combines the three steps that every SSR consumer (browser SPA, Node\n * SSG, Cloudflare Worker SSR) needs in the same order: slice the\n * multi-locale content payload, run `initPrerender` (which builds the\n * Website + wires foundation capabilities), then `setActiveLocale` so\n * `website.activeLang` stays in sync with what the page is rendering\n * for. Caller still owns DataStore hydration (per-request data differs\n * between requests; locale construction can be cached).\n *\n * @param {Object} content - Site content payload (possibly multi-locale).\n * @param {Object} foundation - Loaded foundation module.\n * @param {string} locale - Locale code to render in.\n * @param {Array<Object>|Object} [extensionsOrOptions] - Same shape as initPrerender's\n * third arg: an extensions array, or an options object when no extensions.\n * @param {Object} [maybeOptions] - Options object when extensions are passed.\n * @returns {import('@uniweb/core').default} The configured Uniweb singleton.\n */\nexport function initPrerenderForLocale(content, foundation, locale, extensionsOrOptions, maybeOptions) {\n const localeContent = sliceContentForLocale(content, locale)\n const uniweb = initPrerender(localeContent, foundation, extensionsOrOptions, maybeOptions)\n const defaultLang = content?.config?.defaultLanguage || 'en'\n if (locale && locale !== defaultLang && uniweb.activeWebsite?.setActiveLocale) {\n uniweb.activeWebsite.setActiveLocale(locale)\n }\n return uniweb\n}\n\n/**\n * Create and configure the Uniweb runtime for prerendering.\n *\n * Handles the full initialization sequence in the correct order:\n * createUniweb → setFoundation → capabilities → layoutMeta → basePath → childBlockRenderer.\n *\n * Returns the configured uniweb instance. Consumers can add extras after:\n * - Build: pre-populate DataStore, load extensions\n * - Unicloud: (none needed — payload is complete)\n *\n * NOTE: Does NOT clone content. Cloning is the consumer's responsibility\n * (build modifies content before init; unicloud clones upfront).\n *\n * @param {Object} content - Site content JSON (pages, config, hierarchy)\n * @param {Object} foundation - Loaded foundation module\n * @param {Object} [options]\n * @param {function} [options.onProgress] - Progress callback\n * @returns {Object} Configured uniweb instance\n */\nexport function initPrerender(content, foundation, extensionsOrOptions, maybeOptions) {\n // Backwards-compatible arg shape: (content, foundation, options) or\n // (content, foundation, extensions, options). Extensions must be passed at\n // construction so the Website's FetcherDispatcher sees their routes.\n let extensions = []\n let options = {}\n if (Array.isArray(extensionsOrOptions)) {\n extensions = extensionsOrOptions\n options = maybeOptions || {}\n } else {\n options = extensionsOrOptions || {}\n }\n const { onProgress = () => {} } = options\n\n onProgress('Initializing runtime...')\n // Uniweb constructor wires foundation, capabilities, layoutMeta, handlers,\n // and extensions at construction time — see framework/core/src/uniweb.js.\n const uniweb = createUniweb(content, foundation, extensions)\n\n // Set base path from site config for subdirectory deployments\n if (content.config?.base && uniweb.activeWebsite?.setBasePath) {\n uniweb.activeWebsite.setBasePath(content.config.base)\n }\n\n // Set childBlockRenderer so ChildBlocks/Visual/Render work during prerender.\n // Mirrors the client's ChildBlocks component in PageRenderer.jsx:\n // - default bare rendering (no wrapAs) — component only, no wrapper\n // - pass wrapAs to opt into full section treatment\n uniweb.childBlockRenderer = function InlineChildBlocks({ blocks, from, wrapAs }) {\n const blockList = blocks || from?.childBlocks || []\n return blockList.map((childBlock, index) =>\n React.createElement(React.Fragment, { key: childBlock.id || index },\n renderBlock(childBlock, { as: wrapAs || null })\n )\n )\n }\n\n // L2 (singleton wiring): defaultInsets, xref.build(), and any future\n // framework-level capability bridge — shared with setup.js so both\n // boot paths apply the same foundation contract. See\n // wire-foundation.js for the contract and CLAUDE.md \"Three-Layer\n // Runtime Model\" for the rule about what belongs in this helper vs.\n // here vs. setup.js.\n wireFoundationCapabilities(uniweb, foundation)\n\n // Register SSR-safe routing so useRouting()/useActiveRoute() work during prerender.\n // renderPage() calls website.setActivePage() before rendering each page,\n // so activePage.route always reflects the page being rendered.\n const website = uniweb.activeWebsite\n uniweb.routingComponents = {\n useLocation: () => {\n const route = website?.activePage?.route || ''\n return { pathname: '/' + route, search: '', hash: '', state: null, key: 'default' }\n },\n useParams: () => ({}),\n useNavigate: () => () => {},\n }\n\n return uniweb\n}\n\n/**\n * Pre-fetch icons from CDN and populate the Uniweb icon cache.\n * Stores the cache on siteContent._iconCache for embedding in HTML.\n *\n * @param {Object} siteContent - Site content JSON (mutated: _iconCache added)\n * @param {Object} uniweb - Configured uniweb instance\n * @param {function} [onProgress] - Progress callback\n */\nexport async function prefetchIcons(siteContent, uniweb, onProgress = () => {}) {\n const icons = siteContent.icons?.used || []\n if (icons.length === 0) return\n\n const cdnBase = siteContent.config?.icons?.cdnUrl || 'https://uniweb.github.io/icons'\n\n onProgress(`Fetching ${icons.length} icons for SSR...`)\n\n const results = await Promise.allSettled(\n icons.map(async (iconRef) => {\n const [family, name] = iconRef.split(':')\n const url = `${cdnBase}/${family}/${family}-${name}.svg`\n const response = await fetch(url)\n if (!response.ok) throw new Error(`HTTP ${response.status}`)\n const svg = await response.text()\n uniweb.iconCache.set(`${family}:${name}`, svg)\n })\n )\n\n const succeeded = results.filter(r => r.status === 'fulfilled').length\n const failed = results.filter(r => r.status === 'rejected').length\n if (failed > 0) {\n const msg = `Fetched ${succeeded}/${icons.length} icons (${failed} failed)`\n console.warn(`[prerender] ${msg}`)\n onProgress(` ${msg}`)\n }\n\n // Store icon cache on siteContent for embedding in HTML\n if (uniweb.iconCache.size > 0) {\n siteContent._iconCache = Object.fromEntries(uniweb.iconCache)\n }\n}\n\n// ============================================================================\n// Layer 3: Per-page rendering\n// ============================================================================\n\n/**\n * Classify an SSR rendering error.\n *\n * @param {Error} err\n * @returns {{ type: 'hooks'|'null-component'|'unknown', message: string }}\n */\nexport function classifyRenderError(err) {\n const msg = err.message || ''\n\n if (msg.includes('Invalid hook call') || msg.includes('useState') || msg.includes('useEffect')) {\n return {\n type: 'hooks',\n message: 'contains components with React hooks (renders client-side)',\n }\n }\n\n if (msg.includes('Element type is invalid') && msg.includes('null')) {\n return {\n type: 'null-component',\n message: 'a component resolved to null (often hook-related, renders client-side)',\n }\n }\n\n return {\n type: 'unknown',\n message: msg,\n }\n}\n\n/**\n * Render a single page to HTML.\n *\n * Handles the full per-page pipeline:\n * setActivePage → renderLayout → renderToString → error handling → section override CSS.\n *\n * @param {Page} page - Page instance to render\n * @param {Website} website - Website instance\n * @returns {{ renderedContent: string, sectionOverrideCSS: string } | { error: { type: string, message: string } }}\n */\nexport function renderPage(page, website) {\n website.setActivePage(page.route)\n\n const element = renderLayout(page, website)\n\n let renderedContent\n try {\n renderedContent = renderToString(element)\n } catch (err) {\n return { error: classifyRenderError(err) }\n }\n\n // Build per-page section override CSS (theme pinning, component vars)\n const appearance = website.themeData?.appearance\n const sectionOverrideCSS = buildSectionOverrides(page.getPageBlocks(), appearance)\n\n return { renderedContent, sectionOverrideCSS }\n}\n\n// ============================================================================\n// HTML injection\n// ============================================================================\n\n/**\n * Escape HTML special characters.\n */\nexport function escapeHtml(str) {\n if (!str) return ''\n return String(str)\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n}\n\n/**\n * Inject prerendered content into an HTML shell.\n *\n * Common operations shared by both build and cloud:\n * - Replace #root div with rendered HTML\n * - Update page title\n * - Add/update meta description\n * - Inject section override CSS\n *\n * Build layers its additional injections on top of this return value:\n * __SITE_CONTENT__ JSON, icon cache, theme CSS (build-specific).\n *\n * @param {string} html - HTML shell\n * @param {string} renderedContent - React renderToString output\n * @param {Object} page - Page data { title, description, route }\n * @param {Object} [options]\n * @param {string} [options.sectionOverrideCSS] - Per-page section override CSS\n * @returns {string} HTML with injected content\n */\nexport function injectPageContent(html, renderedContent, page, options = {}) {\n let result = html\n\n // Inject per-page section override CSS before </head>\n if (options.sectionOverrideCSS) {\n const overrideStyle = `<style id=\"uniweb-page-overrides\">\\n${options.sectionOverrideCSS}\\n</style>`\n result = result.replace('</head>', `${overrideStyle}\\n</head>`)\n }\n\n // Replace the empty root div with pre-rendered content\n result = result.replace(\n /<div id=\"root\">[\\s\\S]*?<\\/div>/,\n `<div id=\"root\">${renderedContent}</div>`\n )\n\n // Update page title (use getTitle() so isIndex pages inherit parent title)\n const pageTitle = page.getTitle?.() || page.title\n if (pageTitle) {\n result = result.replace(\n /<title>.*?<\\/title>/,\n `<title>${escapeHtml(pageTitle)}</title>`\n )\n }\n\n // Add/update meta description\n if (page.description) {\n const metaDesc = `<meta name=\"description\" content=\"${escapeHtml(page.description)}\">`\n if (result.includes('<meta name=\"description\"')) {\n result = result.replace(/<meta name=\"description\"[^>]*>/, metaDesc)\n } else {\n result = result.replace('</head>', `${metaDesc}\\n</head>`)\n }\n }\n\n return result\n}\n\n// ============================================================================\n// 404 fallback generation\n// ============================================================================\n\n/**\n * Generate 404.html content for static hosting fallback.\n *\n * Serves two purposes on static hosts (GitHub Pages, Cloudflare Pages, etc.):\n * 1. Real 404: pre-rendered custom 404 page content (or blank #root if none defined)\n * 2. Valid dynamic route (e.g. /blog/2): inline script clears #root so SPA renders fresh\n *\n * Flow: static host serves 404.html → inline script runs before React mounts →\n * - dynamic route: clears #root, React renders the page normally\n * - real 404: leaves #root with pre-rendered content, React re-renders same 404 page\n *\n * @param {Object} options\n * @param {string} options.baseHtml - Assembled HTML shell (with site content already injected)\n * @param {Object} options.website - Initialized Website instance (from initPrerender)\n * @param {Object} options.siteContent - Site content object (to find dynamic templates)\n * @returns {{ html: string, hasNotFoundPage: boolean }}\n */\nexport function generate404Html({ baseHtml, website, siteContent }) {\n // Extract patterns for routes that remain as dynamic templates (prerender: false)\n const dynamicTemplates = siteContent.pages?.filter((p) => p.isDynamic) || []\n const routePatterns = dynamicTemplates.map((p) => {\n // '/blog/:id' → /^\\/blog\\/[^\\/]+\\/?$/\n const escaped = p.route\n .replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/:[^/]+/g, '[^\\\\/]+')\n return `^${escaped}\\\\/?$`\n })\n\n let html = baseHtml\n\n // Pre-render the custom 404 page content into #root (if the site defines one),\n // otherwise inject a default 404 message so the page isn't blank before JS loads\n const notFoundPage = website.getNotFoundPage()\n if (notFoundPage) {\n const notFoundResult = renderPage(notFoundPage, website)\n if (notFoundResult && !notFoundResult.error) {\n html = injectPageContent(html, notFoundResult.renderedContent, notFoundPage, {\n sectionOverrideCSS: notFoundResult.sectionOverrideCSS,\n })\n }\n } else {\n const basePath = website.basePath || ''\n html = html.replace(\n /<div id=\"root\">[\\s\\S]*?<\\/div>/,\n `<div id=\"root\">${default404Html(basePath)}</div>`\n )\n }\n\n // Inject inline script: if path matches a dynamic route, clear #root before React mounts\n // so the SPA renders the correct page rather than the 404 content\n if (routePatterns.length > 0) {\n const patternList = routePatterns.map((p) => `/${p}/`).join(',')\n const dynamicScript =\n `<script>(function(){` +\n `var p=[${patternList}],r=window.location.pathname;` +\n `if(p.some(function(x){return x.test(r)})){` +\n `var el=document.getElementById('root');if(el)el.innerHTML='';` +\n `}})()</script>`\n html = html.replace('</body>', `${dynamicScript}\\n</body>`)\n }\n\n return { html, hasNotFoundPage: !!notFoundPage }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ssr.js","sources":["../src/prepare-props.js","../src/default-404.js","../src/wire-foundation.js","../src/ssr-renderer.js"],"sourcesContent":["/**\n * Props Preparation for Runtime Guarantees\n *\n * Prepares props for foundation components with:\n * - Param defaults from runtime schema\n * - Guaranteed content structure (no null checks needed)\n * - Field defaults applied to `content.data` items from the bound schemas\n *\n * This enables simpler component code by ensuring predictable prop shapes.\n */\n\nimport { isRichSchema } from '@uniweb/core'\n\n/**\n * Guarantee item has flat content structure\n *\n * @param {Object} item - Raw item from parser\n * @returns {Object} Item with guaranteed flat structure\n */\nfunction guaranteeItemStructure(item) {\n return {\n title: item.title || '',\n pretitle: item.pretitle || '',\n subtitle: item.subtitle || '',\n paragraphs: item.paragraphs || [],\n links: item.links || [],\n images: item.images || [],\n lists: item.lists || [],\n icons: item.icons || [],\n videos: item.videos || [],\n snippets: item.snippets || [],\n buttons: item.buttons || [],\n data: item.data || {},\n cards: item.cards || [],\n documents: item.documents || [],\n forms: item.forms || [],\n quotes: item.quotes || [],\n headings: item.headings || [],\n ...(item.math && item.math.length ? { math: item.math } : {}),\n }\n}\n\n/**\n * Guarantee content structure exists\n * Returns a flat content object with all standard fields guaranteed to exist\n *\n * @param {Object} parsedContent - Raw parsed content from semantic parser (flat structure)\n * @returns {Object} Content with guaranteed flat structure\n */\nexport function guaranteeContentStructure(parsedContent) {\n const content = parsedContent || {}\n\n return {\n // Flat header fields\n title: content.title || '',\n pretitle: content.pretitle || '',\n subtitle: content.subtitle || '',\n alignment: content.alignment || null,\n\n // Flat body fields\n paragraphs: content.paragraphs || [],\n links: content.links || [],\n images: content.images || [],\n lists: content.lists || [],\n icons: content.icons || [],\n videos: content.videos || [],\n insets: content.insets || [],\n snippets: content.snippets || [],\n buttons: content.buttons || [],\n data: content.data || {},\n cards: content.cards || [],\n documents: content.documents || [],\n forms: content.forms || [],\n quotes: content.quotes || [],\n headings: content.headings || [],\n\n // Rare collections — surfaced only when present so pages that don't\n // use them don't pay the allocation cost. Foundations that need them\n // should check for presence (content.math?.length) or use\n // content.sequence for in-order rendering.\n ...(content.math && content.math.length ? { math: content.math } : {}),\n\n // Items with guaranteed structure\n items: (content.items || []).map(guaranteeItemStructure),\n\n // Sequence for ordered rendering\n sequence: content.sequence || [],\n\n // Preserve raw content if present\n raw: content.raw,\n }\n}\n\n/**\n * Apply a schema to a single object\n * Only processes fields defined in the schema, preserves unknown fields\n *\n * @param {Object} obj - The object to process\n * @param {Object} schema - Schema definition (fieldName -> fieldDef)\n * @returns {Object} Object with schema defaults applied\n */\nfunction applySchemaToObject(obj, schema) {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {\n return obj\n }\n\n const result = { ...obj }\n\n for (const [field, fieldDef] of Object.entries(schema)) {\n // Get the default value - handle both shorthand and full form\n const defaultValue = typeof fieldDef === 'object' ? fieldDef.default : undefined\n\n // Apply default if field is missing and default exists\n if (result[field] === undefined && defaultValue !== undefined) {\n result[field] = defaultValue\n }\n\n // Bare type strings ('string', 'decimal', …) carry nothing more to apply.\n if (typeof fieldDef !== 'object') continue\n\n // Inline picklist (`enum`): if the value is set but not among the allowed\n // values, fall back to the default.\n if (Array.isArray(fieldDef.enum)) {\n if (result[field] !== undefined && !fieldDef.enum.includes(result[field]) && defaultValue !== undefined) {\n result[field] = defaultValue\n }\n }\n\n // Nested object → recurse into its field map.\n if (fieldDef.type === 'object' && fieldDef.fields && result[field]) {\n result[field] = applySchemaToObject(result[field], fieldDef.fields)\n }\n\n // Array of objects → apply the element field map to each item.\n if (fieldDef.type === 'array' && fieldDef.items && Array.isArray(result[field])) {\n const items = fieldDef.items\n if (items && typeof items === 'object' && items.type === 'object' && items.fields) {\n result[field] = result[field].map((item) => applySchemaToObject(item, items.fields))\n }\n }\n }\n\n return result\n}\n\n/**\n * Apply a schema to a value (object or array of objects)\n *\n * @param {Object|Array} value - The value to process\n * @param {Object} schema - Schema definition\n * @returns {Object|Array} Value with schema defaults applied\n */\nfunction applySchemaToValue(value, schema) {\n if (Array.isArray(value)) {\n return value.map(item => applySchemaToObject(item, schema))\n }\n return applySchemaToObject(value, schema)\n}\n\n/**\n * Apply field defaults from a rich form `fields` array to an object.\n *\n * Recurses into `type: 'form'` (composite arrays with childSchema) and\n * `type: 'nestedObject'` / `type: 'object'` (single nested objects).\n *\n * Conditional visibility (`field.condition`) is not yet applied here —\n * components receive all fields the author filled plus defaults; hiding\n * is a later pass that requires the shared evaluateCondition util.\n *\n * @param {Object} obj - Row data (object keyed by field id)\n * @param {Array} fields - Rich field definitions\n * @returns {Object} - obj with defaults filled in\n */\nfunction applyRichFieldDefaults(obj, fields) {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return obj\n if (!Array.isArray(fields)) return obj\n\n const result = { ...obj }\n\n for (const field of fields) {\n if (!field || typeof field !== 'object' || !field.id) continue\n const id = field.id\n\n if (result[id] === undefined && field.default !== undefined) {\n result[id] = field.default\n }\n\n if (field.type === 'form' && field.childSchema && Array.isArray(result[id])) {\n result[id] = result[id].map(item =>\n applyRichFieldDefaults(item, field.childSchema.fields)\n )\n } else if (\n (field.type === 'nestedObject' || field.type === 'object') &&\n Array.isArray(field.fields) &&\n result[id] &&\n typeof result[id] === 'object'\n ) {\n result[id] = applyRichFieldDefaults(result[id], field.fields)\n }\n }\n\n return result\n}\n\n/**\n * Apply a rich form schema to its stored value.\n *\n * Shape rules:\n * - composite (isComposite=true) → value is array of childSchema rows\n * - when `childCollection` is set, value may be `{ [childCollection]: [...] }`\n * - non-composite → value is a single object keyed by field id\n */\nfunction applyRichSchemaToValue(value, schema) {\n if (value == null) return value\n\n if (schema.isComposite && schema.childSchema) {\n const childFields = schema.childSchema.fields\n const collectionKey = schema.childCollection\n\n if (collectionKey && value && typeof value === 'object' && !Array.isArray(value)) {\n const arr = Array.isArray(value[collectionKey]) ? value[collectionKey] : []\n return {\n ...value,\n [collectionKey]: arr.map(row => applyRichFieldDefaults(row, childFields)),\n }\n }\n\n if (Array.isArray(value)) {\n return value.map(row => applyRichFieldDefaults(row, childFields))\n }\n\n return value\n }\n\n if (Array.isArray(schema.fields)) {\n return applyRichFieldDefaults(value, schema.fields)\n }\n\n return value\n}\n\n/**\n * Apply schemas to content.data\n * Only processes tags that have a matching schema, leaves others untouched\n *\n * @param {Object} data - The data object from content\n * @param {Object} schemas - Schema definitions from runtime meta\n * @returns {Object} Data with schemas applied\n */\nexport function applySchemas(data, schemas) {\n if (!schemas || !data || typeof data !== 'object') {\n return data || {}\n }\n\n const result = { ...data }\n\n for (const [tag, rawValue] of Object.entries(data)) {\n const schema = schemas[tag]\n if (!schema) continue // No schema for this tag - leave as-is\n\n result[tag] = isRichSchema(schema)\n ? applyRichSchemaToValue(rawValue, schema)\n : applySchemaToValue(rawValue, schema)\n }\n\n return result\n}\n\n/**\n * Apply param defaults from runtime schema\n *\n * @param {Object} params - Params from frontmatter\n * @param {Object} defaults - Default values from runtime schema\n * @returns {Object} Merged params with defaults applied\n */\nexport function applyDefaults(params, defaults) {\n if (!defaults || Object.keys(defaults).length === 0) {\n return params || {}\n }\n\n return {\n ...defaults,\n ...(params || {}),\n }\n}\n\n/**\n * Merge entity data onto a block's parsedContent.data.\n *\n * Section-level data already on the block (from prerender fetches via\n * blockData.parsedContent.data in the Block constructor) takes priority;\n * entity data only fills missing keys. Mutates `block.parsedContent.data`\n * in place so the vanilla JS layer holds the assembled data and\n * subsequent reads see the same shape.\n */\nfunction mergeEntityData(block, entityData) {\n if (!entityData) return\n const current = block.parsedContent.data || {}\n let changed = false\n const merged = { ...current }\n for (const key of Object.keys(entityData)) {\n if (merged[key] === undefined) {\n merged[key] = entityData[key]\n changed = true\n }\n }\n if (changed) {\n block.parsedContent.data = merged\n }\n}\n\n/**\n * Run the foundation-level data handler on a block, if one is\n * registered. Runs after entity data merge and before the content\n * handler — the handler sees the fully assembled data and can filter,\n * reshape, or augment it before Loom (or any content transform) runs.\n *\n * The handler receives `(data, block)` where data is\n * `block.parsedContent.data`. It returns a new data object, or\n * null/undefined for no change. The returned data replaces\n * `block.parsedContent.data` for all downstream processing — both\n * the content handler and the component see the transformed data.\n *\n * Skipped when the block is still waiting on async data\n * (`block.dataLoading`), or when no handler is registered.\n * Errors are logged and the original data is preserved.\n */\nfunction runDataHandler(block) {\n if (block.dataLoading) return\n const handler = globalThis.uniweb?.foundationConfig?.handlers?.data\n if (typeof handler !== 'function') return\n\n try {\n const result = handler(block.parsedContent.data, block)\n if (result != null && result !== block.parsedContent.data) {\n block.parsedContent.data = result\n }\n } catch (err) {\n console.error('Foundation data handler failed:', err)\n }\n}\n\n/**\n * Run the foundation-level content handler on a block, if one is\n * registered. Runs at prop-preparation time — after the data handler\n * has had a chance to filter/reshape the data — so the handler sees\n * the fully assembled (and possibly filtered) data. Replaces\n * `block.parsedContent` in place with the re-parsed, instantiated\n * form. The handler receives `(data, block)` and reads raw\n * ProseMirror from `block.rawContent`.\n *\n * Skipped when the block is still waiting on async data\n * (`block.dataLoading`), when no handler is registered, when the\n * block has no raw content, when the handler returns a no-change\n * signal (undefined, null, or the same reference as rawContent), or\n * when the handler throws. Errors are logged via `console.error`.\n */\nfunction runContentHandler(block) {\n if (block.dataLoading) return\n const handler = globalThis.uniweb?.foundationConfig?.handlers?.content\n if (typeof handler !== 'function') return\n if (!block.rawContent || Object.keys(block.rawContent).length === 0) return\n\n try {\n const transformed = handler(block.parsedContent.data, block)\n if (!transformed || transformed === block.rawContent) return\n const reparsed = block.parseContent(transformed)\n reparsed.data = block.parsedContent.data\n block.parsedContent = reparsed\n block.items = reparsed.items || []\n } catch (err) {\n console.error('Foundation content handler failed:', err)\n }\n}\n\n/**\n * Run the foundation-level props handler on the final { content, params }\n * before they reach the component. Runs after content parsing, param\n * defaults, content guarantees, and schema application — the handler\n * sees the exact shape the component would receive and can modify it.\n *\n * The handler receives `(content, params, block)` and returns a new\n * `{ content, params }` object, or null/undefined for no change.\n *\n * Use cases: post-parse content reshaping, computed fields derived\n * from both content and params, param-driven content reorganization.\n * Errors are logged and the original props are preserved.\n */\nfunction runPropsHandler(content, params, block) {\n const handler = globalThis.uniweb?.foundationConfig?.handlers?.props\n if (typeof handler !== 'function') return null\n\n try {\n const result = handler(content, params, block)\n if (result && typeof result === 'object') return result\n } catch (err) {\n console.error('Foundation props handler failed:', err)\n }\n return null\n}\n\n/**\n * Prepare props for a component with runtime guarantees.\n *\n * Does the full content-assembly pipeline in one place so both\n * renderers (`BlockRenderer.jsx` CSR and `ssr-renderer.js` SSG) share\n * the same code path:\n *\n * 1. Merge entity data (resolved by EntityStore) onto\n * `block.parsedContent.data`.\n * 2. Run the foundation data handler (if registered) to filter or\n * reshape the assembled data.\n * 3. Run the foundation content handler (if registered) on the\n * block. This may replace `block.parsedContent` with a re-parsed,\n * instantiated version.\n * 4. Apply param defaults from meta.\n * 5. Build the guaranteed content structure.\n * 6. Apply schemas to content.data.\n * 7. Run the foundation props handler (if registered) for\n * post-processing of the final { content, params }.\n *\n * Steps 1–3 mutate the block (vanilla JS layer). Steps 4–7 are\n * pure derivations of the block's now-assembled state.\n *\n * @param {Object} block - The block instance\n * @param {Object} meta - Runtime metadata for the component (from meta[componentName])\n * @param {Object|null} [entityData] - Entity data resolved by EntityStore (null if none)\n * @returns {Object} Prepared props: { content, params }\n */\nexport function prepareProps(block, meta, entityData = null) {\n mergeEntityData(block, entityData)\n runDataHandler(block)\n runContentHandler(block)\n\n // Apply param defaults\n const defaults = meta?.defaults || {}\n const params = applyDefaults(block.properties, defaults)\n\n // Guarantee content structure\n let content = guaranteeContentStructure(block.parsedContent)\n\n // Apply schemas to content.data\n const schemas = meta?.schemas || null\n if (schemas && content.data) {\n content.data = applySchemas(content.data, schemas)\n }\n\n // Post-process hook\n const adjusted = runPropsHandler(content, params, block)\n if (adjusted) {\n return {\n content: adjusted.content || content,\n params: adjusted.params || params,\n }\n }\n\n return { content, params }\n}\n\n/**\n * Get runtime metadata for a component from the global uniweb instance\n *\n * @param {string} componentName\n * @returns {Object|null}\n */\nexport function getComponentMeta(componentName) {\n return globalThis.uniweb?.getComponentMeta?.(componentName) || null\n}\n\n/**\n * Get default param values for a component\n *\n * @param {string} componentName\n * @returns {Object}\n */\nexport function getComponentDefaults(componentName) {\n return globalThis.uniweb?.getComponentDefaults?.(componentName) || {}\n}\n","/**\n * Default 404 Page Content\n *\n * Single source of truth for the fallback 404 page shown when a site\n * has no custom 404 page defined. Used by:\n * - PageRenderer.jsx (client-side, as React elements)\n * - ssr-renderer.js generate404Html (build-time, as HTML string)\n *\n * The wrapper uses min-height + flex centering so the 404 content\n * renders at the same position regardless of parent layout context.\n * This prevents a visible flash when React hydrates over the SSR content.\n */\n\nimport React from 'react'\n\nconst styles = {\n wrapper: {\n minHeight: '80vh',\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'center',\n justifyContent: 'center',\n padding: '2rem',\n textAlign: 'center',\n },\n heading: { fontSize: '3rem', fontWeight: 'bold', color: '#1f2937', marginBottom: '1rem' },\n message: { color: '#64748b', marginBottom: '2rem' },\n link: { color: '#3b82f6', textDecoration: 'underline' },\n}\n\n/**\n * React element for client-side rendering (PageRenderer).\n * Reads basePath from the runtime so the homepage link works\n * in subdirectory deployments (e.g., /sites/testproject).\n */\nexport function Default404() {\n const basePath = globalThis.uniweb?.activeWebsite?.basePath || ''\n const homeHref = basePath ? `${basePath}/` : '/'\n return React.createElement('div', { className: 'page-not-found', style: styles.wrapper },\n React.createElement('h1', { style: styles.heading }, '404'),\n React.createElement('p', { style: styles.message }, 'Page not found'),\n React.createElement('a', { href: homeHref, style: styles.link }, 'Go to homepage')\n )\n}\n\n/**\n * Static HTML string for SSR injection (generate404Html).\n *\n * @param {string} [basePath] - Base path prefix for the homepage link (e.g., '/sites/testproject')\n */\nexport function default404Html(basePath = '') {\n const homeHref = basePath ? `${basePath}/` : '/'\n return (\n `<div class=\"page-not-found\" style=\"min-height:80vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem;text-align:center\">` +\n `<h1 style=\"font-size:3rem;font-weight:bold;color:#1f2937;margin-bottom:1rem\">404</h1>` +\n `<p style=\"color:#64748b;margin-bottom:2rem\">Page not found</p>` +\n `<a href=\"${homeHref}\" style=\"color:#3b82f6;text-decoration:underline\">Go to homepage</a>` +\n `</div>`\n )\n}\n","/**\n * Layer-2 wiring helpers: runtime ↔ Uniweb singleton.\n *\n * After `createUniweb()` constructs the singleton, the runtime fills a\n * few declared slots on it before the first render — foundation\n * capabilities (`defaultInsets`, `xref.build()`), per-request data\n * hydration into `website.dataStore`, locale-scoped content slicing.\n * This step is identical in every environment (browser SPA, SSG\n * prerender, cloud SSR) because it's plain data manipulation on a JS\n * object: no React rendering happens here, no hooks are called, no DOM\n * is touched, no `react-dom/server` is needed.\n *\n * That's why these helpers live in one file imported by both\n * `setup.js` (browser boot) and `ssr-renderer.js` (SSG/cloud-SSR boot),\n * instead of being duplicated into each. Things that genuinely differ\n * between environments — routing components, icon-cache hydration from\n * the DOM, the per-page render loop — stay in the per-environment\n * entries; these helpers cover only the environment-agnostic L2 work.\n *\n * Keeping this file React-free matters for the SPA bundle: `setup.js`\n * pulls `wire-foundation.js` directly, but it must NOT transitively\n * pull `ssr-renderer.js` (which imports `react-dom/server`). The L2\n * helpers therefore live here, while the L3-composing\n * `initPrerenderForLocale` lives in `ssr-renderer.js`.\n *\n * Adding a new framework-level capability:\n * 1. Read the foundation declaration via `foundation.default.capabilities.<name>`.\n * 2. Apply it to the uniweb singleton (set a slot, call a build hook,\n * register something on `activeWebsite`).\n * 3. Provide a runtime fallback if the capability is one foundations\n * may legitimately not declare (see `FallbackRef`).\n *\n * Foundation export shape contract: the runtime always loads the\n * **built** foundation artifact (`dist/foundation.js`) via\n * `loadFoundation()` in `foundation-loader.js`, which does `import(url)`\n * and returns a module namespace. The build pipeline\n * (`framework/build/src/generate-entry.js`) wraps the foundation's\n * source default export under `default.capabilities.*`, so the runtime\n * sees a single canonical shape with no need for fallback chains. See\n * `framework/CLAUDE.md` \"Three-Layer Runtime Model\" for the rationale\n * and for how this differs from `@uniweb/press` / `@uniweb/unipress`,\n * which DO need to handle a second shape because they're sometimes\n * called from inside a foundation bundle (where the foundation imports\n * its own source as a bare default object).\n */\n\nimport React from 'react'\nimport { deriveCacheKey } from '@uniweb/core'\n\n/**\n * Renders unhandled `[#id]` cross-reference markers as plain text. Used\n * when the active foundation didn't declare its own `<Ref>` via\n * `defaultInsets`. Pure `React.createElement` — safe in every\n * environment, including the hook-free SSR pipeline.\n *\n * Foundations that support cross-references override this by exporting\n * `defaultInsets: { Ref }` (with kit's xref-aware Ref) from their\n * source — the build pipeline carries it through into\n * `default.capabilities.defaultInsets`.\n */\nexport function FallbackRef({ params }) {\n return React.createElement(\n 'span',\n { className: 'xref xref--unhandled' },\n `[${params?.key || '?'}]`,\n )\n}\n\n/**\n * Wire foundation-declared capabilities onto a freshly constructed\n * Uniweb singleton. Called once, after `createUniweb()`, before any\n * rendering. Identical for SPA, SSG, and cloud SSR.\n *\n * @param {import('@uniweb/core').default} uniweb - From createUniweb(...).\n * @param {object} foundation - Loaded foundation module (built shape).\n */\nexport function wireFoundationCapabilities(uniweb, foundation) {\n const caps = foundation?.default?.capabilities || {}\n\n // defaultInsets: framework provides FallbackRef as the floor;\n // foundation overrides win. `getComponent()` on the Uniweb singleton\n // (core/uniweb.js) falls back to defaultInsets[name] when no\n // foundation/extension component matches — that's how `<Ref>` becomes\n // available to every foundation without each one having to register\n // it explicitly.\n uniweb.defaultInsets = { Ref: FallbackRef, ...(caps.defaultInsets || {}) }\n\n // xref: foundations supporting cross-references export\n // `xref.build(website, { foundationKinds })`. The runtime can't\n // import kit directly (kit is bundled into each foundation, not into\n // runtime — see CLAUDE.md gotcha #9 on tree-shaking), so it\n // dispatches through the foundation's reference. Foundations without\n // xref skip this entirely; kit's xref module never enters their\n // bundle thanks to tree-shaking at foundation-build time.\n if (caps.xref?.build && uniweb.activeWebsite) {\n caps.xref.build(uniweb.activeWebsite, {\n foundationKinds: caps.xref.kinds || {},\n })\n }\n}\n\n/**\n * Slice a multi-locale site-content payload to one locale.\n *\n * Sites published through the editor ship a single payload that carries\n * all locales nested under `content.locales[locale]` — `pages`, optional\n * `layouts`, and a `config` overlay. The default locale lives at the\n * top level (no nesting). This helper extracts the requested locale's\n * view as a fresh content object the rest of the runtime can consume\n * unchanged.\n *\n * Returns `content` as-is when `locale` is the default, missing, or not\n * present in `content.locales` — callers that already hand us locale-\n * scoped content (e.g., the framework's per-locale SSG path that loads\n * each `dist/{locale}/site-content.json` separately) get pass-through\n * behavior.\n *\n * The shape comes from the editor's publish payload, which is the\n * production canonical for multi-locale content (the Cloudflare Worker\n * SSR path consumes it directly). Build-time SSG pre-flattens to one\n * file per locale and so falls into the pass-through case.\n *\n * @param {Object} content - Site content payload, possibly multi-locale.\n * @param {string} locale - Requested locale code.\n * @returns {Object} Content scoped to the requested locale.\n */\nexport function sliceContentForLocale(content, locale) {\n const defaultLang = content?.config?.defaultLanguage || 'en'\n const locData = content?.locales?.[locale]\n if (!locale || locale === defaultLang || !locData) return content\n return {\n pages: locData.pages,\n layouts: locData.layouts || content.layouts,\n config: {\n ...locData.config,\n i18n: content.config?.i18n,\n activeLocale: locale,\n },\n }\n}\n\n/**\n * Pre-populate a Website's DataStore from build-time / publish-time\n * fetched data so the dispatcher's first probe hits the cache instead\n * of refetching.\n *\n * The cache key MUST go through `deriveCacheKey(entry.config)` and the\n * value MUST be wrapped as `{ data }` — otherwise the dispatcher's\n * lookup at `_dataStore.get(deriveCacheKey(request))` misses every\n * time and `cached.data` reads `undefined`. Three call sites used to\n * inline this loop independently (browser SPA, Node SSG, Cloudflare\n * Worker SSR); the Cloudflare one was using the wrong shape, silently\n * killing prefetched-data reuse in production. This helper is the one\n * canonical implementation.\n *\n * @param {import('@uniweb/core').Website} website\n * @param {Array<{config: Object, data: any}>} fetchedData\n */\nexport function hydrateDataStore(website, fetchedData) {\n if (!website?.dataStore || !fetchedData?.length) return\n for (const entry of fetchedData) {\n website.dataStore.set(deriveCacheKey(entry.config), { data: entry.data })\n }\n}\n","/**\n * SSR Renderer\n *\n * Hook-free rendering pipeline for SSG (build) and cloud SSR (unicloud).\n * Mirrors BlockRenderer.jsx + Background.jsx using React.createElement\n * directly — no hooks, no JSX, no browser APIs.\n *\n * This is the single source of truth for how blocks render during prerender.\n * When modifying BlockRenderer.jsx or Background.jsx, update this file to match.\n *\n * Exports three layers:\n * 1. Rendering functions (renderBlock, renderBlocks, renderLayout, renderBackground)\n * 2. Initialization (initPrerender, prefetchIcons)\n * 3. Per-page rendering (renderPage, classifyRenderError, injectPageContent, escapeHtml)\n */\n\nimport React from 'react'\nimport { renderToString } from 'react-dom/server'\nimport { createUniweb } from '@uniweb/core'\nimport { buildSectionOverrides } from '@uniweb/theming'\nimport { prepareProps, getComponentMeta } from './prepare-props.js'\nimport { default404Html } from './default-404.js'\nimport {\n wireFoundationCapabilities,\n sliceContentForLocale,\n hydrateDataStore,\n} from './wire-foundation.js'\n\n// Re-export L2 helpers so the public `@uniweb/runtime/ssr` surface\n// carries everything an SSR consumer needs from one entry point.\nexport { sliceContentForLocale, hydrateDataStore }\n\n// ============================================================================\n// Layer 1: Rendering functions\n// ============================================================================\n\n/**\n * Valid color contexts for section theming\n */\nconst VALID_CONTEXTS = ['light', 'medium', 'dark']\n\n/**\n * Build wrapper props from block configuration.\n * Mirrors getWrapperProps in BlockRenderer.jsx.\n */\nexport function getWrapperProps(block) {\n const theme = block.themeName\n const blockClassName = block.state?.className || ''\n\n // Empty themeName = Auto → no context class → inherits tokens from :root\n // Non-empty = Pinned → context class sets tokens directly on the element\n let contextClass = ''\n if (theme && VALID_CONTEXTS.includes(theme)) {\n contextClass = `context-${theme}`\n }\n\n let className = contextClass\n if (blockClassName) {\n className = className ? `${className} ${blockClassName}` : blockClassName\n }\n\n const { background = {} } = block.standardOptions\n const style = {}\n\n // If background has content, ensure relative positioning and a stacking context\n // so the background's z-index stays contained within this section.\n if (background.mode) {\n style.position = 'relative'\n style.isolation = 'isolate'\n }\n\n // Apply context overrides as inline CSS custom properties\n if (block.contextOverrides) {\n for (const [key, value] of Object.entries(block.contextOverrides)) {\n style[`--${key}`] = value\n }\n }\n\n // Use stableId for DOM ID if available (stable across reordering)\n const sectionId = block.stableId || block.id\n\n return { id: `section-${sectionId}`, style, className, background }\n}\n\n/**\n * Convert hex/rgb color to rgba with opacity.\n * Mirrors withOpacity() in Background.jsx.\n */\nfunction withOpacity(color, opacity) {\n if (color.startsWith('#')) {\n const r = parseInt(color.slice(1, 3), 16)\n const g = parseInt(color.slice(3, 5), 16)\n const b = parseInt(color.slice(5, 7), 16)\n return `rgba(${r}, ${g}, ${b}, ${opacity})`\n }\n if (color.startsWith('rgb')) {\n const match = color.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/)\n if (match) {\n return `rgba(${match[1]}, ${match[2]}, ${match[3]}, ${opacity})`\n }\n }\n return color\n}\n\n/**\n * Resolve a URL against the site's base path.\n * Mirrors resolveUrl() in Background.jsx.\n */\nfunction resolveUrl(url) {\n if (!url || !url.startsWith('/')) return url\n const basePath = globalThis.uniweb?.activeWebsite?.basePath || ''\n if (!basePath) return url\n if (url.startsWith(basePath + '/') || url === basePath) return url\n return basePath + url\n}\n\n/**\n * Render a background element for SSR.\n * Mirrors Background.jsx (color, gradient, image — not video).\n * Video backgrounds require JS for autoplay and are skipped during SSR.\n */\nexport function renderBackground(background) {\n if (!background?.mode) return null\n\n const containerStyle = {\n position: 'absolute',\n inset: '0',\n overflow: 'hidden',\n zIndex: 0,\n }\n\n const children = []\n\n // Color background\n if (background.mode === 'color' && background.color) {\n children.push(\n React.createElement('div', {\n key: 'bg-color',\n className: 'background-color',\n style: { position: 'absolute', inset: '0', backgroundColor: background.color },\n 'aria-hidden': 'true',\n })\n )\n }\n\n // Gradient background (supports string or object with opacity)\n if (background.mode === 'gradient' && background.gradient) {\n const g = background.gradient\n\n let bgValue\n if (typeof g === 'string') {\n bgValue = g\n } else {\n const {\n start = 'transparent',\n end = 'transparent',\n angle = 0,\n startPosition = 0,\n endPosition = 100,\n startOpacity = 1,\n endOpacity = 1,\n } = g\n const startColor = startOpacity < 1 ? withOpacity(start, startOpacity) : start\n const endColor = endOpacity < 1 ? withOpacity(end, endOpacity) : end\n bgValue = `linear-gradient(${angle}deg, ${startColor} ${startPosition}%, ${endColor} ${endPosition}%)`\n }\n\n children.push(\n React.createElement('div', {\n key: 'bg-gradient',\n className: 'background-gradient',\n style: { position: 'absolute', inset: '0', background: bgValue },\n 'aria-hidden': 'true',\n })\n )\n }\n\n // Image background\n if (background.mode === 'image' && background.image?.src) {\n const img = background.image\n children.push(\n React.createElement('div', {\n key: 'bg-image',\n className: 'background-image',\n style: {\n position: 'absolute',\n inset: '0',\n backgroundImage: `url(${resolveUrl(img.src)})`,\n backgroundPosition: img.position || 'center',\n backgroundSize: img.size || 'cover',\n backgroundRepeat: 'no-repeat',\n },\n 'aria-hidden': 'true',\n })\n )\n }\n\n // Overlay (gradient or solid)\n if (background.overlay?.enabled) {\n const ov = background.overlay\n let overlayStyle\n\n if (ov.gradient) {\n const g = ov.gradient\n overlayStyle = {\n position: 'absolute', inset: '0', pointerEvents: 'none',\n background: `linear-gradient(${g.angle || 180}deg, ${g.start || 'rgba(0,0,0,0.7)'} ${g.startPosition || 0}%, ${g.end || 'rgba(0,0,0,0)'} ${g.endPosition || 100}%)`,\n opacity: ov.opacity ?? 0.5,\n }\n } else {\n const baseColor = ov.type === 'light' ? '255, 255, 255' : '0, 0, 0'\n overlayStyle = {\n position: 'absolute', inset: '0', pointerEvents: 'none',\n backgroundColor: `rgba(${baseColor}, ${ov.opacity ?? 0.5})`,\n }\n }\n\n children.push(\n React.createElement('div', {\n key: 'bg-overlay',\n className: ov.gradient ? 'background-overlay background-overlay--gradient' : 'background-overlay background-overlay--solid',\n style: overlayStyle,\n 'aria-hidden': 'true',\n })\n )\n }\n\n if (children.length === 0) return null\n\n return React.createElement('div', {\n className: `background background--${background.mode}`,\n style: containerStyle,\n 'aria-hidden': 'true',\n }, ...children)\n}\n\n/**\n * Render a single block for SSR.\n * Mirrors BlockRenderer.jsx but without hooks (no runtime data fetching).\n *\n * Two modes (mirrors client BlockRenderer):\n * - Bare (as=null/false): component only, no wrapper\n * - Section (as='section'/'div'/etc.): full treatment with wrapper, context, background\n *\n * @param {Block} block - Block instance to render\n * @param {Object} [options]\n * @param {string|null} [options.as='section'] - Wrapper element tag, or null/false for bare mode\n * @returns {React.ReactElement}\n */\nexport function renderBlock(block, { as = 'section' } = {}) {\n const Component = block.initComponent()\n\n if (!Component) {\n return React.createElement('div', {\n className: 'block-error',\n style: { padding: '1rem', background: '#fef2f2', color: '#dc2626' },\n }, `Component not found: ${block.type}`)\n }\n\n // Resolve inherited entity data synchronously (SSG has no async).\n // EntityStore walks page/site hierarchy to find data matching meta.inheritData.\n const meta = getComponentMeta(block.type)\n const entityStore = block.website?.entityStore\n let entityData = null\n if (entityStore) {\n const resolved = entityStore.resolve(block, meta)\n if (resolved.status === 'ready') entityData = resolved.data\n }\n\n // Build content and params with runtime guarantees.\n // prepareProps handles the full pipeline: entity data merge,\n // foundation content handler invocation, guaranteed content\n // structure, schema application, and param defaults.\n // See prepare-props.js for the pipeline details.\n const prepared = prepareProps(block, meta, entityData)\n const params = prepared.params\n const content = { ...prepared.content, ...block.properties }\n\n const componentProps = { content, params, block }\n\n // Bare mode: component only, no wrapper or section chrome.\n // Used by ChildBlocks for grid cells, tab panels, inline children, insets.\n if (!as) {\n return React.createElement(Component, componentProps)\n }\n\n // Section mode: full treatment with wrapper, context classes, background.\n const { background, ...wrapperProps } = getWrapperProps(block)\n\n // Merge Component.className (static classes declared on the component function)\n const componentClassName = Component.className\n if (componentClassName) {\n wrapperProps.className = wrapperProps.className\n ? `${wrapperProps.className} ${componentClassName}`\n : componentClassName\n }\n\n // Check if component handles its own background\n const hasBackground = background?.mode && meta?.background !== 'self'\n block.hasBackground = hasBackground\n\n // Determine wrapper element:\n // - Explicit as (not 'section') → use as prop directly\n // - Component.as → use component's declared tag (e.g., Header.as = 'header')\n // - fallback → 'section'\n const wrapperTag = as !== 'section' ? as : (Component.as || 'section')\n\n if (hasBackground) {\n return React.createElement(wrapperTag, wrapperProps,\n renderBackground(background),\n React.createElement('div', { style: { position: 'relative', zIndex: 10 } },\n React.createElement(Component, componentProps)\n )\n )\n }\n\n return React.createElement(wrapperTag, wrapperProps,\n React.createElement(Component, componentProps)\n )\n}\n\n/**\n * Render an array of blocks for SSR.\n */\nexport function renderBlocks(blocks) {\n if (!blocks || blocks.length === 0) return null\n return blocks.map((block, index) =>\n React.createElement(React.Fragment, { key: block.id || index },\n renderBlock(block)\n )\n )\n}\n\n/**\n * Render page layout for SSR.\n * Mirrors Layout.jsx but without hooks.\n */\nexport function renderLayout(page, website) {\n const layoutName = page.getLayoutName()\n const RemoteLayout = website.getRemoteLayout(layoutName)\n const layoutMeta = website.getLayoutMeta(layoutName)\n\n const bodyBlocks = page.getBodyBlocks()\n const areas = page.getLayoutAreas()\n\n const bodyElement = bodyBlocks ? renderBlocks(bodyBlocks) : null\n const areaElements = {}\n for (const [name, blocks] of Object.entries(areas)) {\n areaElements[name] = renderBlocks(blocks)\n }\n\n if (RemoteLayout) {\n const params = { ...(layoutMeta?.defaults || {}), ...(page.getLayoutParams() || {}) }\n return React.createElement(RemoteLayout, {\n page, website, params,\n body: bodyElement,\n ...areaElements,\n })\n }\n\n // Default layout\n return React.createElement(React.Fragment, null,\n areaElements.header && React.createElement('header', null, areaElements.header),\n bodyElement && React.createElement('main', null, bodyElement),\n areaElements.footer && React.createElement('footer', null, areaElements.footer)\n )\n}\n\n// ============================================================================\n// Layer 2: Initialization\n// ============================================================================\n\n/**\n * Construct a Uniweb singleton scoped to a single locale.\n *\n * Combines the three steps that every SSR consumer (browser SPA, Node\n * SSG, Cloudflare Worker SSR) needs in the same order: slice the\n * multi-locale content payload, run `initPrerender` (which builds the\n * Website + wires foundation capabilities), then `setActiveLocale` so\n * `website.activeLang` stays in sync with what the page is rendering\n * for. Caller still owns DataStore hydration (per-request data differs\n * between requests; locale construction can be cached).\n *\n * @param {Object} content - Site content payload (possibly multi-locale).\n * @param {Object} foundation - Loaded foundation module.\n * @param {string} locale - Locale code to render in.\n * @param {Array<Object>|Object} [extensionsOrOptions] - Same shape as initPrerender's\n * third arg: an extensions array, or an options object when no extensions.\n * @param {Object} [maybeOptions] - Options object when extensions are passed.\n * @returns {import('@uniweb/core').default} The configured Uniweb singleton.\n */\nexport function initPrerenderForLocale(content, foundation, locale, extensionsOrOptions, maybeOptions) {\n const localeContent = sliceContentForLocale(content, locale)\n const uniweb = initPrerender(localeContent, foundation, extensionsOrOptions, maybeOptions)\n const defaultLang = content?.config?.defaultLanguage || 'en'\n if (locale && locale !== defaultLang && uniweb.activeWebsite?.setActiveLocale) {\n uniweb.activeWebsite.setActiveLocale(locale)\n }\n return uniweb\n}\n\n/**\n * Create and configure the Uniweb runtime for prerendering.\n *\n * Handles the full initialization sequence in the correct order:\n * createUniweb → setFoundation → capabilities → layoutMeta → basePath → childBlockRenderer.\n *\n * Returns the configured uniweb instance. Consumers can add extras after:\n * - Build: pre-populate DataStore, load extensions\n * - Unicloud: (none needed — payload is complete)\n *\n * NOTE: Does NOT clone content. Cloning is the consumer's responsibility\n * (build modifies content before init; unicloud clones upfront).\n *\n * @param {Object} content - Site content JSON (pages, config, hierarchy)\n * @param {Object} foundation - Loaded foundation module\n * @param {Object} [options]\n * @param {function} [options.onProgress] - Progress callback\n * @returns {Object} Configured uniweb instance\n */\nexport function initPrerender(content, foundation, extensionsOrOptions, maybeOptions) {\n // Backwards-compatible arg shape: (content, foundation, options) or\n // (content, foundation, extensions, options). Extensions must be passed at\n // construction so the Website's FetcherDispatcher sees their routes.\n let extensions = []\n let options = {}\n if (Array.isArray(extensionsOrOptions)) {\n extensions = extensionsOrOptions\n options = maybeOptions || {}\n } else {\n options = extensionsOrOptions || {}\n }\n const { onProgress = () => {} } = options\n\n onProgress('Initializing runtime...')\n // Uniweb constructor wires foundation, capabilities, layoutMeta, handlers,\n // and extensions at construction time — see framework/core/src/uniweb.js.\n const uniweb = createUniweb(content, foundation, extensions)\n\n // Set base path from site config for subdirectory deployments\n if (content.config?.base && uniweb.activeWebsite?.setBasePath) {\n uniweb.activeWebsite.setBasePath(content.config.base)\n }\n\n // Set childBlockRenderer so ChildBlocks/Visual/Render work during prerender.\n // Mirrors the client's ChildBlocks component in PageRenderer.jsx:\n // - default bare rendering (no wrapAs) — component only, no wrapper\n // - pass wrapAs to opt into full section treatment\n uniweb.childBlockRenderer = function InlineChildBlocks({ blocks, from, wrapAs }) {\n const blockList = blocks || from?.childBlocks || []\n return blockList.map((childBlock, index) =>\n React.createElement(React.Fragment, { key: childBlock.id || index },\n renderBlock(childBlock, { as: wrapAs || null })\n )\n )\n }\n\n // L2 (singleton wiring): defaultInsets, xref.build(), and any future\n // framework-level capability bridge — shared with setup.js so both\n // boot paths apply the same foundation contract. See\n // wire-foundation.js for the contract and CLAUDE.md \"Three-Layer\n // Runtime Model\" for the rule about what belongs in this helper vs.\n // here vs. setup.js.\n wireFoundationCapabilities(uniweb, foundation)\n\n // Register SSR-safe routing so useRouting()/useActiveRoute() work during prerender.\n // renderPage() calls website.setActivePage() before rendering each page,\n // so activePage.route always reflects the page being rendered.\n const website = uniweb.activeWebsite\n uniweb.routingComponents = {\n useLocation: () => {\n const route = website?.activePage?.route || ''\n return { pathname: '/' + route, search: '', hash: '', state: null, key: 'default' }\n },\n useParams: () => ({}),\n useNavigate: () => () => {},\n }\n\n return uniweb\n}\n\n/**\n * Pre-fetch icons from CDN and populate the Uniweb icon cache.\n * Stores the cache on siteContent._iconCache for embedding in HTML.\n *\n * @param {Object} siteContent - Site content JSON (mutated: _iconCache added)\n * @param {Object} uniweb - Configured uniweb instance\n * @param {function} [onProgress] - Progress callback\n */\nexport async function prefetchIcons(siteContent, uniweb, onProgress = () => {}) {\n const icons = siteContent.icons?.used || []\n if (icons.length === 0) return\n\n const cdnBase = siteContent.config?.icons?.cdnUrl || 'https://uniweb.github.io/icons'\n\n onProgress(`Fetching ${icons.length} icons for SSR...`)\n\n const results = await Promise.allSettled(\n icons.map(async (iconRef) => {\n const [family, name] = iconRef.split(':')\n const url = `${cdnBase}/${family}/${family}-${name}.svg`\n const response = await fetch(url)\n if (!response.ok) throw new Error(`HTTP ${response.status}`)\n const svg = await response.text()\n uniweb.iconCache.set(`${family}:${name}`, svg)\n })\n )\n\n const succeeded = results.filter(r => r.status === 'fulfilled').length\n const failed = results.filter(r => r.status === 'rejected').length\n if (failed > 0) {\n const msg = `Fetched ${succeeded}/${icons.length} icons (${failed} failed)`\n console.warn(`[prerender] ${msg}`)\n onProgress(` ${msg}`)\n }\n\n // Store icon cache on siteContent for embedding in HTML\n if (uniweb.iconCache.size > 0) {\n siteContent._iconCache = Object.fromEntries(uniweb.iconCache)\n }\n}\n\n// ============================================================================\n// Layer 3: Per-page rendering\n// ============================================================================\n\n/**\n * Classify an SSR rendering error.\n *\n * @param {Error} err\n * @returns {{ type: 'hooks'|'null-component'|'unknown', message: string }}\n */\nexport function classifyRenderError(err) {\n const msg = err.message || ''\n\n if (msg.includes('Invalid hook call') || msg.includes('useState') || msg.includes('useEffect')) {\n return {\n type: 'hooks',\n message: 'contains components with React hooks (renders client-side)',\n }\n }\n\n if (msg.includes('Element type is invalid') && msg.includes('null')) {\n return {\n type: 'null-component',\n message: 'a component resolved to null (often hook-related, renders client-side)',\n }\n }\n\n return {\n type: 'unknown',\n message: msg,\n }\n}\n\n/**\n * Render a single page to HTML.\n *\n * Handles the full per-page pipeline:\n * setActivePage → renderLayout → renderToString → error handling → section override CSS.\n *\n * @param {Page} page - Page instance to render\n * @param {Website} website - Website instance\n * @returns {{ renderedContent: string, sectionOverrideCSS: string } | { error: { type: string, message: string } }}\n */\nexport function renderPage(page, website) {\n website.setActivePage(page.route)\n\n const element = renderLayout(page, website)\n\n let renderedContent\n try {\n renderedContent = renderToString(element)\n } catch (err) {\n return { error: classifyRenderError(err) }\n }\n\n // Build per-page section override CSS (theme pinning, component vars)\n const appearance = website.themeData?.appearance\n const sectionOverrideCSS = buildSectionOverrides(page.getPageBlocks(), appearance)\n\n return { renderedContent, sectionOverrideCSS }\n}\n\n// ============================================================================\n// HTML injection\n// ============================================================================\n\n/**\n * Escape HTML special characters.\n */\nexport function escapeHtml(str) {\n if (!str) return ''\n return String(str)\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n}\n\n/**\n * Inject prerendered content into an HTML shell.\n *\n * Common operations shared by both build and cloud:\n * - Replace #root div with rendered HTML\n * - Update page title\n * - Add/update meta description\n * - Inject section override CSS\n *\n * Build layers its additional injections on top of this return value:\n * __SITE_CONTENT__ JSON, icon cache, theme CSS (build-specific).\n *\n * @param {string} html - HTML shell\n * @param {string} renderedContent - React renderToString output\n * @param {Object} page - Page data { title, description, route }\n * @param {Object} [options]\n * @param {string} [options.sectionOverrideCSS] - Per-page section override CSS\n * @returns {string} HTML with injected content\n */\nexport function injectPageContent(html, renderedContent, page, options = {}) {\n let result = html\n\n // Inject per-page section override CSS before </head>\n if (options.sectionOverrideCSS) {\n const overrideStyle = `<style id=\"uniweb-page-overrides\">\\n${options.sectionOverrideCSS}\\n</style>`\n result = result.replace('</head>', `${overrideStyle}\\n</head>`)\n }\n\n // Replace the empty root div with pre-rendered content\n result = result.replace(\n /<div id=\"root\">[\\s\\S]*?<\\/div>/,\n `<div id=\"root\">${renderedContent}</div>`\n )\n\n // Update page title (use getTitle() so isIndex pages inherit parent title)\n const pageTitle = page.getTitle?.() || page.title\n if (pageTitle) {\n result = result.replace(\n /<title>.*?<\\/title>/,\n `<title>${escapeHtml(pageTitle)}</title>`\n )\n }\n\n // Add/update meta description\n if (page.description) {\n const metaDesc = `<meta name=\"description\" content=\"${escapeHtml(page.description)}\">`\n if (result.includes('<meta name=\"description\"')) {\n result = result.replace(/<meta name=\"description\"[^>]*>/, metaDesc)\n } else {\n result = result.replace('</head>', `${metaDesc}\\n</head>`)\n }\n }\n\n return result\n}\n\n// ============================================================================\n// 404 fallback generation\n// ============================================================================\n\n/**\n * Generate 404.html content for static hosting fallback.\n *\n * Serves two purposes on static hosts (GitHub Pages, Cloudflare Pages, etc.):\n * 1. Real 404: pre-rendered custom 404 page content (or blank #root if none defined)\n * 2. Valid dynamic route (e.g. /blog/2): inline script clears #root so SPA renders fresh\n *\n * Flow: static host serves 404.html → inline script runs before React mounts →\n * - dynamic route: clears #root, React renders the page normally\n * - real 404: leaves #root with pre-rendered content, React re-renders same 404 page\n *\n * @param {Object} options\n * @param {string} options.baseHtml - Assembled HTML shell (with site content already injected)\n * @param {Object} options.website - Initialized Website instance (from initPrerender)\n * @param {Object} options.siteContent - Site content object (to find dynamic templates)\n * @returns {{ html: string, hasNotFoundPage: boolean }}\n */\nexport function generate404Html({ baseHtml, website, siteContent }) {\n // Extract patterns for routes that remain as dynamic templates (prerender: false)\n const dynamicTemplates = siteContent.pages?.filter((p) => p.isDynamic) || []\n const routePatterns = dynamicTemplates.map((p) => {\n // '/blog/:id' → /^\\/blog\\/[^\\/]+\\/?$/\n const escaped = p.route\n .replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/:[^/]+/g, '[^\\\\/]+')\n return `^${escaped}\\\\/?$`\n })\n\n let html = baseHtml\n\n // Pre-render the custom 404 page content into #root (if the site defines one),\n // otherwise inject a default 404 message so the page isn't blank before JS loads\n const notFoundPage = website.getNotFoundPage()\n if (notFoundPage) {\n const notFoundResult = renderPage(notFoundPage, website)\n if (notFoundResult && !notFoundResult.error) {\n html = injectPageContent(html, notFoundResult.renderedContent, notFoundPage, {\n sectionOverrideCSS: notFoundResult.sectionOverrideCSS,\n })\n }\n } else {\n const basePath = website.basePath || ''\n html = html.replace(\n /<div id=\"root\">[\\s\\S]*?<\\/div>/,\n `<div id=\"root\">${default404Html(basePath)}</div>`\n )\n }\n\n // Inject inline script: if path matches a dynamic route, clear #root before React mounts\n // so the SPA renders the correct page rather than the 404 content\n if (routePatterns.length > 0) {\n const patternList = routePatterns.map((p) => `/${p}/`).join(',')\n const dynamicScript =\n `<script>(function(){` +\n `var p=[${patternList}],r=window.location.pathname;` +\n `if(p.some(function(x){return x.test(r)})){` +\n `var el=document.getElementById('root');if(el)el.innerHTML='';` +\n `}})()</script>`\n html = html.replace('</body>', `${dynamicScript}\\n</body>`)\n }\n\n return { html, hasNotFoundPage: !!notFoundPage }\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/runtime",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.15",
|
|
4
4
|
"description": "Minimal runtime for loading Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -35,14 +35,14 @@
|
|
|
35
35
|
"node": ">=20.19"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@uniweb/
|
|
39
|
-
"@uniweb/
|
|
38
|
+
"@uniweb/core": "0.7.12",
|
|
39
|
+
"@uniweb/theming": "0.1.3"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@vitejs/plugin-react": "^4.5.2",
|
|
43
43
|
"vite": "^7.3.1",
|
|
44
|
-
"vitest": "^
|
|
45
|
-
"@uniweb/build": "0.14.
|
|
44
|
+
"vitest": "^4.1.7",
|
|
45
|
+
"@uniweb/build": "0.14.5"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"react": "^18.0.0 || ^19.0.0",
|
package/src/prepare-props.js
CHANGED
|
@@ -4,13 +4,12 @@
|
|
|
4
4
|
* Prepares props for foundation components with:
|
|
5
5
|
* - Param defaults from runtime schema
|
|
6
6
|
* - Guaranteed content structure (no null checks needed)
|
|
7
|
-
* -
|
|
8
|
-
* and a cascade match exists (see applyEntityShape below)
|
|
7
|
+
* - Field defaults applied to `content.data` items from the bound schemas
|
|
9
8
|
*
|
|
10
9
|
* This enables simpler component code by ensuring predictable prop shapes.
|
|
11
10
|
*/
|
|
12
11
|
|
|
13
|
-
import {
|
|
12
|
+
import { isRichSchema } from '@uniweb/core'
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Guarantee item has flat content structure
|
|
@@ -116,25 +115,27 @@ function applySchemaToObject(obj, schema) {
|
|
|
116
115
|
result[field] = defaultValue
|
|
117
116
|
}
|
|
118
117
|
|
|
119
|
-
//
|
|
120
|
-
if (typeof fieldDef
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
118
|
+
// Bare type strings ('string', 'decimal', …) carry nothing more to apply.
|
|
119
|
+
if (typeof fieldDef !== 'object') continue
|
|
120
|
+
|
|
121
|
+
// Inline picklist (`enum`): if the value is set but not among the allowed
|
|
122
|
+
// values, fall back to the default.
|
|
123
|
+
if (Array.isArray(fieldDef.enum)) {
|
|
124
|
+
if (result[field] !== undefined && !fieldDef.enum.includes(result[field]) && defaultValue !== undefined) {
|
|
125
|
+
result[field] = defaultValue
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
//
|
|
130
|
-
if (
|
|
131
|
-
result[field] = applySchemaToObject(result[field], fieldDef.
|
|
129
|
+
// Nested object → recurse into its field map.
|
|
130
|
+
if (fieldDef.type === 'object' && fieldDef.fields && result[field]) {
|
|
131
|
+
result[field] = applySchemaToObject(result[field], fieldDef.fields)
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
//
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
// Array of objects → apply the element field map to each item.
|
|
135
|
+
if (fieldDef.type === 'array' && fieldDef.items && Array.isArray(result[field])) {
|
|
136
|
+
const items = fieldDef.items
|
|
137
|
+
if (items && typeof items === 'object' && items.type === 'object' && items.fields) {
|
|
138
|
+
result[field] = result[field].map((item) => applySchemaToObject(item, items.fields))
|
|
138
139
|
}
|
|
139
140
|
}
|
|
140
141
|
}
|
|
@@ -265,54 +266,6 @@ export function applySchemas(data, schemas) {
|
|
|
265
266
|
return result
|
|
266
267
|
}
|
|
267
268
|
|
|
268
|
-
/**
|
|
269
|
-
* Apply entity-shape guarantees based on `data.entity` declaration.
|
|
270
|
-
*
|
|
271
|
-
* When a component declares `data: { entity: 'articles' }` **and** the
|
|
272
|
-
* cascade produced a match for that schema (`content.data.articles` is
|
|
273
|
-
* present), normalize:
|
|
274
|
-
* - `content.data.articles` to an array (missing → `[]` is *not* added;
|
|
275
|
-
* absence is preserved as a signal of "no source"). This only shapes
|
|
276
|
-
* when a cascade match exists — if the key is missing entirely, it
|
|
277
|
-
* stays missing.
|
|
278
|
-
* - On template pages, `content.data[singular(entity)]` is guaranteed
|
|
279
|
-
* to exist (defaulting to `null`) so components can do
|
|
280
|
-
* `if (!article) return <NotFound />` without a `?.` chain.
|
|
281
|
-
*
|
|
282
|
-
* The `undefined` vs `[]` vs `null` distinctions are load-bearing:
|
|
283
|
-
* - `content.data.articles === undefined` → no query for this entity
|
|
284
|
-
* - `content.data.articles === []` → query ran, returned empty
|
|
285
|
-
* - `content.data.article === null` → on template page, item not found
|
|
286
|
-
* - `content.data.article === {...}` → on template page, item resolved
|
|
287
|
-
*
|
|
288
|
-
* @param {Object} data - content.data (already merged with entity data)
|
|
289
|
-
* @param {Object|null} entityMeta - runtime data meta (`{ type, limit }`)
|
|
290
|
-
* @param {Object|null} dynamicContext - set when block is on a template page
|
|
291
|
-
* @returns {Object} data with entity-shape guarantees applied
|
|
292
|
-
*/
|
|
293
|
-
export function applyEntityShape(data, entityMeta, dynamicContext) {
|
|
294
|
-
if (!entityMeta?.type || !data) return data || {}
|
|
295
|
-
|
|
296
|
-
const plural = entityMeta.type
|
|
297
|
-
const result = { ...data }
|
|
298
|
-
|
|
299
|
-
// Only shape the collection when it was delivered. Absence stays absence.
|
|
300
|
-
if (plural in result && !Array.isArray(result[plural]) && result[plural] != null) {
|
|
301
|
-
// Delivered but wrong shape — coerce to array (single item → [item]).
|
|
302
|
-
result[plural] = [result[plural]]
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// On template pages, guarantee the singular key exists (null = not found).
|
|
306
|
-
if (dynamicContext) {
|
|
307
|
-
const singular = singularize(plural) || plural
|
|
308
|
-
if (singular !== plural && !(singular in result)) {
|
|
309
|
-
result[singular] = null
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
return result
|
|
314
|
-
}
|
|
315
|
-
|
|
316
269
|
/**
|
|
317
270
|
* Apply param defaults from runtime schema
|
|
318
271
|
*
|
|
@@ -486,16 +439,6 @@ export function prepareProps(block, meta, entityData = null) {
|
|
|
486
439
|
// Guarantee content structure
|
|
487
440
|
let content = guaranteeContentStructure(block.parsedContent)
|
|
488
441
|
|
|
489
|
-
// Apply entity-shape guarantees when the component declared `data.entity`
|
|
490
|
-
// and a cascade match exists. Preserves `undefined` vs `[]` vs `null`
|
|
491
|
-
// distinctions so components can differentiate "no source" from
|
|
492
|
-
// "empty source" from "template item not found."
|
|
493
|
-
const entityMeta = meta?.data || null
|
|
494
|
-
if (entityMeta && content.data) {
|
|
495
|
-
const dynamicContext = block.dynamicContext || block.page?.dynamicContext || null
|
|
496
|
-
content.data = applyEntityShape(content.data, entityMeta, dynamicContext)
|
|
497
|
-
}
|
|
498
|
-
|
|
499
442
|
// Apply schemas to content.data
|
|
500
443
|
const schemas = meta?.schemas || null
|
|
501
444
|
if (schemas && content.data) {
|