@uniweb/runtime 0.6.15 → 0.6.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/ssr.js +35 -0
  2. package/dist/ssr.js.map +1 -1
  3. package/package.json +5 -4
  4. package/src/components/BlockRenderer.jsx +1 -0
  5. package/src/components/Layout.jsx +3 -3
  6. package/src/components/PageRenderer.jsx +20 -8
  7. package/src/default-404.js +60 -0
  8. package/src/setup.js +3 -0
  9. package/src/ssr-renderer.js +69 -0
  10. package/src/ssr.js +3 -0
  11. package/dist/app/_importmap/@uniweb-core.js +0 -2
  12. package/dist/app/_importmap/@uniweb-core.js.map +0 -1
  13. package/dist/app/_importmap/react-dom.js +0 -2
  14. package/dist/app/_importmap/react-dom.js.map +0 -1
  15. package/dist/app/_importmap/react-jsx-dev-runtime.js +0 -2
  16. package/dist/app/_importmap/react-jsx-dev-runtime.js.map +0 -1
  17. package/dist/app/_importmap/react-jsx-runtime.js +0 -2
  18. package/dist/app/_importmap/react-jsx-runtime.js.map +0 -1
  19. package/dist/app/_importmap/react.js +0 -2
  20. package/dist/app/_importmap/react.js.map +0 -1
  21. package/dist/app/assets/_commonjsHelpers-CqkleIqs.js +0 -2
  22. package/dist/app/assets/_commonjsHelpers-CqkleIqs.js.map +0 -1
  23. package/dist/app/assets/_importmap_react-dWoQamCw.js +0 -2
  24. package/dist/app/assets/_importmap_react-dWoQamCw.js.map +0 -1
  25. package/dist/app/assets/index-C0udIITE.js +0 -9
  26. package/dist/app/assets/index-C0udIITE.js.map +0 -1
  27. package/dist/app/assets/index-C6TPxGbh.js +0 -133
  28. package/dist/app/assets/index-C6TPxGbh.js.map +0 -1
  29. package/dist/app/assets/index-CsyMBO9p.js +0 -8
  30. package/dist/app/assets/index-CsyMBO9p.js.map +0 -1
  31. package/dist/app/assets/index-kA4PVysc.js +0 -2
  32. package/dist/app/assets/index-kA4PVysc.js.map +0 -1
  33. package/dist/app/assets/jsx-runtime-C3x2e0aW.js +0 -2
  34. package/dist/app/assets/jsx-runtime-C3x2e0aW.js.map +0 -1
  35. package/dist/app/index.html +0 -31
  36. package/dist/app/manifest.json +0 -19
package/dist/ssr.js CHANGED
@@ -126,6 +126,10 @@ function getComponentMeta(componentName) {
126
126
  function getComponentDefaults(componentName) {
127
127
  return globalThis.uniweb?.getComponentDefaults?.(componentName) || {};
128
128
  }
129
+ function default404Html(basePath = "") {
130
+ const homeHref = basePath ? `${basePath}/` : "/";
131
+ return `<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"><h1 style="font-size:3rem;font-weight:bold;color:#1f2937;margin-bottom:1rem">404</h1><p style="color:#64748b;margin-bottom:2rem">Page not found</p><a href="${homeHref}" style="color:#3b82f6;text-decoration:underline">Go to homepage</a></div>`;
132
+ }
129
133
  const VALID_CONTEXTS = ["light", "medium", "dark"];
130
134
  function getWrapperProps(block) {
131
135
  const theme = block.themeName;
@@ -491,11 +495,42 @@ ${options.sectionOverrideCSS}
491
495
  }
492
496
  return result;
493
497
  }
498
+ function generate404Html({ baseHtml, website, siteContent }) {
499
+ const dynamicTemplates = siteContent.pages?.filter((p) => p.isDynamic) || [];
500
+ const routePatterns = dynamicTemplates.map((p) => {
501
+ const escaped = p.route.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/:[^/]+/g, "[^\\/]+");
502
+ return `^${escaped}\\/?$`;
503
+ });
504
+ let html = baseHtml;
505
+ const notFoundPage = website.getNotFoundPage();
506
+ if (notFoundPage) {
507
+ const notFoundResult = renderPage(notFoundPage, website);
508
+ if (notFoundResult && !notFoundResult.error) {
509
+ html = injectPageContent(html, notFoundResult.renderedContent, notFoundPage, {
510
+ sectionOverrideCSS: notFoundResult.sectionOverrideCSS
511
+ });
512
+ }
513
+ } else {
514
+ const basePath = website.basePath || "";
515
+ html = html.replace(
516
+ /<div id="root">[\s\S]*?<\/div>/,
517
+ `<div id="root">${default404Html(basePath)}</div>`
518
+ );
519
+ }
520
+ if (routePatterns.length > 0) {
521
+ const patternList = routePatterns.map((p) => `/${p}/`).join(",");
522
+ const dynamicScript = `<script>(function(){var p=[${patternList}],r=window.location.pathname;if(p.some(function(x){return x.test(r)})){var el=document.getElementById('root');if(el)el.innerHTML='';}})()<\/script>`;
523
+ html = html.replace("</body>", `${dynamicScript}
524
+ </body>`);
525
+ }
526
+ return { html, hasNotFoundPage: !!notFoundPage };
527
+ }
494
528
  export {
495
529
  applyDefaults,
496
530
  applySchemas,
497
531
  classifyRenderError,
498
532
  escapeHtml,
533
+ generate404Html,
499
534
  getComponentDefaults,
500
535
  getComponentMeta,
501
536
  getWrapperProps,
package/dist/ssr.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ssr.js","sources":["../src/prepare-props.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 * 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'\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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\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"],"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 * @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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\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.15",
3
+ "version": "0.6.16",
4
4
  "description": "Minimal runtime for loading Uniweb foundations",
5
5
  "type": "module",
6
6
  "exports": {
@@ -8,7 +8,8 @@
8
8
  "./ssr": "./dist/ssr.js",
9
9
  "./provider": "./src/RuntimeProvider.jsx",
10
10
  "./setup": "./src/setup.js",
11
- "./foundation-loader": "./src/foundation-loader.js"
11
+ "./foundation-loader": "./src/foundation-loader.js",
12
+ "./data-fetcher-client": "./src/data-fetcher-client.js"
12
13
  },
13
14
  "files": [
14
15
  "src",
@@ -34,13 +35,13 @@
34
35
  "node": ">=20.19"
35
36
  },
36
37
  "dependencies": {
37
- "@uniweb/core": "0.5.14",
38
+ "@uniweb/core": "0.5.15",
38
39
  "@uniweb/theming": "0.1.2"
39
40
  },
40
41
  "devDependencies": {
41
42
  "@vitejs/plugin-react": "^4.5.2",
42
43
  "vite": "^7.3.1",
43
- "@uniweb/build": "0.8.19"
44
+ "@uniweb/build": "0.8.20"
44
45
  },
45
46
  "peerDependencies": {
46
47
  "react": "^18.0.0 || ^19.0.0",
@@ -80,6 +80,7 @@ export default function BlockRenderer({ block, pure = false, as = 'section', ext
80
80
  // Entity-aware data resolution via EntityStore
81
81
  const entityStore = block.website.entityStore
82
82
  const meta = getComponentMeta(block.type)
83
+
83
84
  const resolved = entityStore.resolve(block, meta)
84
85
 
85
86
  // Async data for when resolve returns 'pending' (runtime fetches)
@@ -39,11 +39,11 @@ import Blocks from './Blocks.jsx'
39
39
  */
40
40
  function DefaultLayout({ header, body, footer }) {
41
41
  return (
42
- <>
42
+ <div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
43
43
  {header && <header>{header}</header>}
44
- {body && <main>{body}</main>}
44
+ {body && <main style={{ flex: 1 }}>{body}</main>}
45
45
  {footer && <footer>{footer}</footer>}
46
- </>
46
+ </div>
47
47
  )
48
48
  }
49
49
 
@@ -6,12 +6,13 @@
6
6
  * Manages head meta tags for SEO and social sharing.
7
7
  */
8
8
 
9
- import React, { useMemo, useEffect, useRef } from 'react'
9
+ import React, { useMemo, useEffect, useReducer, useRef } from 'react'
10
10
  import { useLocation } from 'react-router-dom'
11
11
  import BlockRenderer from './BlockRenderer.jsx'
12
12
  import Layout from './Layout.jsx'
13
13
  import { useHeadMeta } from '../hooks/useHeadMeta.js'
14
14
  import { buildSectionOverrides } from '@uniweb/theming'
15
+ import { Default404 } from '../default-404.js'
15
16
 
16
17
  /**
17
18
  * ChildBlocks - renders child blocks of a block
@@ -110,6 +111,9 @@ export default function PageRenderer() {
110
111
  const website = uniweb?.activeWebsite
111
112
  const siteName = website?.name || ''
112
113
 
114
+ // Force re-render counter — incremented by DataStore listener below
115
+ const [, forceUpdate] = useReducer((x) => x + 1, 0)
116
+
113
117
  // Resolve page from current URL and sync website.activePage immediately.
114
118
  // This must happen synchronously (not in an effect) so child components
115
119
  // like Header see the correct activePage during the same render cycle.
@@ -131,17 +135,25 @@ export default function PageRenderer() {
131
135
  // Manage head meta tags
132
136
  useHeadMeta(headMeta, { siteName })
133
137
 
138
+ // For dynamic pages created before collection data was available (hard reload),
139
+ // the page title starts as the template name (e.g. '[id]'). Subscribe to
140
+ // DataStore updates so we re-render once the collection loads — _createDynamicPage
141
+ // then runs again with fresh data and sets the correct title/notFound state.
142
+ const isDynamicPending = !!(page?.dynamicContext && !website?._dynamicPageCache?.has(location.pathname))
143
+ useEffect(() => {
144
+ if (!isDynamicPending || !website?.dataStore) return
145
+ return website.dataStore.onUpdate(forceUpdate)
146
+ }, [isDynamicPending, website])
147
+
134
148
  if (!page) {
135
- // No page and no 404 page defined - show minimal fallback with debug info
149
+ // No page and no 404 page defined - show shared fallback + dev debug info
136
150
  const requestedPath = location.pathname
137
151
  const isDev = import.meta.env?.DEV
138
152
  return (
139
- <div className="page-not-found" style={{ padding: '4rem 2rem', textAlign: 'center' }}>
140
- <h1 style={{ fontSize: '3rem', fontWeight: 'bold', color: '#1f2937', marginBottom: '1rem' }}>404</h1>
141
- <p style={{ color: '#64748b', marginBottom: '2rem' }}>Page not found</p>
142
- <a href="/" style={{ color: '#3b82f6', textDecoration: 'underline' }}>Go to homepage</a>
153
+ <>
154
+ <Default404 />
143
155
  {isDev && (
144
- <div style={{ marginTop: '2rem', padding: '1rem', background: '#f1f5f9', borderRadius: '0.5rem', textAlign: 'left', maxWidth: '32rem', margin: '2rem auto 0' }}>
156
+ <div style={{ marginTop: '0', padding: '1rem', background: '#f1f5f9', borderRadius: '0.5rem', textAlign: 'left', maxWidth: '32rem', margin: '0 auto' }}>
145
157
  <p style={{ fontWeight: '600', color: '#475569', marginBottom: '0.5rem' }}>Debug info</p>
146
158
  <p style={{ fontSize: '0.875rem', color: '#64748b' }}>Path: {requestedPath}</p>
147
159
  <p style={{ fontSize: '0.875rem', color: '#64748b' }}>Locale: {website?.getActiveLocale?.() || 'unknown'}</p>
@@ -149,7 +161,7 @@ export default function PageRenderer() {
149
161
  <p style={{ fontSize: '0.75rem', color: '#94a3b8', marginTop: '0.5rem' }}>Tip: Create a pages/404/ folder with a page.yml and content to customize this page.</p>
150
162
  </div>
151
163
  )}
152
- </div>
164
+ </>
153
165
  )
154
166
  }
155
167
 
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Default 404 Page Content
3
+ *
4
+ * Single source of truth for the fallback 404 page shown when a site
5
+ * has no custom 404 page defined. Used by:
6
+ * - PageRenderer.jsx (client-side, as React elements)
7
+ * - ssr-renderer.js generate404Html (build-time, as HTML string)
8
+ *
9
+ * The wrapper uses min-height + flex centering so the 404 content
10
+ * renders at the same position regardless of parent layout context.
11
+ * This prevents a visible flash when React hydrates over the SSR content.
12
+ */
13
+
14
+ import React from 'react'
15
+
16
+ const styles = {
17
+ wrapper: {
18
+ minHeight: '80vh',
19
+ display: 'flex',
20
+ flexDirection: 'column',
21
+ alignItems: 'center',
22
+ justifyContent: 'center',
23
+ padding: '2rem',
24
+ textAlign: 'center',
25
+ },
26
+ heading: { fontSize: '3rem', fontWeight: 'bold', color: '#1f2937', marginBottom: '1rem' },
27
+ message: { color: '#64748b', marginBottom: '2rem' },
28
+ link: { color: '#3b82f6', textDecoration: 'underline' },
29
+ }
30
+
31
+ /**
32
+ * React element for client-side rendering (PageRenderer).
33
+ * Reads basePath from the runtime so the homepage link works
34
+ * in subdirectory deployments (e.g., /sites/testproject).
35
+ */
36
+ export function Default404() {
37
+ const basePath = globalThis.uniweb?.activeWebsite?.basePath || ''
38
+ const homeHref = basePath ? `${basePath}/` : '/'
39
+ return React.createElement('div', { className: 'page-not-found', style: styles.wrapper },
40
+ React.createElement('h1', { style: styles.heading }, '404'),
41
+ React.createElement('p', { style: styles.message }, 'Page not found'),
42
+ React.createElement('a', { href: homeHref, style: styles.link }, 'Go to homepage')
43
+ )
44
+ }
45
+
46
+ /**
47
+ * Static HTML string for SSR injection (generate404Html).
48
+ *
49
+ * @param {string} [basePath] - Base path prefix for the homepage link (e.g., '/sites/testproject')
50
+ */
51
+ export function default404Html(basePath = '') {
52
+ const homeHref = basePath ? `${basePath}/` : '/'
53
+ return (
54
+ `<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">` +
55
+ `<h1 style="font-size:3rem;font-weight:bold;color:#1f2937;margin-bottom:1rem">404</h1>` +
56
+ `<p style="color:#64748b;margin-bottom:2rem">Page not found</p>` +
57
+ `<a href="${homeHref}" style="color:#3b82f6;text-decoration:underline">Go to homepage</a>` +
58
+ `</div>`
59
+ )
60
+ }
package/src/setup.js CHANGED
@@ -121,6 +121,8 @@ function createIconResolver(iconConfig = {}) {
121
121
  */
122
122
  export function setupUniweb(configData) {
123
123
  // Create singleton via @uniweb/core (also assigns to globalThis.uniweb)
124
+ // The serving layer is responsible for injecting locale-specific content
125
+ // into __DATA__ — the runtime treats whatever it receives as the active content.
124
126
  const uniwebInstance = createUniweb(configData)
125
127
 
126
128
  // Pre-populate DataStore from build-time fetched data
@@ -184,6 +186,7 @@ export function rebuildWebsite(configData) {
184
186
  const uniweb = globalThis.uniweb
185
187
  const newWebsite = new Website(configData)
186
188
  newWebsite.dataStore.registerFetcher(executeFetchClient)
189
+
187
190
  uniweb.activeWebsite = newWebsite
188
191
  return newWebsite
189
192
  }
@@ -19,6 +19,7 @@ import { renderToString } from 'react-dom/server'
19
19
  import { createUniweb } from '@uniweb/core'
20
20
  import { buildSectionOverrides } from '@uniweb/theming'
21
21
  import { prepareProps, getComponentMeta } from './prepare-props.js'
22
+ import { default404Html } from './default-404.js'
22
23
 
23
24
  // ============================================================================
24
25
  // Layer 1: Rendering functions
@@ -589,3 +590,71 @@ export function injectPageContent(html, renderedContent, page, options = {}) {
589
590
 
590
591
  return result
591
592
  }
593
+
594
+ // ============================================================================
595
+ // 404 fallback generation
596
+ // ============================================================================
597
+
598
+ /**
599
+ * Generate 404.html content for static hosting fallback.
600
+ *
601
+ * Serves two purposes on static hosts (GitHub Pages, Cloudflare Pages, etc.):
602
+ * 1. Real 404: pre-rendered custom 404 page content (or blank #root if none defined)
603
+ * 2. Valid dynamic route (e.g. /blog/2): inline script clears #root so SPA renders fresh
604
+ *
605
+ * Flow: static host serves 404.html → inline script runs before React mounts →
606
+ * - dynamic route: clears #root, React renders the page normally
607
+ * - real 404: leaves #root with pre-rendered content, React re-renders same 404 page
608
+ *
609
+ * @param {Object} options
610
+ * @param {string} options.baseHtml - Assembled HTML shell (with site content already injected)
611
+ * @param {Object} options.website - Initialized Website instance (from initPrerender)
612
+ * @param {Object} options.siteContent - Site content object (to find dynamic templates)
613
+ * @returns {{ html: string, hasNotFoundPage: boolean }}
614
+ */
615
+ export function generate404Html({ baseHtml, website, siteContent }) {
616
+ // Extract patterns for routes that remain as dynamic templates (prerender: false)
617
+ const dynamicTemplates = siteContent.pages?.filter((p) => p.isDynamic) || []
618
+ const routePatterns = dynamicTemplates.map((p) => {
619
+ // '/blog/:id' → /^\/blog\/[^\/]+\/?$/
620
+ const escaped = p.route
621
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
622
+ .replace(/:[^/]+/g, '[^\\/]+')
623
+ return `^${escaped}\\/?$`
624
+ })
625
+
626
+ let html = baseHtml
627
+
628
+ // Pre-render the custom 404 page content into #root (if the site defines one),
629
+ // otherwise inject a default 404 message so the page isn't blank before JS loads
630
+ const notFoundPage = website.getNotFoundPage()
631
+ if (notFoundPage) {
632
+ const notFoundResult = renderPage(notFoundPage, website)
633
+ if (notFoundResult && !notFoundResult.error) {
634
+ html = injectPageContent(html, notFoundResult.renderedContent, notFoundPage, {
635
+ sectionOverrideCSS: notFoundResult.sectionOverrideCSS,
636
+ })
637
+ }
638
+ } else {
639
+ const basePath = website.basePath || ''
640
+ html = html.replace(
641
+ /<div id="root">[\s\S]*?<\/div>/,
642
+ `<div id="root">${default404Html(basePath)}</div>`
643
+ )
644
+ }
645
+
646
+ // Inject inline script: if path matches a dynamic route, clear #root before React mounts
647
+ // so the SPA renders the correct page rather than the 404 content
648
+ if (routePatterns.length > 0) {
649
+ const patternList = routePatterns.map((p) => `/${p}/`).join(',')
650
+ const dynamicScript =
651
+ `<script>(function(){` +
652
+ `var p=[${patternList}],r=window.location.pathname;` +
653
+ `if(p.some(function(x){return x.test(r)})){` +
654
+ `var el=document.getElementById('root');if(el)el.innerHTML='';` +
655
+ `}})()</script>`
656
+ html = html.replace('</body>', `${dynamicScript}\n</body>`)
657
+ }
658
+
659
+ return { html, hasNotFoundPage: !!notFoundPage }
660
+ }
package/src/ssr.js CHANGED
@@ -43,4 +43,7 @@ export {
43
43
  // HTML injection
44
44
  injectPageContent,
45
45
  escapeHtml,
46
+
47
+ // 404 fallback
48
+ generate404Html,
46
49
  } from './ssr-renderer.js'
@@ -1,2 +0,0 @@
1
- import{B as h,P as g,U as p,W as _,c as f,g as d}from"../assets/index-C6TPxGbh.js";const a=[50,100,200,300,400,500,600,700,800,900,950],r=["light","medium","dark"],n={light:{bg:"var(--neutral-50)",text:"var(--neutral-950)",heading:"var(--neutral-900)",link:"var(--primary-600)"},medium:{bg:"var(--neutral-100)",text:"var(--neutral-950)",heading:"var(--neutral-900)",link:"var(--primary-600)"},dark:{bg:"var(--neutral-900)",text:"var(--neutral-50)",heading:"white",link:"var(--primary-400)"}};class i{constructor(t={}){this._data=t,this._palettes=t.palettes||t.colors||{},this._rawColors=t.colors||{},this._contexts=t.contexts||{},this._fonts=t.fonts||{},this._appearance=t.appearance||{default:"light"},this._foundationVars=t.foundationVars||{},this._css=t.css||null,Object.seal(this)}get css(){return this._css}get data(){return this._data}getColor(t,e=500){const s=this._palettes[t];return s&&s[e]||null}getPalette(t){return this._palettes[t]||null}getPaletteNames(){return Object.keys(this._palettes)}hasPalette(t){return t in this._palettes}getColorVar(t,e=500){return`var(--${t}-${e})`}getContextToken(t,e){const s=this._contexts[t];return s&&s[e]?s[e]:n[t]?.[e]||null}getContextTokens(t){const e=n[t]||{},s=this._contexts[t]||{};return{...e,...s}}getContextClass(t){return r.includes(t)?`context-${t}`:(console.warn(`Invalid context: ${t}. Using 'light'.`),"context-light")}isValidContext(t){return r.includes(t)}getValidContexts(){return[...r]}getAppearance(){return{default:this._appearance.default||"light",allowToggle:this._appearance.allowToggle||!1,respectSystemPreference:this._appearance.respectSystemPreference??!0,schemes:this._appearance.schemes||["light"]}}getDefaultScheme(){return this._appearance.default||"light"}supportsScheme(t){return(this._appearance.schemes||["light"]).includes(t)}hasSchemeToggle(){return this._appearance.allowToggle===!0}getSchemeClass(t){return`scheme-${t}`}getFonts(){return{...this._fonts}}getFont(t){return this._fonts[t]||null}getFontVar(t){return`var(--font-${t})`}getFoundationVar(t){const e=this._foundationVars[t];return e?typeof e=="object"?e.default:e:null}getFoundationVars(){const t={};for(const[e,s]of Object.entries(this._foundationVars))t[e]=typeof s=="object"?s.default:s;return t}getFoundationVarRef(t){return`var(--${t})`}getShadeLevels(){return[...a]}hasCustomization(){return Object.keys(this._palettes).length>0||Object.keys(this._contexts).length>0||Object.keys(this._fonts).length>0}toJSON(){return{palettes:this._palettes,colors:this._rawColors,contexts:this._contexts,fonts:this._fonts,appearance:this._appearance,foundationVars:this._foundationVars}}}export{h as Block,g as Page,i as Theme,p as Uniweb,_ as Website,f as createUniweb,d as getUniweb};
2
- //# sourceMappingURL=@uniweb-core.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"@uniweb-core.js","sources":["../../../../core/src/theme.js"],"sourcesContent":["/**\n * Theme Class\n *\n * Runtime representation of a site's theme configuration.\n * Provides access to colors, semantic tokens, and appearance settings.\n *\n * @module @uniweb/core/theme\n */\n\n// Standard shade levels\nconst SHADE_LEVELS = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]\n\n// Valid color contexts\nconst VALID_CONTEXTS = ['light', 'medium', 'dark']\n\n// Default semantic tokens by context\nconst DEFAULT_CONTEXT_TOKENS = {\n light: {\n bg: 'var(--neutral-50)',\n text: 'var(--neutral-950)',\n heading: 'var(--neutral-900)',\n link: 'var(--primary-600)',\n },\n medium: {\n bg: 'var(--neutral-100)',\n text: 'var(--neutral-950)',\n heading: 'var(--neutral-900)',\n link: 'var(--primary-600)',\n },\n dark: {\n bg: 'var(--neutral-900)',\n text: 'var(--neutral-50)',\n heading: 'white',\n link: 'var(--primary-400)',\n },\n}\n\n/**\n * Theme class for runtime theme access\n */\nexport default class Theme {\n /**\n * Create a Theme instance\n *\n * @param {Object} themeData - Processed theme data from build\n * @param {Object} themeData.palettes - Generated color palettes (name → { shade → value })\n * @param {Object} themeData.colors - Raw colors (for reference)\n * @param {Object} themeData.contexts - Context token overrides\n * @param {Object} themeData.fonts - Font configuration\n * @param {Object} themeData.appearance - Appearance settings\n * @param {Object} themeData.foundationVars - Foundation variables\n * @param {string} themeData.css - Pre-generated CSS (optional)\n */\n constructor(themeData = {}) {\n this._data = themeData\n // Use palettes if available, fall back to colors for backwards compatibility\n this._palettes = themeData.palettes || themeData.colors || {}\n this._rawColors = themeData.colors || {}\n this._contexts = themeData.contexts || {}\n this._fonts = themeData.fonts || {}\n this._appearance = themeData.appearance || { default: 'light' }\n this._foundationVars = themeData.foundationVars || {}\n this._css = themeData.css || null\n\n Object.seal(this)\n }\n\n /**\n * Get the pre-generated CSS string\n * @returns {string|null}\n */\n get css() {\n return this._css\n }\n\n /**\n * Get the raw theme data\n * @returns {Object}\n */\n get data() {\n return this._data\n }\n\n // ============================================================\n // Color Access\n // ============================================================\n\n /**\n * Get a color value from a palette\n *\n * @param {string} name - Palette name (e.g., 'primary', 'neutral')\n * @param {number} shade - Shade level (50-950), defaults to 500\n * @returns {string|null} Color value (oklch string) or null if not found\n *\n * @example\n * theme.getColor('primary', 500) // → \"oklch(55.0% 0.2000 260.0)\"\n * theme.getColor('primary') // → same as above (500 is default)\n */\n getColor(name, shade = 500) {\n const palette = this._palettes[name]\n if (!palette) return null\n\n return palette[shade] || null\n }\n\n /**\n * Get all shades for a color palette\n *\n * @param {string} name - Palette name\n * @returns {Object|null} Object with shade levels as keys, or null\n *\n * @example\n * theme.getPalette('primary')\n * // → { 50: \"oklch(...)\", 100: \"oklch(...)\", ... }\n */\n getPalette(name) {\n return this._palettes[name] || null\n }\n\n /**\n * Get all available palette names\n *\n * @returns {string[]} Array of palette names\n */\n getPaletteNames() {\n return Object.keys(this._palettes)\n }\n\n /**\n * Check if a palette exists\n *\n * @param {string} name - Palette name\n * @returns {boolean}\n */\n hasPalette(name) {\n return name in this._palettes\n }\n\n /**\n * Get a CSS variable reference for a color\n *\n * @param {string} name - Palette name\n * @param {number} shade - Shade level\n * @returns {string} CSS var() reference\n *\n * @example\n * theme.getColorVar('primary', 600) // → \"var(--primary-600)\"\n */\n getColorVar(name, shade = 500) {\n return `var(--${name}-${shade})`\n }\n\n // ============================================================\n // Context Access\n // ============================================================\n\n /**\n * Get a semantic token value for a context\n *\n * @param {string} context - Context name ('light', 'medium', 'dark')\n * @param {string} token - Token name (e.g., 'bg', 'text', 'link')\n * @returns {string|null} Token value or null\n *\n * @example\n * theme.getContextToken('light', 'bg') // → \"var(--neutral-50)\"\n * theme.getContextToken('dark', 'text') // → \"var(--neutral-50)\"\n */\n getContextToken(context, token) {\n // Check custom context tokens first\n const customContext = this._contexts[context]\n if (customContext && customContext[token]) {\n return customContext[token]\n }\n\n // Fall back to defaults\n const defaults = DEFAULT_CONTEXT_TOKENS[context]\n return defaults?.[token] || null\n }\n\n /**\n * Get all tokens for a context\n *\n * @param {string} context - Context name\n * @returns {Object} Token name → value mapping\n */\n getContextTokens(context) {\n const defaults = DEFAULT_CONTEXT_TOKENS[context] || {}\n const custom = this._contexts[context] || {}\n return { ...defaults, ...custom }\n }\n\n /**\n * Get the CSS class name for a context\n *\n * @param {string} context - Context name\n * @returns {string} CSS class name\n *\n * @example\n * theme.getContextClass('dark') // → \"context-dark\"\n */\n getContextClass(context) {\n if (!VALID_CONTEXTS.includes(context)) {\n console.warn(`Invalid context: ${context}. Using 'light'.`)\n return 'context-light'\n }\n return `context-${context}`\n }\n\n /**\n * Check if a context is valid\n *\n * @param {string} context - Context name\n * @returns {boolean}\n */\n isValidContext(context) {\n return VALID_CONTEXTS.includes(context)\n }\n\n /**\n * Get all valid context names\n *\n * @returns {string[]}\n */\n getValidContexts() {\n return [...VALID_CONTEXTS]\n }\n\n // ============================================================\n // Appearance (Color Scheme)\n // ============================================================\n\n /**\n * Get appearance configuration\n *\n * @returns {Object} Appearance settings\n * @property {string} default - Default scheme ('light', 'dark', 'system')\n * @property {boolean} allowToggle - Whether scheme toggle is enabled\n * @property {boolean} respectSystemPreference - Honor prefers-color-scheme\n * @property {string[]} schemes - Available schemes\n */\n getAppearance() {\n return {\n default: this._appearance.default || 'light',\n allowToggle: this._appearance.allowToggle || false,\n respectSystemPreference: this._appearance.respectSystemPreference ?? true,\n schemes: this._appearance.schemes || ['light'],\n }\n }\n\n /**\n * Get the default color scheme\n *\n * @returns {string} 'light', 'dark', or 'system'\n */\n getDefaultScheme() {\n return this._appearance.default || 'light'\n }\n\n /**\n * Check if a color scheme is supported\n *\n * @param {string} scheme - Scheme name\n * @returns {boolean}\n */\n supportsScheme(scheme) {\n const schemes = this._appearance.schemes || ['light']\n return schemes.includes(scheme)\n }\n\n /**\n * Check if scheme toggle is enabled\n *\n * @returns {boolean}\n */\n hasSchemeToggle() {\n return this._appearance.allowToggle === true\n }\n\n /**\n * Get the CSS class for a scheme\n *\n * @param {string} scheme - Scheme name\n * @returns {string} CSS class name\n *\n * @example\n * theme.getSchemeClass('dark') // → \"scheme-dark\"\n */\n getSchemeClass(scheme) {\n return `scheme-${scheme}`\n }\n\n // ============================================================\n // Fonts\n // ============================================================\n\n /**\n * Get font configuration\n *\n * @returns {Object} Font settings\n */\n getFonts() {\n return { ...this._fonts }\n }\n\n /**\n * Get a specific font family\n *\n * @param {string} type - Font type ('body', 'heading', 'mono')\n * @returns {string|null} Font family string or null\n */\n getFont(type) {\n return this._fonts[type] || null\n }\n\n /**\n * Get CSS variable reference for a font\n *\n * @param {string} type - Font type\n * @returns {string} CSS var() reference\n */\n getFontVar(type) {\n return `var(--font-${type})`\n }\n\n // ============================================================\n // Foundation Variables\n // ============================================================\n\n /**\n * Get a foundation variable value\n *\n * @param {string} name - Variable name\n * @returns {string|null} Variable value or null\n */\n getFoundationVar(name) {\n const config = this._foundationVars[name]\n if (!config) return null\n return typeof config === 'object' ? config.default : config\n }\n\n /**\n * Get all foundation variables\n *\n * @returns {Object} Variable name → value mapping\n */\n getFoundationVars() {\n const vars = {}\n for (const [name, config] of Object.entries(this._foundationVars)) {\n vars[name] = typeof config === 'object' ? config.default : config\n }\n return vars\n }\n\n /**\n * Get CSS variable reference for a foundation variable\n *\n * @param {string} name - Variable name\n * @returns {string} CSS var() reference\n */\n getFoundationVarRef(name) {\n return `var(--${name})`\n }\n\n // ============================================================\n // Utility Methods\n // ============================================================\n\n /**\n * Get all shade levels\n *\n * @returns {number[]}\n */\n getShadeLevels() {\n return [...SHADE_LEVELS]\n }\n\n /**\n * Check if theme has any custom configuration\n *\n * @returns {boolean}\n */\n hasCustomization() {\n return (\n Object.keys(this._palettes).length > 0 ||\n Object.keys(this._contexts).length > 0 ||\n Object.keys(this._fonts).length > 0\n )\n }\n\n /**\n * Convert theme to a plain object (for serialization)\n *\n * @returns {Object}\n */\n toJSON() {\n return {\n palettes: this._palettes,\n colors: this._rawColors,\n contexts: this._contexts,\n fonts: this._fonts,\n appearance: this._appearance,\n foundationVars: this._foundationVars,\n }\n }\n}\n"],"names":["SHADE_LEVELS","VALID_CONTEXTS","DEFAULT_CONTEXT_TOKENS","Theme","themeData","name","shade","palette","context","token","customContext","defaults","custom","scheme","type","config","vars"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{g as e}from"../assets/_commonjsHelpers-CqkleIqs.js";import{r as o}from"../assets/index-C0udIITE.js";import"../assets/index-kA4PVysc.js";var t=o();const s=e(t),_=t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,c=t.createPortal,d=t.createRoot,u=t.findDOMNode,E=t.flushSync,R=t.hydrate,m=t.hydrateRoot,i=t.render,l=t.unmountComponentAtNode,p=t.unstable_batchedUpdates,N=t.unstable_renderSubtreeIntoContainer,O=t.version;export{_ as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,c as createPortal,d as createRoot,s as default,u as findDOMNode,E as flushSync,R as hydrate,m as hydrateRoot,i as render,l as unmountComponentAtNode,p as unstable_batchedUpdates,N as unstable_renderSubtreeIntoContainer,O as version};
2
- //# sourceMappingURL=react-dom.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"react-dom.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- import{g as s}from"../assets/_commonjsHelpers-CqkleIqs.js";var r={exports:{}},e={};var n;function u(){if(n)return e;n=1;var o=Symbol.for("react.fragment");return e.Fragment=o,e.jsxDEV=void 0,e}var i;function a(){return i||(i=1,r.exports=u()),r.exports}var t=a();const x=s(t),v=t.Fragment,c=t.jsxDEV;export{v as Fragment,x as default,c as jsxDEV};
2
- //# sourceMappingURL=react-jsx-dev-runtime.js.map