@uniweb/runtime 0.6.25 → 0.6.26
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/ssr.js +5 -5
- package/dist/ssr.js.map +1 -1
- package/package.json +2 -2
- package/src/components/BlockRenderer.jsx +25 -23
- package/src/components/Blocks.jsx +5 -4
- package/src/components/PageRenderer.jsx +15 -10
- package/src/ssr-renderer.js +19 -13
package/dist/ssr.js
CHANGED
|
@@ -280,7 +280,7 @@ function renderBackground(background) {
|
|
|
280
280
|
"aria-hidden": "true"
|
|
281
281
|
}, ...children);
|
|
282
282
|
}
|
|
283
|
-
function renderBlock(block, {
|
|
283
|
+
function renderBlock(block, { as = "section" } = {}) {
|
|
284
284
|
const Component = block.initComponent();
|
|
285
285
|
if (!Component) {
|
|
286
286
|
return React.createElement("div", {
|
|
@@ -306,7 +306,7 @@ function renderBlock(block, { pure = false, as = void 0 } = {}) {
|
|
|
306
306
|
}
|
|
307
307
|
}
|
|
308
308
|
const componentProps = { content, params, block };
|
|
309
|
-
if (
|
|
309
|
+
if (!as) {
|
|
310
310
|
return React.createElement(Component, componentProps);
|
|
311
311
|
}
|
|
312
312
|
const { background, ...wrapperProps } = getWrapperProps(block);
|
|
@@ -316,7 +316,7 @@ function renderBlock(block, { pure = false, as = void 0 } = {}) {
|
|
|
316
316
|
}
|
|
317
317
|
const hasBackground = background?.mode && meta?.background !== "self";
|
|
318
318
|
block.hasBackground = hasBackground;
|
|
319
|
-
const wrapperTag = as !==
|
|
319
|
+
const wrapperTag = as !== "section" ? as : Component.as || "section";
|
|
320
320
|
if (hasBackground) {
|
|
321
321
|
return React.createElement(
|
|
322
322
|
wrapperTag,
|
|
@@ -389,13 +389,13 @@ function initPrerender(content, foundation, options = {}) {
|
|
|
389
389
|
if (content.config?.base && uniweb.activeWebsite?.setBasePath) {
|
|
390
390
|
uniweb.activeWebsite.setBasePath(content.config.base);
|
|
391
391
|
}
|
|
392
|
-
uniweb.childBlockRenderer = function InlineChildBlocks({ blocks, from,
|
|
392
|
+
uniweb.childBlockRenderer = function InlineChildBlocks({ blocks, from, wrapAs }) {
|
|
393
393
|
const blockList = blocks || from?.childBlocks || [];
|
|
394
394
|
return blockList.map(
|
|
395
395
|
(childBlock, index) => React.createElement(
|
|
396
396
|
React.Fragment,
|
|
397
397
|
{ key: childBlock.id || index },
|
|
398
|
-
renderBlock(childBlock, {
|
|
398
|
+
renderBlock(childBlock, { as: wrapAs || null })
|
|
399
399
|
)
|
|
400
400
|
);
|
|
401
401
|
};
|
package/dist/ssr.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr.js","sources":["../src/prepare-props.js","../src/default-404.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 *\n * This enables simpler component code by ensuring predictable prop shapes.\n */\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 }\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 // 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 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] = 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 * Prepare props for a component with runtime guarantees\n *\n * @param {Object} block - The block instance\n * @param {Object} meta - Runtime metadata for the component (from meta[componentName])\n * @returns {Object} Prepared props: { content, params }\n */\nexport function prepareProps(block, meta) {\n // Apply param defaults\n const defaults = meta?.defaults || {}\n const params = applyDefaults(block.properties, defaults)\n\n // Guarantee content structure\n const 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 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 * 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'\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 * @param {Block} block - Block instance to render\n * @param {Object} [options]\n * @param {boolean} [options.pure=false] - Render component without section wrapper (used by ChildBlocks)\n * @returns {React.ReactElement}\n */\nexport function renderBlock(block, { pure = false, as = undefined } = {}) {\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 // Build content and params with runtime guarantees\n const meta = getComponentMeta(block.type)\n const prepared = prepareProps(block, meta)\n const params = prepared.params\n const content = { ...prepared.content, ...block.properties }\n\n // Resolve inherited entity data (mirrors BlockRenderer.jsx)\n // EntityStore walks page/site hierarchy to find data matching meta.inheritData\n const entityStore = block.website?.entityStore\n if (entityStore) {\n const resolved = entityStore.resolve(block, meta)\n if (resolved.status === 'ready' && resolved.data) {\n const merged = { ...content.data }\n for (const key of Object.keys(resolved.data)) {\n if (merged[key] === undefined) {\n merged[key] = resolved.data[key]\n }\n }\n content.data = merged\n }\n }\n\n const componentProps = { content, params, block }\n\n // Pure mode: render component without section wrapper (used by ChildBlocks)\n if (pure) {\n return React.createElement(Component, componentProps)\n }\n\n // Background handling (mirrors BlockRenderer.jsx)\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 // Use Component.as as the wrapper tag (default: 'section').\n // An explicit `as` prop (e.g. 'div' from ChildBlocks) overrides Component.as,\n // mirroring the BlockRenderer.jsx Wrapper resolution logic.\n const wrapperTag = (as !== undefined && 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 * 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, options = {}) {\n const { onProgress = () => {} } = options\n\n onProgress('Initializing runtime...')\n const uniweb = createUniweb(content)\n uniweb.setFoundation(foundation)\n\n // Set foundation capabilities (Layout, props, etc.)\n if (foundation.default?.capabilities) {\n uniweb.setFoundationConfig(foundation.default.capabilities)\n }\n\n // Attach layout metadata (areas, transitions, defaults)\n if (foundation.default?.layoutMeta && uniweb.foundationConfig) {\n uniweb.foundationConfig.layoutMeta = foundation.default.layoutMeta\n }\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 as='div' so nested blocks use <div> wrapper (not <section>)\n // matching the client and avoiding React hydration mismatch (error #418)\n uniweb.childBlockRenderer = function InlineChildBlocks({ blocks, from, pure = false, as = 'div' }) {\n const blockList = blocks || from?.childBlocks || []\n return blockList.map((childBlock, index) =>\n React.createElement(React.Fragment, { key: childBlock.id || index },\n renderBlock(childBlock, { pure, as })\n )\n )\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/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 *\n * This enables simpler component code by ensuring predictable prop shapes.\n */\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 }\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 // 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 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] = 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 * Prepare props for a component with runtime guarantees\n *\n * @param {Object} block - The block instance\n * @param {Object} meta - Runtime metadata for the component (from meta[componentName])\n * @returns {Object} Prepared props: { content, params }\n */\nexport function prepareProps(block, meta) {\n // Apply param defaults\n const defaults = meta?.defaults || {}\n const params = applyDefaults(block.properties, defaults)\n\n // Guarantee content structure\n const 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 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 * 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'\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 // Build content and params with runtime guarantees\n const meta = getComponentMeta(block.type)\n const prepared = prepareProps(block, meta)\n const params = prepared.params\n const content = { ...prepared.content, ...block.properties }\n\n // Resolve inherited entity data (mirrors BlockRenderer.jsx)\n // EntityStore walks page/site hierarchy to find data matching meta.inheritData\n const entityStore = block.website?.entityStore\n if (entityStore) {\n const resolved = entityStore.resolve(block, meta)\n if (resolved.status === 'ready' && resolved.data) {\n const merged = { ...content.data }\n for (const key of Object.keys(resolved.data)) {\n if (merged[key] === undefined) {\n merged[key] = resolved.data[key]\n }\n }\n content.data = merged\n }\n }\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 * 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, options = {}) {\n const { onProgress = () => {} } = options\n\n onProgress('Initializing runtime...')\n const uniweb = createUniweb(content)\n uniweb.setFoundation(foundation)\n\n // Set foundation capabilities (Layout, props, etc.)\n if (foundation.default?.capabilities) {\n uniweb.setFoundationConfig(foundation.default.capabilities)\n }\n\n // Attach layout metadata (areas, transitions, defaults)\n if (foundation.default?.layoutMeta && uniweb.foundationConfig) {\n uniweb.foundationConfig.layoutMeta = foundation.default.layoutMeta\n }\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 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.6.
|
|
3
|
+
"version": "0.6.26",
|
|
4
4
|
"description": "Minimal runtime for loading Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@vitejs/plugin-react": "^4.5.2",
|
|
43
43
|
"vite": "^7.3.1",
|
|
44
|
-
"@uniweb/build": "0.8.
|
|
44
|
+
"@uniweb/build": "0.8.29"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -68,13 +68,16 @@ const getWrapperProps = (block) => {
|
|
|
68
68
|
/**
|
|
69
69
|
* BlockRenderer component
|
|
70
70
|
*
|
|
71
|
+
* Two rendering modes:
|
|
72
|
+
* - Bare (as=null/false): component only, no wrapper — used by ChildBlocks (default)
|
|
73
|
+
* - Section (as='section'/'div'/etc.): full treatment with wrapper element, context
|
|
74
|
+
* classes, background, section ID — used by page-level Blocks component
|
|
75
|
+
*
|
|
71
76
|
* @param {Object} props
|
|
72
77
|
* @param {Block} props.block - Block instance to render
|
|
73
|
-
* @param {
|
|
74
|
-
* @param {string|false} props.as - Element type to render as ('section', 'div', 'article', etc.) or false for Fragment
|
|
75
|
-
* @param {Object} props.extra - Extra props to pass to the component
|
|
78
|
+
* @param {string|null} props.as - Wrapper element tag for section mode, or null/false for bare mode
|
|
76
79
|
*/
|
|
77
|
-
export default function BlockRenderer({ block,
|
|
80
|
+
export default function BlockRenderer({ block, as = 'section' }) {
|
|
78
81
|
const Component = block.initComponent()
|
|
79
82
|
|
|
80
83
|
// Entity-aware data resolution via EntityStore
|
|
@@ -142,6 +145,19 @@ export default function BlockRenderer({ block, pure = false, as = 'section', ext
|
|
|
142
145
|
content.data = merged
|
|
143
146
|
}
|
|
144
147
|
|
|
148
|
+
const componentProps = {
|
|
149
|
+
content,
|
|
150
|
+
params,
|
|
151
|
+
block
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Bare mode — component only, no wrapper or section chrome.
|
|
155
|
+
// Used by ChildBlocks for grid cells, tab panels, inline children, insets.
|
|
156
|
+
if (!as) {
|
|
157
|
+
return <Component {...componentProps} />
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Section mode — full treatment with wrapper, context classes, background.
|
|
145
161
|
const { background, ...wrapperProps } = getWrapperProps(block)
|
|
146
162
|
|
|
147
163
|
// Merge Component.className (static classes declared on the component function)
|
|
@@ -163,30 +179,16 @@ export default function BlockRenderer({ block, pure = false, as = 'section', ext
|
|
|
163
179
|
// and let the engine background show through.
|
|
164
180
|
block.hasBackground = hasBackground
|
|
165
181
|
|
|
166
|
-
const componentProps = {
|
|
167
|
-
content,
|
|
168
|
-
params,
|
|
169
|
-
block
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (pure) {
|
|
173
|
-
return <Component {...componentProps} extra={extra} />
|
|
174
|
-
}
|
|
175
|
-
|
|
176
182
|
// Determine wrapper element:
|
|
177
|
-
// - as
|
|
178
|
-
// - as
|
|
179
|
-
// - Component.as → use component's declared tag
|
|
183
|
+
// - Explicit as prop (not 'section') → use as prop directly
|
|
184
|
+
// - Component.as → use component's declared tag (e.g., Header.as = 'header')
|
|
180
185
|
// - fallback → 'section'
|
|
181
|
-
const
|
|
182
|
-
const Wrapper = as === false ? React.Fragment : (as !== 'section' ? as : componentAs || 'section')
|
|
183
|
-
// Fragment doesn't accept props, so only pass them for real elements
|
|
184
|
-
const wrapperElementProps = as === false ? {} : wrapperProps
|
|
186
|
+
const Wrapper = as !== 'section' ? as : (Component.as || 'section')
|
|
185
187
|
|
|
186
188
|
// Render with or without background
|
|
187
189
|
if (hasBackground) {
|
|
188
190
|
return (
|
|
189
|
-
<Wrapper {...
|
|
191
|
+
<Wrapper {...wrapperProps}>
|
|
190
192
|
{/* Background layer (positioned absolutely) */}
|
|
191
193
|
<Background
|
|
192
194
|
mode={background.mode}
|
|
@@ -207,7 +209,7 @@ export default function BlockRenderer({ block, pure = false, as = 'section', ext
|
|
|
207
209
|
|
|
208
210
|
// No background - simpler render without extra wrapper
|
|
209
211
|
return (
|
|
210
|
-
<Wrapper {...
|
|
212
|
+
<Wrapper {...wrapperProps}>
|
|
211
213
|
<Component {...componentProps} />
|
|
212
214
|
</Wrapper>
|
|
213
215
|
)
|
|
@@ -9,18 +9,19 @@ import React from 'react'
|
|
|
9
9
|
import BlockRenderer from './BlockRenderer.jsx'
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Render a list of blocks
|
|
12
|
+
* Render a list of blocks as top-level sections.
|
|
13
|
+
* Used by Layout to pre-render each area (header, body, footer, panels).
|
|
14
|
+
* Each block gets full section treatment (wrapper, context classes, background).
|
|
13
15
|
*
|
|
14
16
|
* @param {Object} props
|
|
15
17
|
* @param {Block[]} props.blocks - Array of Block instances to render
|
|
16
|
-
* @param {Object} [props.extra] - Extra props to pass to each block
|
|
17
18
|
*/
|
|
18
|
-
export default function Blocks({ blocks
|
|
19
|
+
export default function Blocks({ blocks }) {
|
|
19
20
|
if (!blocks || blocks.length === 0) return null
|
|
20
21
|
|
|
21
22
|
return blocks.map((block, index) => (
|
|
22
23
|
<React.Fragment key={block.id || index}>
|
|
23
|
-
<BlockRenderer block={block}
|
|
24
|
+
<BlockRenderer block={block} />
|
|
24
25
|
</React.Fragment>
|
|
25
26
|
))
|
|
26
27
|
}
|
|
@@ -16,29 +16,34 @@ import { Default404 } from '../default-404.js'
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* ChildBlocks - renders child blocks of a block
|
|
19
|
-
* Exposed for use by foundation components
|
|
19
|
+
* Exposed for use by foundation components via @uniweb/kit
|
|
20
|
+
*
|
|
21
|
+
* By default, renders each child as a bare component (no wrapper element,
|
|
22
|
+
* no context classes, no background). This is the common case for grid cells,
|
|
23
|
+
* tab panels, carousel slides, and inline children.
|
|
24
|
+
*
|
|
25
|
+
* Pass `wrapAs` to opt into full section treatment (wrapper element, context
|
|
26
|
+
* classes, background, section ID) — rare, for independent nested sections.
|
|
20
27
|
*
|
|
21
28
|
* @param {Object} props
|
|
22
29
|
* @param {Block[]} props.blocks - Explicit array of blocks to render
|
|
23
30
|
* @param {Block} props.from - Parent block to extract childBlocks from (convenience shorthand)
|
|
24
|
-
* @param {
|
|
25
|
-
* @param {string|false} props.as - Element type to render as (default: 'div' for nested blocks)
|
|
26
|
-
* @param {Object} props.extra - Extra props to pass to each component
|
|
31
|
+
* @param {string} [props.wrapAs] - Wrapper element tag for section treatment ('div', 'article', etc.)
|
|
27
32
|
*
|
|
28
33
|
* @example
|
|
29
|
-
* //
|
|
30
|
-
* <ChildBlocks
|
|
34
|
+
* // Bare rendering (default) — grid cells, tabs, inline children
|
|
35
|
+
* <ChildBlocks from={block} />
|
|
31
36
|
*
|
|
32
37
|
* @example
|
|
33
|
-
* //
|
|
34
|
-
* <ChildBlocks from={block} />
|
|
38
|
+
* // Section treatment — each child gets its own wrapper, context, background
|
|
39
|
+
* <ChildBlocks from={block} wrapAs="div" />
|
|
35
40
|
*/
|
|
36
|
-
export function ChildBlocks({ blocks, from,
|
|
41
|
+
export function ChildBlocks({ blocks, from, wrapAs }) {
|
|
37
42
|
const blockList = blocks || from?.childBlocks || []
|
|
38
43
|
|
|
39
44
|
return blockList.map((childBlock, index) => (
|
|
40
45
|
<React.Fragment key={childBlock.id || index}>
|
|
41
|
-
<BlockRenderer block={childBlock}
|
|
46
|
+
<BlockRenderer block={childBlock} as={wrapAs || null} />
|
|
42
47
|
</React.Fragment>
|
|
43
48
|
))
|
|
44
49
|
}
|
package/src/ssr-renderer.js
CHANGED
|
@@ -229,12 +229,16 @@ export function renderBackground(background) {
|
|
|
229
229
|
* Render a single block for SSR.
|
|
230
230
|
* Mirrors BlockRenderer.jsx but without hooks (no runtime data fetching).
|
|
231
231
|
*
|
|
232
|
+
* Two modes (mirrors client BlockRenderer):
|
|
233
|
+
* - Bare (as=null/false): component only, no wrapper
|
|
234
|
+
* - Section (as='section'/'div'/etc.): full treatment with wrapper, context, background
|
|
235
|
+
*
|
|
232
236
|
* @param {Block} block - Block instance to render
|
|
233
237
|
* @param {Object} [options]
|
|
234
|
-
* @param {
|
|
238
|
+
* @param {string|null} [options.as='section'] - Wrapper element tag, or null/false for bare mode
|
|
235
239
|
* @returns {React.ReactElement}
|
|
236
240
|
*/
|
|
237
|
-
export function renderBlock(block, {
|
|
241
|
+
export function renderBlock(block, { as = 'section' } = {}) {
|
|
238
242
|
const Component = block.initComponent()
|
|
239
243
|
|
|
240
244
|
if (!Component) {
|
|
@@ -268,12 +272,13 @@ export function renderBlock(block, { pure = false, as = undefined } = {}) {
|
|
|
268
272
|
|
|
269
273
|
const componentProps = { content, params, block }
|
|
270
274
|
|
|
271
|
-
//
|
|
272
|
-
|
|
275
|
+
// Bare mode: component only, no wrapper or section chrome.
|
|
276
|
+
// Used by ChildBlocks for grid cells, tab panels, inline children, insets.
|
|
277
|
+
if (!as) {
|
|
273
278
|
return React.createElement(Component, componentProps)
|
|
274
279
|
}
|
|
275
280
|
|
|
276
|
-
//
|
|
281
|
+
// Section mode: full treatment with wrapper, context classes, background.
|
|
277
282
|
const { background, ...wrapperProps } = getWrapperProps(block)
|
|
278
283
|
|
|
279
284
|
// Merge Component.className (static classes declared on the component function)
|
|
@@ -288,10 +293,11 @@ export function renderBlock(block, { pure = false, as = undefined } = {}) {
|
|
|
288
293
|
const hasBackground = background?.mode && meta?.background !== 'self'
|
|
289
294
|
block.hasBackground = hasBackground
|
|
290
295
|
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
|
|
296
|
+
// Determine wrapper element:
|
|
297
|
+
// - Explicit as (not 'section') → use as prop directly
|
|
298
|
+
// - Component.as → use component's declared tag (e.g., Header.as = 'header')
|
|
299
|
+
// - fallback → 'section'
|
|
300
|
+
const wrapperTag = as !== 'section' ? as : (Component.as || 'section')
|
|
295
301
|
|
|
296
302
|
if (hasBackground) {
|
|
297
303
|
return React.createElement(wrapperTag, wrapperProps,
|
|
@@ -401,13 +407,13 @@ export function initPrerender(content, foundation, options = {}) {
|
|
|
401
407
|
|
|
402
408
|
// Set childBlockRenderer so ChildBlocks/Visual/Render work during prerender.
|
|
403
409
|
// Mirrors the client's ChildBlocks component in PageRenderer.jsx:
|
|
404
|
-
// - default
|
|
405
|
-
//
|
|
406
|
-
uniweb.childBlockRenderer = function InlineChildBlocks({ blocks, from,
|
|
410
|
+
// - default bare rendering (no wrapAs) — component only, no wrapper
|
|
411
|
+
// - pass wrapAs to opt into full section treatment
|
|
412
|
+
uniweb.childBlockRenderer = function InlineChildBlocks({ blocks, from, wrapAs }) {
|
|
407
413
|
const blockList = blocks || from?.childBlocks || []
|
|
408
414
|
return blockList.map((childBlock, index) =>
|
|
409
415
|
React.createElement(React.Fragment, { key: childBlock.id || index },
|
|
410
|
-
renderBlock(childBlock, {
|
|
416
|
+
renderBlock(childBlock, { as: wrapAs || null })
|
|
411
417
|
)
|
|
412
418
|
)
|
|
413
419
|
}
|