@takazudo/zfb-runtime 0.1.0-next.75 → 0.1.0-next.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/dist/client-router/index.d.ts +2 -2
- package/dist/client-router/index.js +2 -1
- package/dist/client-router/index.js.map +1 -1
- package/dist/client-router/prefetch.js +27 -8
- package/dist/client-router/prefetch.js.map +1 -1
- package/dist/client-router/router.d.ts +28 -1
- package/dist/client-router/router.js +158 -11
- package/dist/client-router/router.js.map +1 -1
- package/dist/client-router/swap-functions.js +35 -7
- package/dist/client-router/swap-functions.js.map +1 -1
- package/dist/client-router/types.d.ts +4 -0
- package/dist/client-router/types.js.map +1 -1
- package/dist/client-router.d.ts +18 -1
- package/dist/client-router.js +11 -1
- package/dist/client-router.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +15 -1
- package/dist/router.js +21 -0
- package/dist/router.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swap-functions.js","sourceRoot":"","sources":["../../src/client-router/swap-functions.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference lib=\"dom\" />\n/// <reference lib=\"dom.iterable\" />\n// Ported verbatim from Astro transitions/swap-functions.ts. Mechanical renames\n// applied per W1B §13.4 (data-astro-* → data-zfb-*, dataset.astro* → dataset.zfb*,\n// astro-island element-name branch → data-zfb-island marker per W1B §12.3 island-lifecycle\n// contract). vueScopedStyleId is intentionally KEPT verbatim per W2A §2.8 (the W3B\n// issue body's \"DELETE\" instruction is overridden by W2A's reclassification).\n\nexport type SavedFocus = {\n activeElement: HTMLElement | null;\n start?: number | null;\n end?: number | null;\n};\n\nconst PERSIST_ATTR = \"data-zfb-transition-persist\";\n\nconst NON_OVERRIDABLE_ZFB_ATTRS = [\"data-zfb-transition\", \"data-zfb-transition-fallback\"];\n\n// Consumers extend the preserve-set via <meta name=\"zfb-preserve-html-attrs\"\n// content=\"data-theme data-sidebar-hidden …\">, emitted by\n// <ClientRouter preserveHtmlAttrs={[…]} />. Without it, a runtime <html>\n// attribute set from a persisted island is wiped on every swap. See\n// client-router/port-spec.md deviation #11 and Takazudo/zudo-front-builder#1103.\nconst PRESERVE_ATTRS_META_NAME = \"zfb-preserve-html-attrs\";\n\nconst knownVueScopedStyles = new Map<string, HTMLStyleElement>();\n\nconst scriptsAlreadyRan = new Set<string>();\nexport function detectScriptExecuted(script: HTMLScriptElement) {\n const key = script.src ? new URL(script.src, location.href).href : script.textContent!;\n if (scriptsAlreadyRan.has(key)) return true;\n scriptsAlreadyRan.add(key);\n return false;\n}\n\n/*\n * \tMark new scripts that should not execute\n */\nexport function deselectScripts(doc: Document) {\n for (const s2 of doc.scripts) {\n if (\n // Check if the script should be rerun regardless of it being the same\n !s2.hasAttribute(\"data-zfb-rerun\") &&\n // Check if the script has already been executed\n detectScriptExecuted(s2)\n ) {\n // the old script is in the new document and doesn't have the rerun attribute\n // we mark it as executed to prevent re-execution\n s2.dataset[\"zfbExec\"] = \"\";\n }\n }\n}\n\n/*\n * Read the consumer-configured preserve-list from the live document's\n * <meta name=\"zfb-preserve-html-attrs\"> tag. The list is a site-wide, static\n * contract (the tag is rendered on every page, so it is present on both the\n * current and incoming documents); reading the live document is sufficient.\n * Returns [] when the tag is absent.\n */\nfunction consumerPreservedAttrs(): string[] {\n const content = document\n .querySelector(`meta[name=\"${PRESERVE_ATTRS_META_NAME}\"]`)\n ?.getAttribute(\"content\");\n // Attribute names are ASCII-case-insensitive and the DOM exposes them\n // lowercased, so normalize the list to match (NON_OVERRIDABLE_ZFB_ATTRS is\n // already lowercase). Without this, a mixed-case entry would silently never\n // match the live attribute name.\n return content ? content.toLowerCase().split(/\\s+/).filter(Boolean) : [];\n}\n\n/*\n * swap attributes of the html element\n * delete all attributes from the current document\n * insert all attributes from doc\n * reinsert all original attributes whose name is in the preserve-set:\n * NON_OVERRIDABLE_ZFB_ATTRS (transition-internal) ∪ the consumer preserve-list.\n * Preserved attributes are re-applied last, so a runtime value on the current\n * root wins over the incoming document's default (e.g. a localStorage-driven\n * data-theme). See client-router/port-spec.md deviation #11 / #1103.\n */\nexport function swapRootAttributes(newDoc: Document) {\n const currentRoot = document.documentElement;\n const preserve = new Set([...NON_OVERRIDABLE_ZFB_ATTRS, ...consumerPreservedAttrs()]);\n const preservedAttributes = [...currentRoot.attributes].filter(\n ({ name }) => (currentRoot.removeAttribute(name), preserve.has(name)),\n );\n [...newDoc.documentElement.attributes, ...preservedAttributes].forEach(({ name, value }) =>\n currentRoot.setAttribute(name, value),\n );\n}\n\n/*\n * make the old head look like the new one\n */\nexport function swapHeadElements(doc: Document) {\n for (const el of Array.from(document.head.children)) {\n const newEl = persistedHeadElement(el as HTMLElement, doc);\n // If the element exists in the document already, remove it\n // from the new document and leave the current node alone\n if (newEl) {\n newEl.remove();\n } else {\n if ((import.meta as any).env?.DEV && el instanceof HTMLStyleElement) {\n // In DEV mode, keep updated Vue scoped styles for later reuse\n const viteDevId = vueScopedStyleId(el);\n viteDevId && knownVueScopedStyles.set(viteDevId, el);\n }\n // If the element does not exist in the new document, remove the element from current the head.\n el.remove();\n }\n }\n\n // Everything left in the new head is new, append it all.\n if ((import.meta as any).env?.DEV) {\n // In DEV mode, replace known Vue scoped styles with the versions we remembered\n [...doc.head.children].forEach((child) => {\n document.head.append(knownVueScopedStyles.get((child as any).dataset?.viteDevId) || child);\n });\n } else {\n document.head.append(...doc.head.children);\n }\n}\n\nexport function swapBodyElement(newElement: Element, oldElement: Element) {\n // Lift persist elements to <html> before the body swap so they stay in the DOM\n // throughout replaceWith(). This prevents Safari from losing WebGL context on\n // <canvas> elements due to brief DOM detachment. Uses moveBefore() where available\n // (Chrome 133+) for zero-detachment atomic moves.\n const persistPairs: { old: Element; newTarget: Element }[] = [];\n const docEl = oldElement.ownerDocument.documentElement;\n\n // moveBefore() is not yet in TypeScript's DOM lib, feature-detect and wrap.\n const moveBefore: ((parent: Node, node: Node, child: Node | null) => void) | null =\n typeof (docEl as any).moveBefore === \"function\"\n ? (parent, node, child) => (parent as any).moveBefore(node, child)\n : null;\n\n for (const el of oldElement.querySelectorAll(`[${PERSIST_ATTR}]`)) {\n const id = el.getAttribute(PERSIST_ATTR);\n const newEl = newElement.querySelector(`[${PERSIST_ATTR}=\"${id}\"]`);\n if (!newEl) continue; // no matching target — leave in old body to be discarded\n persistPairs.push({ old: el, newTarget: newEl });\n if (moveBefore) {\n moveBefore(docEl, el, null);\n } else {\n docEl.appendChild(el);\n }\n }\n\n // this will reset scroll Position\n oldElement.replaceWith(newElement);\n\n // Move persist elements into the new body at the position of their targets\n for (const { old: el, newTarget } of persistPairs) {\n if (moveBefore) {\n moveBefore(newTarget.parentNode!, el, newTarget);\n newTarget.remove();\n } else {\n newTarget.replaceWith(el);\n }\n // For islands, copy over the props to allow them to re-render\n if (\n newTarget.matches(\"[data-zfb-island]\") &&\n shouldCopyProps(el as HTMLElement) &&\n !isSameProps(el, newTarget)\n ) {\n el.setAttribute(\"ssr\", \"\");\n // zfb island wrapper writes SSR props to data-props, not props (different attribute, not just renamed).\n const np = newTarget.getAttribute(\"data-props\");\n if (np !== null) el.setAttribute(\"data-props\", np);\n else el.removeAttribute(\"data-props\");\n }\n }\n\n // This will upgrade any Declarative Shadow DOM in the new body.\n attachShadowRoots(newElement);\n}\n\n/**\n * Attach Shadow DOM roots for templates with the declarative `shadowrootmode` attribute.\n * @see https://web.dev/articles/declarative-shadow-dom#polyfill\n * @param root DOM subtree to attach shadow roots within.\n */\nfunction attachShadowRoots(root: Element | ShadowRoot) {\n root.querySelectorAll<HTMLTemplateElement>(\"template[shadowrootmode]\").forEach((template) => {\n const mode = template.getAttribute(\"shadowrootmode\");\n const parent = template.parentNode;\n if ((mode === \"closed\" || mode === \"open\") && parent instanceof HTMLElement) {\n // Skip if shadow root already exists (e.g., from transition-persisted elements)\n if (parent.shadowRoot) {\n template.remove();\n return;\n }\n const shadowRoot = parent.attachShadow({ mode });\n shadowRoot.appendChild(template.content);\n template.remove();\n attachShadowRoots(shadowRoot);\n }\n });\n}\n\nexport const saveFocus = (): (() => void) => {\n const activeElement = document.activeElement as HTMLElement;\n // The element that currently has the focus is part of a DOM tree\n // that will survive the transition to the new document.\n // Save the element and the cursor position\n if (activeElement?.closest(`[${PERSIST_ATTR}]`)) {\n if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {\n const start = activeElement.selectionStart;\n const end = activeElement.selectionEnd;\n return () => restoreFocus({ activeElement, start, end });\n }\n return () => restoreFocus({ activeElement });\n } else {\n return () => restoreFocus({ activeElement: null });\n }\n};\n\nexport const restoreFocus = ({ activeElement, start, end }: SavedFocus) => {\n if (activeElement) {\n activeElement.focus();\n if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {\n if (typeof start === \"number\") activeElement.selectionStart = start;\n if (typeof end === \"number\") activeElement.selectionEnd = end;\n }\n }\n};\n\nexport const vueScopedStyleId = (el: HTMLStyleElement): string => {\n const viteDevId = el.dataset[\"viteDevId\"] || \"\";\n\n const url = new URL(viteDevId, location.href);\n return url.searchParams.get(\"vue\") !== null &&\n url.searchParams.get(\"type\") === \"style\" &&\n url.searchParams.has(\"scoped\")\n ? viteDevId\n : \"\";\n};\n\n// Check for a head element that should persist and returns it,\n// either because it has the data attribute or because replacing it would cause avoidable FOUC.\nconst persistedHeadElement = (el: HTMLElement, newDoc: Document): Element | null => {\n const id = el.getAttribute(PERSIST_ATTR);\n const newEl = id && newDoc.head.querySelector(`[${PERSIST_ATTR}=\"${id}\"]`);\n if (newEl) {\n return newEl;\n }\n if (el.matches(\"link[rel=stylesheet]\")) {\n const href = el.getAttribute(\"href\");\n return newDoc.head.querySelector(`link[rel=stylesheet][href=\"${href}\"]`);\n }\n // In dev mode, Vite injects <style data-vite-dev-id=\"...\"> elements whose\n // textContent may later be transformed (especially Vue's `:deep()` → `[data-v-xxx]`).\n // Match these by their stable dev ID so the already-transformed style is preserved\n // across ClientRouter soft navigations instead of being replaced by the raw version.\n // There are other ids that can't be preserved and need a refresh, like Uno's /__uno.css,\n // which keeps the same id, but with different contents.\n // To avoid enumerating all exceptions, we only apply the auto-persist logic to elements\n // that look like Vue's dev styles.\n if ((import.meta as any).env?.DEV && el instanceof HTMLStyleElement) {\n const viteDevId = vueScopedStyleId(el);\n if (viteDevId) {\n return newDoc.head.querySelector(`style[data-vite-dev-id=\"${viteDevId}\"]`);\n }\n }\n // Preserve inline <style> elements with identical content across navigations.\n // This prevents unnecessary removal and re-insertion of styles (e.g. @font-face\n // declarations from <Font>), which would cause the browser to re-evaluate them\n // and trigger a flash of unstyled text (FOUT).\n if (el.tagName === \"STYLE\" && el.textContent) {\n const styles = newDoc.head.querySelectorAll(\"style\");\n for (const s of styles) {\n if (s.textContent === el.textContent) {\n return s;\n }\n }\n }\n // Preserve font preload links across navigations to avoid re-fetching cached fonts.\n if (el.matches(\"link[rel=preload][as=font]\")) {\n const href = el.getAttribute(\"href\");\n return newDoc.head.querySelector(`link[rel=preload][as=font][href=\"${href}\"]`);\n }\n return null;\n};\n\nconst shouldCopyProps = (el: HTMLElement): boolean => {\n const persistProps = el.dataset[\"zfbTransitionPersistProps\"];\n return persistProps == null || persistProps === \"false\";\n};\n\nconst isSameProps = (oldEl: Element, newEl: Element) => {\n // zfb island wrapper writes SSR props to data-props, not props (different attribute, not just renamed).\n return oldEl.getAttribute(\"data-props\") === newEl.getAttribute(\"data-props\");\n};\n\nexport const swapFunctions = {\n deselectScripts,\n swapRootAttributes,\n swapHeadElements,\n swapBodyElement,\n saveFocus,\n};\n\nexport const swap = (doc: Document) => {\n deselectScripts(doc);\n swapRootAttributes(doc);\n swapHeadElements(doc);\n const restoreFocusFunction = saveFocus();\n swapBodyElement(doc.body, document.body);\n restoreFocusFunction();\n};\n"]}
|
|
1
|
+
{"version":3,"file":"swap-functions.js","sourceRoot":"","sources":["../../src/client-router/swap-functions.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference lib=\"dom\" />\n/// <reference lib=\"dom.iterable\" />\n// Ported verbatim from Astro transitions/swap-functions.ts. Mechanical renames\n// applied per W1B §13.4 (data-astro-* → data-zfb-*, dataset.astro* → dataset.zfb*,\n// astro-island element-name branch → data-zfb-island marker per W1B §12.3 island-lifecycle\n// contract). vueScopedStyleId is intentionally KEPT verbatim per W2A §2.8 (the W3B\n// issue body's \"DELETE\" instruction is overridden by W2A's reclassification).\n\nexport type SavedFocus = {\n activeElement: HTMLElement | null;\n start?: number | null;\n end?: number | null;\n};\n\nconst PERSIST_ATTR = \"data-zfb-transition-persist\";\n\n// Cross-package \"needs-remount\" flag written onto a persisted island whose props\n// changed across the swap. @takazudo/zfb's mountNewIslands consumes it — the\n// island mounted-map lives in that separate package, so a DOM attribute is the\n// only channel the two packages share. Namespaced under data-zfb-* like every\n// other zfb marker so it can't collide with a bare attribute a consumer sets on\n// the same island root. Replaces the opaque `ssr` attribute the original Astro\n// port set here but nothing consumed — zfb islands are marker divs, not\n// <astro-island> custom elements. See client-router/port-spec.md §12.3 / #1389.\nconst ISLAND_REMOUNT_ATTR = \"data-zfb-island-remount\";\n\nconst NON_OVERRIDABLE_ZFB_ATTRS = [\"data-zfb-transition\", \"data-zfb-transition-fallback\"];\n\n// Consumers extend the preserve-set via <meta name=\"zfb-preserve-html-attrs\"\n// content=\"data-theme data-sidebar-hidden …\">, emitted by\n// <ClientRouter preserveHtmlAttrs={[…]} />. Without it, a runtime <html>\n// attribute set from a persisted island is wiped on every swap. See\n// client-router/port-spec.md deviation #11 and Takazudo/zudo-front-builder#1103.\nconst PRESERVE_ATTRS_META_NAME = \"zfb-preserve-html-attrs\";\n\nconst knownVueScopedStyles = new Map<string, HTMLStyleElement>();\n\nconst scriptsAlreadyRan = new Set<string>();\nexport function detectScriptExecuted(script: HTMLScriptElement) {\n const key = script.src ? new URL(script.src, location.href).href : script.textContent!;\n if (scriptsAlreadyRan.has(key)) return true;\n scriptsAlreadyRan.add(key);\n return false;\n}\n\n/*\n * \tMark new scripts that should not execute\n */\nexport function deselectScripts(doc: Document) {\n for (const s2 of doc.scripts) {\n if (\n // Check if the script should be rerun regardless of it being the same\n !s2.hasAttribute(\"data-zfb-rerun\") &&\n // Check if the script has already been executed\n detectScriptExecuted(s2)\n ) {\n // the old script is in the new document and doesn't have the rerun attribute\n // we mark it as executed to prevent re-execution\n s2.dataset[\"zfbExec\"] = \"\";\n }\n }\n}\n\n/*\n * Read the consumer-configured preserve-list from the live document's\n * <meta name=\"zfb-preserve-html-attrs\"> tag. The list is a site-wide, static\n * contract (the tag is rendered on every page, so it is present on both the\n * current and incoming documents); reading the live document is sufficient.\n * Returns [] when the tag is absent.\n */\nfunction consumerPreservedAttrs(): string[] {\n const content = document\n .querySelector(`meta[name=\"${PRESERVE_ATTRS_META_NAME}\"]`)\n ?.getAttribute(\"content\");\n // Attribute names are ASCII-case-insensitive and the DOM exposes them\n // lowercased, so normalize the list to match (NON_OVERRIDABLE_ZFB_ATTRS is\n // already lowercase). Without this, a mixed-case entry would silently never\n // match the live attribute name.\n return content ? content.toLowerCase().split(/\\s+/).filter(Boolean) : [];\n}\n\nconst attrValueSelector = (attr: string, value: string): string =>\n value === \"\" ? `[${attr}=\"\"]` : `[${attr}=${CSS.escape(value)}]`;\n\nconst querySelectorWithAttrValue = (\n root: ParentNode,\n selectorPrefix: string,\n attr: string,\n value: string,\n): Element | null => {\n const escapedMatch = root.querySelector(`${selectorPrefix}${attrValueSelector(attr, value)}`);\n if (escapedMatch) return escapedMatch;\n return (\n Array.from(root.querySelectorAll(`${selectorPrefix}[${attr}]`)).find(\n (el) => el.getAttribute(attr) === value,\n ) ?? null\n );\n};\n\n/*\n * swap attributes of the html element\n * delete all attributes from the current document\n * insert all attributes from doc\n * reinsert all original attributes whose name is in the preserve-set:\n * NON_OVERRIDABLE_ZFB_ATTRS (transition-internal) ∪ the consumer preserve-list.\n * Preserved attributes are re-applied last, so a runtime value on the current\n * root wins over the incoming document's default (e.g. a localStorage-driven\n * data-theme). See client-router/port-spec.md deviation #11 / #1103.\n */\nexport function swapRootAttributes(newDoc: Document) {\n const currentRoot = document.documentElement;\n const preserve = new Set([...NON_OVERRIDABLE_ZFB_ATTRS, ...consumerPreservedAttrs()]);\n const preservedAttributes = [...currentRoot.attributes].filter(\n ({ name }) => (currentRoot.removeAttribute(name), preserve.has(name)),\n );\n [...newDoc.documentElement.attributes, ...preservedAttributes].forEach(({ name, value }) =>\n currentRoot.setAttribute(name, value),\n );\n}\n\n/*\n * make the old head look like the new one\n */\nexport function swapHeadElements(doc: Document) {\n for (const el of Array.from(document.head.children)) {\n const newEl = persistedHeadElement(el as HTMLElement, doc);\n // If the element exists in the document already, remove it\n // from the new document and leave the current node alone\n if (newEl) {\n newEl.remove();\n } else {\n if ((import.meta as any).env?.DEV && el instanceof HTMLStyleElement) {\n // In DEV mode, keep updated Vue scoped styles for later reuse\n const viteDevId = vueScopedStyleId(el);\n viteDevId && knownVueScopedStyles.set(viteDevId, el);\n }\n // If the element does not exist in the new document, remove the element from current the head.\n el.remove();\n }\n }\n\n // Everything left in the new head is new, append it all.\n if ((import.meta as any).env?.DEV) {\n // In DEV mode, replace known Vue scoped styles with the versions we remembered\n [...doc.head.children].forEach((child) => {\n document.head.append(knownVueScopedStyles.get((child as any).dataset?.viteDevId) || child);\n });\n } else {\n document.head.append(...doc.head.children);\n }\n}\n\nexport function swapBodyElement(newElement: Element, oldElement: Element) {\n // Lift persist elements to <html> before the body swap so they stay in the DOM\n // throughout replaceWith(). This prevents Safari from losing WebGL context on\n // <canvas> elements due to brief DOM detachment. Uses moveBefore() where available\n // (Chrome 133+) for zero-detachment atomic moves.\n const persistPairs: { old: Element; newTarget: Element }[] = [];\n const docEl = oldElement.ownerDocument.documentElement;\n\n // moveBefore() is not yet in TypeScript's DOM lib, feature-detect and wrap.\n const moveBefore: ((parent: Node, node: Node, child: Node | null) => void) | null =\n typeof (docEl as any).moveBefore === \"function\"\n ? (parent, node, child) => (parent as any).moveBefore(node, child)\n : null;\n\n for (const el of oldElement.querySelectorAll(`[${PERSIST_ATTR}]`)) {\n const id = el.getAttribute(PERSIST_ATTR);\n const newEl = id !== null ? querySelectorWithAttrValue(newElement, \"\", PERSIST_ATTR, id) : null;\n if (!newEl) continue; // no matching target — leave in old body to be discarded\n persistPairs.push({ old: el, newTarget: newEl });\n if (moveBefore) {\n moveBefore(docEl, el, null);\n } else {\n docEl.appendChild(el);\n }\n }\n\n // this will reset scroll Position\n oldElement.replaceWith(newElement);\n\n // Move persist elements into the new body at the position of their targets\n for (const { old: el, newTarget } of persistPairs) {\n if (moveBefore) {\n moveBefore(newTarget.parentNode!, el, newTarget);\n newTarget.remove();\n } else {\n newTarget.replaceWith(el);\n }\n // Persist-props hybrid path (port-spec §12.3.1 hybrid case / §12.3.2): the\n // persisted island survived the lift with its live component instance, but\n // the incoming page carries different props. Refresh data-props on the\n // surviving node and flag it with ISLAND_REMOUNT_ATTR. That flag is the\n // cross-package \"needs-remount\" signal consumed by @takazudo/zfb's\n // mountNewIslands (clearMountedForRemount): it can't be an in-memory queue\n // because the islands runtime's mounted-map lives in a different package, so\n // the flagged DOM node itself is the queue. mountNewIslands unmounts the\n // stale instance and re-mounts against this node with the new data-props.\n if (\n newTarget.matches(\"[data-zfb-island]\") &&\n shouldCopyProps(el as HTMLElement) &&\n !isSameProps(el, newTarget)\n ) {\n el.setAttribute(ISLAND_REMOUNT_ATTR, \"\");\n // zfb island wrapper writes SSR props to data-props, not props (different attribute, not just renamed).\n const np = newTarget.getAttribute(\"data-props\");\n if (np !== null) el.setAttribute(\"data-props\", np);\n else el.removeAttribute(\"data-props\");\n }\n }\n\n // This will upgrade any Declarative Shadow DOM in the new body.\n attachShadowRoots(newElement);\n}\n\n/**\n * Attach Shadow DOM roots for templates with the declarative `shadowrootmode` attribute.\n * @see https://web.dev/articles/declarative-shadow-dom#polyfill\n * @param root DOM subtree to attach shadow roots within.\n */\nfunction attachShadowRoots(root: Element | ShadowRoot) {\n root.querySelectorAll<HTMLTemplateElement>(\"template[shadowrootmode]\").forEach((template) => {\n const mode = template.getAttribute(\"shadowrootmode\");\n const parent = template.parentNode;\n if ((mode === \"closed\" || mode === \"open\") && parent instanceof HTMLElement) {\n // Skip if shadow root already exists (e.g., from transition-persisted elements)\n if (parent.shadowRoot) {\n template.remove();\n return;\n }\n const shadowRoot = parent.attachShadow({ mode });\n shadowRoot.appendChild(template.content);\n template.remove();\n attachShadowRoots(shadowRoot);\n }\n });\n}\n\nexport const saveFocus = (): (() => void) => {\n const activeElement = document.activeElement as HTMLElement;\n // The element that currently has the focus is part of a DOM tree\n // that will survive the transition to the new document.\n // Save the element and the cursor position\n if (activeElement?.closest(`[${PERSIST_ATTR}]`)) {\n if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {\n const start = activeElement.selectionStart;\n const end = activeElement.selectionEnd;\n return () => restoreFocus({ activeElement, start, end });\n }\n return () => restoreFocus({ activeElement });\n } else {\n return () => restoreFocus({ activeElement: null });\n }\n};\n\nexport const restoreFocus = ({ activeElement, start, end }: SavedFocus) => {\n if (activeElement) {\n activeElement.focus();\n if (activeElement instanceof HTMLInputElement || activeElement instanceof HTMLTextAreaElement) {\n if (typeof start === \"number\") activeElement.selectionStart = start;\n if (typeof end === \"number\") activeElement.selectionEnd = end;\n }\n }\n};\n\nexport const vueScopedStyleId = (el: HTMLStyleElement): string => {\n const viteDevId = el.dataset[\"viteDevId\"] || \"\";\n\n const url = new URL(viteDevId, location.href);\n return url.searchParams.get(\"vue\") !== null &&\n url.searchParams.get(\"type\") === \"style\" &&\n url.searchParams.has(\"scoped\")\n ? viteDevId\n : \"\";\n};\n\n// Check for a head element that should persist and returns it,\n// either because it has the data attribute or because replacing it would cause avoidable FOUC.\nconst persistedHeadElement = (el: HTMLElement, newDoc: Document): Element | null => {\n const id = el.getAttribute(PERSIST_ATTR);\n const newEl = id !== null ? querySelectorWithAttrValue(newDoc.head, \"\", PERSIST_ATTR, id) : null;\n if (newEl) {\n return newEl;\n }\n if (el.matches(\"link[rel=stylesheet]\")) {\n const href = el.getAttribute(\"href\");\n return href !== null\n ? querySelectorWithAttrValue(newDoc.head, \"link[rel=stylesheet]\", \"href\", href)\n : null;\n }\n // In dev mode, Vite injects <style data-vite-dev-id=\"...\"> elements whose\n // textContent may later be transformed (especially Vue's `:deep()` → `[data-v-xxx]`).\n // Match these by their stable dev ID so the already-transformed style is preserved\n // across ClientRouter soft navigations instead of being replaced by the raw version.\n // There are other ids that can't be preserved and need a refresh, like Uno's /__uno.css,\n // which keeps the same id, but with different contents.\n // To avoid enumerating all exceptions, we only apply the auto-persist logic to elements\n // that look like Vue's dev styles.\n if ((import.meta as any).env?.DEV && el instanceof HTMLStyleElement) {\n const viteDevId = vueScopedStyleId(el);\n if (viteDevId) {\n return querySelectorWithAttrValue(newDoc.head, \"style\", \"data-vite-dev-id\", viteDevId);\n }\n }\n // Preserve inline <style> elements with identical content across navigations.\n // This prevents unnecessary removal and re-insertion of styles (e.g. @font-face\n // declarations from <Font>), which would cause the browser to re-evaluate them\n // and trigger a flash of unstyled text (FOUT).\n if (el.tagName === \"STYLE\" && el.textContent) {\n const styles = newDoc.head.querySelectorAll(\"style\");\n for (const s of styles) {\n if (s.textContent === el.textContent) {\n return s;\n }\n }\n }\n // Preserve font preload links across navigations to avoid re-fetching cached fonts.\n if (el.matches(\"link[rel=preload][as=font]\")) {\n const href = el.getAttribute(\"href\");\n return href !== null\n ? querySelectorWithAttrValue(newDoc.head, \"link[rel=preload][as=font]\", \"href\", href)\n : null;\n }\n return null;\n};\n\nconst shouldCopyProps = (el: HTMLElement): boolean => {\n const persistProps = el.dataset[\"zfbTransitionPersistProps\"];\n return persistProps == null || persistProps === \"false\";\n};\n\nconst isSameProps = (oldEl: Element, newEl: Element) => {\n // zfb island wrapper writes SSR props to data-props, not props (different attribute, not just renamed).\n return oldEl.getAttribute(\"data-props\") === newEl.getAttribute(\"data-props\");\n};\n\nexport const swapFunctions = {\n deselectScripts,\n swapRootAttributes,\n swapHeadElements,\n swapBodyElement,\n saveFocus,\n};\n\nexport const swap = (doc: Document) => {\n deselectScripts(doc);\n swapRootAttributes(doc);\n swapHeadElements(doc);\n const restoreFocusFunction = saveFocus();\n swapBodyElement(doc.body, document.body);\n restoreFocusFunction();\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/client-router/types.ts"],"names":[],"mappings":"AAAA,2BAA2B","sourcesContent":["/// <reference lib=\"dom\" />\nexport type Fallback = \"none\" | \"animate\" | \"swap\";\nexport type Direction = \"forward\" | \"back\";\nexport type NavigationTypeString = \"push\" | \"replace\" | \"traverse\";\nexport type Options = {\n history?: \"auto\" | \"push\" | \"replace\";\n info?: any;\n state?: any;\n formData?: FormData;\n sourceElement?: Element; // more than HTMLElement, e.g. SVGAElement\n};\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/client-router/types.ts"],"names":[],"mappings":"AAAA,2BAA2B","sourcesContent":["/// <reference lib=\"dom\" />\nexport type Fallback = \"none\" | \"animate\" | \"swap\";\nexport type Direction = \"forward\" | \"back\";\nexport type NavigationTypeString = \"push\" | \"replace\" | \"traverse\";\nexport type Options = {\n history?: \"auto\" | \"push\" | \"replace\";\n info?: any;\n state?: any;\n formData?: FormData;\n sourceElement?: Element; // more than HTMLElement, e.g. SVGAElement\n};\n\n// Public options for syncHistoryEntry() (#1377).\nexport type SyncHistoryEntryOptions = {\n // Replace the current history entry instead of pushing a new one.\n replace?: boolean;\n // Consumer state merged into the entry; the router's bookkeeping keys\n // (index/scrollX/scrollY) always take precedence over colliding keys.\n state?: any;\n};\n"]}
|
package/dist/client-router.d.ts
CHANGED
|
@@ -26,6 +26,23 @@ export interface ClientRouterProps {
|
|
|
26
26
|
* @see https://github.com/Takazudo/zudo-front-builder/issues/1103
|
|
27
27
|
*/
|
|
28
28
|
preserveHtmlAttrs?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* Opt this page OUT of the same-page traverse fast-path (default `false` —
|
|
31
|
+
* fast-path ON). By default a Back/Forward traversal between two history
|
|
32
|
+
* entries sharing the same `pathname + search` is served instantly from the
|
|
33
|
+
* live DOM — no re-fetch, no re-swap, so island/client state is preserved.
|
|
34
|
+
*
|
|
35
|
+
* Set this on a **per-request SSR page** (`prerender = false`) whose
|
|
36
|
+
* server-rendered content can legitimately differ between two visits to the
|
|
37
|
+
* same URL: with the fast-path skipped such a traverse would pin the stale
|
|
38
|
+
* first-render content. Emitted as
|
|
39
|
+
* `<meta name="zfb-traverse-refetch" content="true">`, which the router reads
|
|
40
|
+
* on the current (target) page to force the fetch back on. Mount with the
|
|
41
|
+
* **same value on every page** that participates in SPA navigation, mirroring
|
|
42
|
+
* the `preserveHtmlAttrs` guidance.
|
|
43
|
+
* @see https://github.com/Takazudo/zudo-front-builder/issues/1376
|
|
44
|
+
*/
|
|
45
|
+
traverseRefetch?: boolean;
|
|
29
46
|
}
|
|
30
47
|
/**
|
|
31
48
|
* Public element shape for each node returned by `<ClientRouter />`.
|
|
@@ -52,4 +69,4 @@ export type ClientRouterElement = {
|
|
|
52
69
|
* <ClientRouter fallback="animate" />
|
|
53
70
|
* ```
|
|
54
71
|
*/
|
|
55
|
-
export declare function ClientRouter({ fallback, prefetchAll: prefetchAllProp, preserveHtmlAttrs, }?: ClientRouterProps): readonly ClientRouterElement[];
|
|
72
|
+
export declare function ClientRouter({ fallback, prefetchAll: prefetchAllProp, preserveHtmlAttrs, traverseRefetch, }?: ClientRouterProps): readonly ClientRouterElement[];
|
package/dist/client-router.js
CHANGED
|
@@ -98,7 +98,7 @@ const announcerCss = `
|
|
|
98
98
|
* <ClientRouter fallback="animate" />
|
|
99
99
|
* ```
|
|
100
100
|
*/
|
|
101
|
-
export function ClientRouter({ fallback = "animate", prefetchAll: prefetchAllProp = false, preserveHtmlAttrs = [], } = {}) {
|
|
101
|
+
export function ClientRouter({ fallback = "animate", prefetchAll: prefetchAllProp = false, preserveHtmlAttrs = [], traverseRefetch = false, } = {}) {
|
|
102
102
|
// Bootstrap prefetch exactly once on the client when prefetchAll is true.
|
|
103
103
|
// The initialized flag inside prefetchInit() provides a second safety layer
|
|
104
104
|
// in case of concurrent hydration or manual callers (#276).
|
|
@@ -138,6 +138,16 @@ export function ClientRouter({ fallback = "animate", prefetchAll: prefetchAllPro
|
|
|
138
138
|
if (preserveList.length > 0) {
|
|
139
139
|
nodes.push(makeVNode("meta", { name: "zfb-preserve-html-attrs", content: preserveList.join(" ") }, "zfb-preserve-html-attrs"));
|
|
140
140
|
}
|
|
141
|
+
// Traverse-refetch opt-out meta (#1376). A same-page Back/Forward traversal
|
|
142
|
+
// skips the re-fetch/re-swap by default (the router's traverse fast-path);
|
|
143
|
+
// this meta opts a per-request SSR page (`prerender = false`) back INTO the
|
|
144
|
+
// fetch, since skipping it would pin stale server-rendered content. Emitted
|
|
145
|
+
// only when opted in, so non-opt-in output stays byte-identical. Same
|
|
146
|
+
// conditional-emission shape as the prefetch-disabled / preserve-html-attrs
|
|
147
|
+
// metas; the router reads meta[name="zfb-traverse-refetch"][content="true"].
|
|
148
|
+
if (traverseRefetch) {
|
|
149
|
+
nodes.push(makeVNode("meta", { name: "zfb-traverse-refetch", content: "true" }, "zfb-traverse-refetch"));
|
|
150
|
+
}
|
|
141
151
|
return nodes;
|
|
142
152
|
}
|
|
143
153
|
//# sourceMappingURL=client-router.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-router.js","sourceRoot":"","sources":["../src/client-router.ts"],"names":[],"mappings":"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","sourcesContent":["// `@takazudo/zfb-runtime` — `<ClientRouter />` component.\n//\n// Ported from Astro's `ClientRouter.astro` (155 lines).\n// Source: packages/astro/components/ClientRouter.astro\n// Issue: zudolab/zudo-doc#1519 (W3D), parent epic zudolab/zudo-doc#1510.\n//\n// Named-cause deviation — inline script split (W1B §13.6):\n// Astro's <script> block is processed by Vite at build time as a bundled\n// module with virtual-module imports (`astro:transitions/client`). The zfb\n// port emits meta tags + global styles from the JSX component, while the\n// click/form intercepts live in `client-router/router.ts` and are registered\n// via `init()`. The component calls `init()` as a side effect on first import.\n// No inline <script> dangerouslySetInnerHTML is emitted.\n//\n// Framework-agnostic element minting (no JSX syntax):\n// `@takazudo/zfb-runtime` does not depend on a framework runtime. Head nodes\n// are minted by calling `jsx` from `react/jsx-runtime` directly — NOT JSX\n// syntax, so this stays a plain `.ts` file with no tsconfig JSX changes. The\n// engine alias-rewrites `react/jsx-runtime` → `preact/jsx-runtime` in Preact\n// mode (bundler.rs ~2886) and resolves it natively in React mode, so the same\n// call mints a real element for whichever framework the project configured.\n// The previous approach (a hand-rolled `{ type, props, key, constructor:\n// undefined }` object literal — the Preact diff-path sentinel) only worked for\n// Preact: React's renderer rejects such an object as a child with React error\n// #31 (\"Objects are not valid as a React child\"), because a real React element\n// carries `$$typeof: Symbol.for(\"react.element\")` a literal cannot fake. Same\n// migration as `Island` in @takazudo/zfb.\n//\n// The component renders three base sibling elements to <head> (plus optional\n// metas emitted conditionally — see `prefetchDisabled` and `preserveHtmlAttrs`):\n// 1. A <style> tag with the `.zfb-route-announcer` ARIA helper class.\n// 2. <meta name=\"zfb-view-transitions-enabled\" content=\"true\" />\n// 3. <meta name=\"zfb-view-transitions-fallback\" content={fallback} />\n//\n// The route-announcer <div> is injected into <body> by `announce()` in\n// `client-router/router.ts` on every navigation.\n\nimport { jsx } from \"react/jsx-runtime\";\n\nimport { init } from \"./client-router/router.js\";\nimport { init as prefetchInit } from \"./client-router/prefetch.js\";\n\n// Side-effect: wire click + submit intercepts on first import of this component.\n// Guarded by the idempotent `initialized` flag in router.ts — safe for multiple\n// <ClientRouter /> mounts and HMR re-runs. (W3C3 init idempotency.)\nif (typeof document !== \"undefined\") {\n init();\n}\n\n// Module-level guard: prefetch bootstrap is triggered at most once per module\n// lifetime even if <ClientRouter prefetchAll /> is mounted multiple times (#276).\nlet prefetchBootstrapped = false;\n\nexport interface ClientRouterProps {\n /** Fallback animation strategy when native View Transitions are not supported. */\n fallback?: \"none\" | \"animate\" | \"swap\";\n /**\n * When true, opts every same-origin link into the default prefetch strategy\n * (hover) by calling prefetchInit({ prefetchAll: true }) once on the client.\n */\n prefetchAll?: boolean;\n /**\n * Extra `<html>` attribute names to preserve across SPA swaps. By default the\n * client router copies the incoming server-rendered document's `<html>`\n * attributes onto the live root, dropping any current attribute that isn't\n * internal to the transition machinery — so a *runtime* attribute a consumer\n * sets from a persisted island (e.g. `data-theme` or `data-sidebar-hidden`\n * driven from `localStorage`) is lost on every navigation. List those names\n * here and the router re-applies their current value after each swap. Emitted\n * as a `<meta name=\"zfb-preserve-html-attrs\">` tag that `swapRootAttributes`\n * reads at swap time.\n *\n * Mount with the **same list on every page** that participates in SPA\n * navigation: the preserve-list is read from the *current* (outgoing) page's\n * meta at swap time, so a page that omits an entry drops that attribute when\n * navigating away from it. Names are matched case-insensitively (DOM attribute\n * names are lowercased). For dynamic/computed cases, mutate\n * `event.newDocument.documentElement` in a `zfb:before-swap` listener instead.\n * @see https://github.com/Takazudo/zudo-front-builder/issues/1103\n */\n preserveHtmlAttrs?: string[];\n}\n\n/**\n * Public element shape for each node returned by `<ClientRouter />`.\n * Structural type — intentionally matches the Preact/React VNode object shape\n * so consumers do not type-infer through the internal representation.\n */\nexport type ClientRouterElement = {\n readonly type: string;\n readonly props: Readonly<Record<string, unknown>>;\n readonly key: unknown;\n};\n\n/**\n * Mint a head element through the per-project JSX runtime.\n *\n * Calls `jsx` from `react/jsx-runtime` (alias-rewritten to\n * `preact/jsx-runtime` in Preact mode by the engine, native in React mode)\n * so the returned value is a real element for whichever framework the\n * project configured — NOT a hand-rolled `{ type, props, key }` literal,\n * which only Preact accepts and which makes React throw error #31. A stable\n * `key` is passed because `ClientRouter()` returns these nodes in a plain\n * array (React warns about keyless list children otherwise). Mirrors the\n * `Island` migration in `@takazudo/zfb`.\n */\nfunction makeVNode(type: string, props: Record<string, unknown>, key: string): ClientRouterElement {\n // `jsx`'s `type` param is typed `ElementType` (string-literal intrinsic\n // tags or component types), which rejects an arbitrary runtime `string`.\n // The tag is dynamic here, so cast to the factory's own first-param type —\n // robust whether the engine aliases `jsx` to react or preact at build time.\n return jsx(type as Parameters<typeof jsx>[0], props, key) as unknown as ClientRouterElement;\n}\n\n// CSS for the route-announcer element. Ported verbatim from Astro's\n// `<style is:global>` block in ClientRouter.astro (lines 11–23), renaming\n// `.astro-route-announcer` → `.zfb-route-announcer` per W1B §5.\n// This is a global (non-scoped) <style> because the announcer <div> is\n// appended to document.body at runtime, outside any Preact-controlled subtree.\nconst announcerCss = `\n.zfb-route-announcer {\n\tposition: absolute;\n\tleft: 0;\n\ttop: 0;\n\tclip: rect(0 0 0 0);\n\tclip-path: inset(50%);\n\toverflow: hidden;\n\twhite-space: nowrap;\n\twidth: 1px;\n\theight: 1px;\n}\n`;\n\n/**\n * `<ClientRouter />` — SPA soft-swap navigation with View Transition animations.\n *\n * Mount once in your page `<head>`. Emits the opt-in meta tags and the global\n * `.zfb-route-announcer` stylesheet that the route-announcer ARIA div needs.\n * Click and form-submit intercepts are registered as a side effect of importing\n * this component (idempotent — safe to mount multiple times).\n *\n * @example\n * ```tsx\n * import { ClientRouter } from \"@takazudo/zfb-runtime\";\n * // In your page <head>:\n * <ClientRouter fallback=\"animate\" />\n * ```\n */\nexport function ClientRouter({\n fallback = \"animate\",\n prefetchAll: prefetchAllProp = false,\n preserveHtmlAttrs = [],\n}: ClientRouterProps = {}): readonly ClientRouterElement[] {\n // Bootstrap prefetch exactly once on the client when prefetchAll is true.\n // The initialized flag inside prefetchInit() provides a second safety layer\n // in case of concurrent hydration or manual callers (#276).\n if (typeof document !== \"undefined\" && prefetchAllProp && !prefetchBootstrapped) {\n prefetchBootstrapped = true;\n prefetchInit({ prefetchAll: true });\n }\n\n const nodes: ClientRouterElement[] = [\n // Global styles for the ARIA route-announcer div injected into <body>.\n makeVNode(\"style\", { dangerouslySetInnerHTML: { __html: announcerCss } }, \"zfb-vt-style\"),\n // Opt-in meta tag: router checks for this to decide whether to intercept navigations.\n makeVNode(\"meta\", { name: \"zfb-view-transitions-enabled\", content: \"true\" }, \"zfb-vt-enabled\"),\n // Fallback strategy meta tag: read by getFallback() in router.ts.\n makeVNode(\n \"meta\",\n { name: \"zfb-view-transitions-fallback\", content: fallback },\n \"zfb-vt-fallback\",\n ),\n ];\n\n // Prefetch-disabled meta tag (#277): emitted when the bundler set\n // `globalThis.__zfb.prefetchDisabled = true` (from `zfb.config.ts`\n // `prefetch: { disabled: true }`). The sibling prefetch-core module reads\n // `document.querySelector('meta[name=\"zfb-prefetch-disabled\"][content=\"true\"]')`\n // at `init()` time and short-circuits if found.\n //\n // The flag is site-wide and static — set once at bundle-emit time, never\n // per-page. This meta tag appears on every page that mounts `<ClientRouter />`\n // or not at all.\n //\n // Pin the contract verbatim — the attribute names and content value are\n // shared with the sibling prefetch-core sub-issue (#276).\n if ((globalThis as { __zfb?: { prefetchDisabled?: boolean } }).__zfb?.prefetchDisabled === true) {\n nodes.push(\n makeVNode(\n \"meta\",\n { name: \"zfb-prefetch-disabled\", content: \"true\" },\n \"zfb-prefetch-disabled\",\n ),\n );\n }\n\n // Consumer-extensible <html> attribute preserve-list (#1103). swapRootAttributes\n // reads this meta and re-applies the listed attributes' runtime values after each\n // swap, so persisted-island state on <html> (data-theme, data-sidebar-hidden, …)\n // survives navigation. Emitted only when non-empty, so non-opt-in output stays\n // byte-identical. Same conditional-emission shape as the prefetch-disabled meta.\n const preserveList = preserveHtmlAttrs.filter(Boolean);\n if (preserveList.length > 0) {\n nodes.push(\n makeVNode(\n \"meta\",\n { name: \"zfb-preserve-html-attrs\", content: preserveList.join(\" \") },\n \"zfb-preserve-html-attrs\",\n ),\n );\n }\n\n return nodes;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"client-router.js","sourceRoot":"","sources":["../src/client-router.ts"],"names":[],"mappings":"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","sourcesContent":["// `@takazudo/zfb-runtime` — `<ClientRouter />` component.\n//\n// Ported from Astro's `ClientRouter.astro` (155 lines).\n// Source: packages/astro/components/ClientRouter.astro\n// Issue: zudolab/zudo-doc#1519 (W3D), parent epic zudolab/zudo-doc#1510.\n//\n// Named-cause deviation — inline script split (W1B §13.6):\n// Astro's <script> block is processed by Vite at build time as a bundled\n// module with virtual-module imports (`astro:transitions/client`). The zfb\n// port emits meta tags + global styles from the JSX component, while the\n// click/form intercepts live in `client-router/router.ts` and are registered\n// via `init()`. The component calls `init()` as a side effect on first import.\n// No inline <script> dangerouslySetInnerHTML is emitted.\n//\n// Framework-agnostic element minting (no JSX syntax):\n// `@takazudo/zfb-runtime` does not depend on a framework runtime. Head nodes\n// are minted by calling `jsx` from `react/jsx-runtime` directly — NOT JSX\n// syntax, so this stays a plain `.ts` file with no tsconfig JSX changes. The\n// engine alias-rewrites `react/jsx-runtime` → `preact/jsx-runtime` in Preact\n// mode (bundler.rs ~2886) and resolves it natively in React mode, so the same\n// call mints a real element for whichever framework the project configured.\n// The previous approach (a hand-rolled `{ type, props, key, constructor:\n// undefined }` object literal — the Preact diff-path sentinel) only worked for\n// Preact: React's renderer rejects such an object as a child with React error\n// #31 (\"Objects are not valid as a React child\"), because a real React element\n// carries `$$typeof: Symbol.for(\"react.element\")` a literal cannot fake. Same\n// migration as `Island` in @takazudo/zfb.\n//\n// The component renders three base sibling elements to <head> (plus optional\n// metas emitted conditionally — see `prefetchDisabled` and `preserveHtmlAttrs`):\n// 1. A <style> tag with the `.zfb-route-announcer` ARIA helper class.\n// 2. <meta name=\"zfb-view-transitions-enabled\" content=\"true\" />\n// 3. <meta name=\"zfb-view-transitions-fallback\" content={fallback} />\n//\n// The route-announcer <div> is injected into <body> by `announce()` in\n// `client-router/router.ts` on every navigation.\n\nimport { jsx } from \"react/jsx-runtime\";\n\nimport { init } from \"./client-router/router.js\";\nimport { init as prefetchInit } from \"./client-router/prefetch.js\";\n\n// Side-effect: wire click + submit intercepts on first import of this component.\n// Guarded by the idempotent `initialized` flag in router.ts — safe for multiple\n// <ClientRouter /> mounts and HMR re-runs. (W3C3 init idempotency.)\nif (typeof document !== \"undefined\") {\n init();\n}\n\n// Module-level guard: prefetch bootstrap is triggered at most once per module\n// lifetime even if <ClientRouter prefetchAll /> is mounted multiple times (#276).\nlet prefetchBootstrapped = false;\n\nexport interface ClientRouterProps {\n /** Fallback animation strategy when native View Transitions are not supported. */\n fallback?: \"none\" | \"animate\" | \"swap\";\n /**\n * When true, opts every same-origin link into the default prefetch strategy\n * (hover) by calling prefetchInit({ prefetchAll: true }) once on the client.\n */\n prefetchAll?: boolean;\n /**\n * Extra `<html>` attribute names to preserve across SPA swaps. By default the\n * client router copies the incoming server-rendered document's `<html>`\n * attributes onto the live root, dropping any current attribute that isn't\n * internal to the transition machinery — so a *runtime* attribute a consumer\n * sets from a persisted island (e.g. `data-theme` or `data-sidebar-hidden`\n * driven from `localStorage`) is lost on every navigation. List those names\n * here and the router re-applies their current value after each swap. Emitted\n * as a `<meta name=\"zfb-preserve-html-attrs\">` tag that `swapRootAttributes`\n * reads at swap time.\n *\n * Mount with the **same list on every page** that participates in SPA\n * navigation: the preserve-list is read from the *current* (outgoing) page's\n * meta at swap time, so a page that omits an entry drops that attribute when\n * navigating away from it. Names are matched case-insensitively (DOM attribute\n * names are lowercased). For dynamic/computed cases, mutate\n * `event.newDocument.documentElement` in a `zfb:before-swap` listener instead.\n * @see https://github.com/Takazudo/zudo-front-builder/issues/1103\n */\n preserveHtmlAttrs?: string[];\n /**\n * Opt this page OUT of the same-page traverse fast-path (default `false` —\n * fast-path ON). By default a Back/Forward traversal between two history\n * entries sharing the same `pathname + search` is served instantly from the\n * live DOM — no re-fetch, no re-swap, so island/client state is preserved.\n *\n * Set this on a **per-request SSR page** (`prerender = false`) whose\n * server-rendered content can legitimately differ between two visits to the\n * same URL: with the fast-path skipped such a traverse would pin the stale\n * first-render content. Emitted as\n * `<meta name=\"zfb-traverse-refetch\" content=\"true\">`, which the router reads\n * on the current (target) page to force the fetch back on. Mount with the\n * **same value on every page** that participates in SPA navigation, mirroring\n * the `preserveHtmlAttrs` guidance.\n * @see https://github.com/Takazudo/zudo-front-builder/issues/1376\n */\n traverseRefetch?: boolean;\n}\n\n/**\n * Public element shape for each node returned by `<ClientRouter />`.\n * Structural type — intentionally matches the Preact/React VNode object shape\n * so consumers do not type-infer through the internal representation.\n */\nexport type ClientRouterElement = {\n readonly type: string;\n readonly props: Readonly<Record<string, unknown>>;\n readonly key: unknown;\n};\n\n/**\n * Mint a head element through the per-project JSX runtime.\n *\n * Calls `jsx` from `react/jsx-runtime` (alias-rewritten to\n * `preact/jsx-runtime` in Preact mode by the engine, native in React mode)\n * so the returned value is a real element for whichever framework the\n * project configured — NOT a hand-rolled `{ type, props, key }` literal,\n * which only Preact accepts and which makes React throw error #31. A stable\n * `key` is passed because `ClientRouter()` returns these nodes in a plain\n * array (React warns about keyless list children otherwise). Mirrors the\n * `Island` migration in `@takazudo/zfb`.\n */\nfunction makeVNode(type: string, props: Record<string, unknown>, key: string): ClientRouterElement {\n // `jsx`'s `type` param is typed `ElementType` (string-literal intrinsic\n // tags or component types), which rejects an arbitrary runtime `string`.\n // The tag is dynamic here, so cast to the factory's own first-param type —\n // robust whether the engine aliases `jsx` to react or preact at build time.\n return jsx(type as Parameters<typeof jsx>[0], props, key) as unknown as ClientRouterElement;\n}\n\n// CSS for the route-announcer element. Ported verbatim from Astro's\n// `<style is:global>` block in ClientRouter.astro (lines 11–23), renaming\n// `.astro-route-announcer` → `.zfb-route-announcer` per W1B §5.\n// This is a global (non-scoped) <style> because the announcer <div> is\n// appended to document.body at runtime, outside any Preact-controlled subtree.\nconst announcerCss = `\n.zfb-route-announcer {\n\tposition: absolute;\n\tleft: 0;\n\ttop: 0;\n\tclip: rect(0 0 0 0);\n\tclip-path: inset(50%);\n\toverflow: hidden;\n\twhite-space: nowrap;\n\twidth: 1px;\n\theight: 1px;\n}\n`;\n\n/**\n * `<ClientRouter />` — SPA soft-swap navigation with View Transition animations.\n *\n * Mount once in your page `<head>`. Emits the opt-in meta tags and the global\n * `.zfb-route-announcer` stylesheet that the route-announcer ARIA div needs.\n * Click and form-submit intercepts are registered as a side effect of importing\n * this component (idempotent — safe to mount multiple times).\n *\n * @example\n * ```tsx\n * import { ClientRouter } from \"@takazudo/zfb-runtime\";\n * // In your page <head>:\n * <ClientRouter fallback=\"animate\" />\n * ```\n */\nexport function ClientRouter({\n fallback = \"animate\",\n prefetchAll: prefetchAllProp = false,\n preserveHtmlAttrs = [],\n traverseRefetch = false,\n}: ClientRouterProps = {}): readonly ClientRouterElement[] {\n // Bootstrap prefetch exactly once on the client when prefetchAll is true.\n // The initialized flag inside prefetchInit() provides a second safety layer\n // in case of concurrent hydration or manual callers (#276).\n if (typeof document !== \"undefined\" && prefetchAllProp && !prefetchBootstrapped) {\n prefetchBootstrapped = true;\n prefetchInit({ prefetchAll: true });\n }\n\n const nodes: ClientRouterElement[] = [\n // Global styles for the ARIA route-announcer div injected into <body>.\n makeVNode(\"style\", { dangerouslySetInnerHTML: { __html: announcerCss } }, \"zfb-vt-style\"),\n // Opt-in meta tag: router checks for this to decide whether to intercept navigations.\n makeVNode(\"meta\", { name: \"zfb-view-transitions-enabled\", content: \"true\" }, \"zfb-vt-enabled\"),\n // Fallback strategy meta tag: read by getFallback() in router.ts.\n makeVNode(\n \"meta\",\n { name: \"zfb-view-transitions-fallback\", content: fallback },\n \"zfb-vt-fallback\",\n ),\n ];\n\n // Prefetch-disabled meta tag (#277): emitted when the bundler set\n // `globalThis.__zfb.prefetchDisabled = true` (from `zfb.config.ts`\n // `prefetch: { disabled: true }`). The sibling prefetch-core module reads\n // `document.querySelector('meta[name=\"zfb-prefetch-disabled\"][content=\"true\"]')`\n // at `init()` time and short-circuits if found.\n //\n // The flag is site-wide and static — set once at bundle-emit time, never\n // per-page. This meta tag appears on every page that mounts `<ClientRouter />`\n // or not at all.\n //\n // Pin the contract verbatim — the attribute names and content value are\n // shared with the sibling prefetch-core sub-issue (#276).\n if ((globalThis as { __zfb?: { prefetchDisabled?: boolean } }).__zfb?.prefetchDisabled === true) {\n nodes.push(\n makeVNode(\n \"meta\",\n { name: \"zfb-prefetch-disabled\", content: \"true\" },\n \"zfb-prefetch-disabled\",\n ),\n );\n }\n\n // Consumer-extensible <html> attribute preserve-list (#1103). swapRootAttributes\n // reads this meta and re-applies the listed attributes' runtime values after each\n // swap, so persisted-island state on <html> (data-theme, data-sidebar-hidden, …)\n // survives navigation. Emitted only when non-empty, so non-opt-in output stays\n // byte-identical. Same conditional-emission shape as the prefetch-disabled meta.\n const preserveList = preserveHtmlAttrs.filter(Boolean);\n if (preserveList.length > 0) {\n nodes.push(\n makeVNode(\n \"meta\",\n { name: \"zfb-preserve-html-attrs\", content: preserveList.join(\" \") },\n \"zfb-preserve-html-attrs\",\n ),\n );\n }\n\n // Traverse-refetch opt-out meta (#1376). A same-page Back/Forward traversal\n // skips the re-fetch/re-swap by default (the router's traverse fast-path);\n // this meta opts a per-request SSR page (`prerender = false`) back INTO the\n // fetch, since skipping it would pin stale server-rendered content. Emitted\n // only when opted in, so non-opt-in output stays byte-identical. Same\n // conditional-emission shape as the prefetch-disabled / preserve-html-attrs\n // metas; the router reads meta[name=\"zfb-traverse-refetch\"][content=\"true\"].\n if (traverseRefetch) {\n nodes.push(\n makeVNode(\"meta\", { name: \"zfb-traverse-refetch\", content: \"true\" }, \"zfb-traverse-refetch\"),\n );\n }\n\n return nodes;\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,10 +3,10 @@ export type { FrameworkAdapter } from "./framework.js";
|
|
|
3
3
|
export type { ContentSnapshot, EntrySnapshot } from "./snapshot.js";
|
|
4
4
|
export { ViewTransitions, type ViewTransitionsElement } from "./view-transitions.js";
|
|
5
5
|
export { ClientRouter, type ClientRouterProps } from "./client-router.js";
|
|
6
|
-
export { navigate, supportsViewTransitions, transitionEnabledOnThisPage, } from "./client-router/router.js";
|
|
6
|
+
export { navigate, supportsViewTransitions, transitionEnabledOnThisPage, syncHistoryEntry, } from "./client-router/router.js";
|
|
7
7
|
export { prefetch, init as prefetchInit } from "./client-router/prefetch.js";
|
|
8
8
|
export type { PrefetchStrategy, PrefetchInitOptions, PrefetchOptions, } from "./client-router/prefetch.js";
|
|
9
9
|
export { TRANSITION_BEFORE_PREPARATION, TRANSITION_AFTER_PREPARATION, TRANSITION_BEFORE_SWAP, TRANSITION_AFTER_SWAP, TRANSITION_PAGE_LOAD, TRANSITION_NAVIGATION_ABORTED, TransitionBeforePreparationEvent, TransitionBeforeSwapEvent, isTransitionBeforePreparationEvent, isTransitionBeforeSwapEvent, } from "./client-router/events.js";
|
|
10
10
|
export { swapFunctions, swap } from "./client-router/swap-functions.js";
|
|
11
|
-
export type { Direction, Fallback, NavigationTypeString, Options } from "./client-router/types.js";
|
|
11
|
+
export type { Direction, Fallback, NavigationTypeString, Options, SyncHistoryEntryOptions, } from "./client-router/types.js";
|
|
12
12
|
export type { ZfbPlugin, ZfbPluginLogger, ZfbBuildHookContext, ZfbDevMiddlewareContext, ZfbDevMiddlewareHandler, ZfbDevMiddlewareRequest, ZfbDevMiddlewareResponse, ZfbRouteEntry, ZfbRouteManifest, ZfbSetupContext, ZfbVirtualModuleLoader, } from "@takazudo/zfb/plugins";
|
package/dist/index.js
CHANGED
|
@@ -28,7 +28,7 @@ export { ViewTransitions } from "./view-transitions.js";
|
|
|
28
28
|
// Client-router public surface (W3D — mirrors @takazudo/zfb-runtime/client-router barrel).
|
|
29
29
|
// See W1B §2 for the full public API spec.
|
|
30
30
|
export { ClientRouter } from "./client-router.js";
|
|
31
|
-
export { navigate, supportsViewTransitions, transitionEnabledOnThisPage, } from "./client-router/router.js";
|
|
31
|
+
export { navigate, supportsViewTransitions, transitionEnabledOnThisPage, syncHistoryEntry, } from "./client-router/router.js";
|
|
32
32
|
// Prefetch public surface (#276).
|
|
33
33
|
// `init` from prefetch.ts is re-exported as `prefetchInit` to avoid collision
|
|
34
34
|
// with the router's `init`.
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,EAAE;AACF,0EAA0E;AAC1E,oEAAoE;AACpE,2EAA2E;AAC3E,qEAAqE;AACrE,2EAA2E;AAC3E,mFAAmF;AACnF,sEAAsE;AACtE,0EAA0E;AAC1E,qEAAqE;AACrE,EAAE;AACF,qEAAqE;AACrE,uEAAuE;AACvE,EAAE;AACF,qEAAqE;AACrE,EAAE;AACF,sCAAsC;AACtC,oBAAoB;AACpB,4EAA4E;AAC5E,iFAAiF;AACjF,QAAQ;AACR,EAAE;AACF,sCAAsC;AACtC,EAAE;AACF,wEAAwE;AAWxE,OAAO,EAAE,eAAe,EAA+B,MAAM,uBAAuB,CAAC;AAErF,2FAA2F;AAC3F,2CAA2C;AAC3C,OAAO,EAAE,YAAY,EAA0B,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EACL,QAAQ,EACR,uBAAuB,EACvB,2BAA2B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,EAAE;AACF,0EAA0E;AAC1E,oEAAoE;AACpE,2EAA2E;AAC3E,qEAAqE;AACrE,2EAA2E;AAC3E,mFAAmF;AACnF,sEAAsE;AACtE,0EAA0E;AAC1E,qEAAqE;AACrE,EAAE;AACF,qEAAqE;AACrE,uEAAuE;AACvE,EAAE;AACF,qEAAqE;AACrE,EAAE;AACF,sCAAsC;AACtC,oBAAoB;AACpB,4EAA4E;AAC5E,iFAAiF;AACjF,QAAQ;AACR,EAAE;AACF,sCAAsC;AACtC,EAAE;AACF,wEAAwE;AAWxE,OAAO,EAAE,eAAe,EAA+B,MAAM,uBAAuB,CAAC;AAErF,2FAA2F;AAC3F,2CAA2C;AAC3C,OAAO,EAAE,YAAY,EAA0B,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EACL,QAAQ,EACR,uBAAuB,EACvB,2BAA2B,EAC3B,gBAAgB,GACjB,MAAM,2BAA2B,CAAC;AAEnC,kCAAkC;AAClC,8EAA8E;AAC9E,4BAA4B;AAC5B,OAAO,EAAE,QAAQ,EAAE,IAAI,IAAI,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAM7E,OAAO,EACL,6BAA6B,EAC7B,4BAA4B,EAC5B,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,6BAA6B,EAC7B,gCAAgC,EAChC,yBAAyB,EACzB,kCAAkC,EAClC,2BAA2B,GAC5B,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,mCAAmC,CAAC","sourcesContent":["// Public entry point for `@takazudo/zfb-runtime`.\n//\n// This is the **client-safe** barrel: everything re-exported here must be\n// bundleable for `--platform=browser` without pulling a server-only\n// dependency. In particular `createPageRouter` (and the Hono server router\n// it builds) is intentionally NOT re-exported here — it lives at the\n// server-only subpath `@takazudo/zfb-runtime/server`. Re-exporting it from\n// this barrel makes any island that does `import ... from \"@takazudo/zfb-runtime\"`\n// drag `hono` into the browser graph, which esbuild must then resolve\n// (issue #1298). The page-router *types* below are `export type` only, so\n// esbuild strips them and no runtime edge to `./router.js` survives.\n//\n// Worker bundles produced by T3 import the server subpath and invoke\n// `createPageRouter` once, at module top, to obtain the fetch handler:\n//\n// import { createPageRouter } from \"@takazudo/zfb-runtime/server\";\n//\n// const router = createPageRouter({\n// pages: [...],\n// contentSnapshot: __ZFB_CONTENT_SNAPSHOT__, // embedded by the bundler\n// framework: { renderToString: render }, // preact-render-to-string etc.\n// });\n//\n// export default { fetch: router };\n//\n// The README documents the bundle shape T6 (embedded V8 host) consumes.\n\nexport type {\n CreatePageRouterOptions,\n PageDefinition,\n PageHeading,\n PageModule,\n PageRouter,\n} from \"./router.js\";\nexport type { FrameworkAdapter } from \"./framework.js\";\nexport type { ContentSnapshot, EntrySnapshot } from \"./snapshot.js\";\nexport { ViewTransitions, type ViewTransitionsElement } from \"./view-transitions.js\";\n\n// Client-router public surface (W3D — mirrors @takazudo/zfb-runtime/client-router barrel).\n// See W1B §2 for the full public API spec.\nexport { ClientRouter, type ClientRouterProps } from \"./client-router.js\";\nexport {\n navigate,\n supportsViewTransitions,\n transitionEnabledOnThisPage,\n syncHistoryEntry,\n} from \"./client-router/router.js\";\n\n// Prefetch public surface (#276).\n// `init` from prefetch.ts is re-exported as `prefetchInit` to avoid collision\n// with the router's `init`.\nexport { prefetch, init as prefetchInit } from \"./client-router/prefetch.js\";\nexport type {\n PrefetchStrategy,\n PrefetchInitOptions,\n PrefetchOptions,\n} from \"./client-router/prefetch.js\";\nexport {\n TRANSITION_BEFORE_PREPARATION,\n TRANSITION_AFTER_PREPARATION,\n TRANSITION_BEFORE_SWAP,\n TRANSITION_AFTER_SWAP,\n TRANSITION_PAGE_LOAD,\n TRANSITION_NAVIGATION_ABORTED,\n TransitionBeforePreparationEvent,\n TransitionBeforeSwapEvent,\n isTransitionBeforePreparationEvent,\n isTransitionBeforeSwapEvent,\n} from \"./client-router/events.js\";\nexport { swapFunctions, swap } from \"./client-router/swap-functions.js\";\nexport type {\n Direction,\n Fallback,\n NavigationTypeString,\n Options,\n SyncHistoryEntryOptions,\n} from \"./client-router/types.js\";\n\n// Plugin lifecycle types (#255). The runtime package re-exports the\n// `@takazudo/zfb/plugins` surface so consumers writing plugins from a\n// project that only depends on `@takazudo/zfb-runtime` can still\n// import the canonical types (e.g. `import type { ZfbPlugin } from\n// \"@takazudo/zfb-runtime\"`).\nexport type {\n ZfbPlugin,\n ZfbPluginLogger,\n ZfbBuildHookContext,\n ZfbDevMiddlewareContext,\n ZfbDevMiddlewareHandler,\n ZfbDevMiddlewareRequest,\n ZfbDevMiddlewareResponse,\n ZfbRouteEntry,\n ZfbRouteManifest,\n ZfbSetupContext,\n ZfbVirtualModuleLoader,\n} from \"@takazudo/zfb/plugins\";\n"]}
|
package/dist/router.d.ts
CHANGED
|
@@ -16,7 +16,10 @@ export interface PageHeading {
|
|
|
16
16
|
* - `default`: the JSX page component. Called with the props returned by
|
|
17
17
|
* `getStaticProps` (if exported) or the `props` from the matching
|
|
18
18
|
* `paths()` entry (for dynamic routes). The return value is fed straight
|
|
19
|
-
* to the framework adapter's `renderToString`.
|
|
19
|
+
* to the framework adapter's `renderToString`. A dynamic route with no
|
|
20
|
+
* `paths()` export is called with `{ params: urlParams }` instead of
|
|
21
|
+
* `{}` — see `createPageRouter`'s JSDoc for the full componentInput
|
|
22
|
+
* derivation table.
|
|
20
23
|
* - `prerender`: literal `false` opts a route OUT of build-time SSG (T5
|
|
21
24
|
* contract). The page router still serves it under the embedded V8 host
|
|
22
25
|
* so dev mode behaves identically; SSG callers filter the route list before
|
|
@@ -106,6 +109,17 @@ export type PageRouter = (request: Request) => Promise<Response>;
|
|
|
106
109
|
* `framework.renderToString(module.default({}))`, and returns the
|
|
107
110
|
* string in a `Response` with the appropriate `Content-Type`.
|
|
108
111
|
*
|
|
112
|
+
* Per-route `componentInput` (the object passed to `module.default(...)`) is
|
|
113
|
+
* derived from the route pattern and the page module's exports:
|
|
114
|
+
* - Dynamic route + `paths()` export: match the URL params against the
|
|
115
|
+
* `paths()` entries and pass `{ params, ...props }` (404 on no match).
|
|
116
|
+
* - Static route + `getStaticProps()` export: pass the returned `props`.
|
|
117
|
+
* - Dynamic route with NO `paths()` export (e.g. a per-request SSR page
|
|
118
|
+
* whose slugs can't be enumerated ahead of time): pass `{ params:
|
|
119
|
+
* urlParams }` so the component still knows which URL params it is
|
|
120
|
+
* serving, rather than being invoked with `{}`.
|
|
121
|
+
* - Anything else (static route, no `getStaticProps`): pass `{}`.
|
|
122
|
+
*
|
|
109
123
|
* The returned function is a plain `(request) => Promise<Response>` so a
|
|
110
124
|
* Worker entry point can `export default { fetch: createPageRouter(...) }`
|
|
111
125
|
* directly.
|
package/dist/router.js
CHANGED
|
@@ -110,6 +110,17 @@ async function getOrEvalPaths(memo, routeKey, pathsFn) {
|
|
|
110
110
|
* `framework.renderToString(module.default({}))`, and returns the
|
|
111
111
|
* string in a `Response` with the appropriate `Content-Type`.
|
|
112
112
|
*
|
|
113
|
+
* Per-route `componentInput` (the object passed to `module.default(...)`) is
|
|
114
|
+
* derived from the route pattern and the page module's exports:
|
|
115
|
+
* - Dynamic route + `paths()` export: match the URL params against the
|
|
116
|
+
* `paths()` entries and pass `{ params, ...props }` (404 on no match).
|
|
117
|
+
* - Static route + `getStaticProps()` export: pass the returned `props`.
|
|
118
|
+
* - Dynamic route with NO `paths()` export (e.g. a per-request SSR page
|
|
119
|
+
* whose slugs can't be enumerated ahead of time): pass `{ params:
|
|
120
|
+
* urlParams }` so the component still knows which URL params it is
|
|
121
|
+
* serving, rather than being invoked with `{}`.
|
|
122
|
+
* - Anything else (static route, no `getStaticProps`): pass `{}`.
|
|
123
|
+
*
|
|
113
124
|
* The returned function is a plain `(request) => Promise<Response>` so a
|
|
114
125
|
* Worker entry point can `export default { fetch: createPageRouter(...) }`
|
|
115
126
|
* directly.
|
|
@@ -314,6 +325,16 @@ export function createPageRouter(opts) {
|
|
|
314
325
|
...(match.props ?? {}),
|
|
315
326
|
};
|
|
316
327
|
}
|
|
328
|
+
else if (isDynamicRoute) {
|
|
329
|
+
// Dynamic route with no `paths()` export: the natural shape for a
|
|
330
|
+
// per-request SSR page whose slugs can't be enumerated ahead of
|
|
331
|
+
// time. Neither branch above applies (no paths() to match against,
|
|
332
|
+
// and getStaticProps only fires for static routes), so without this
|
|
333
|
+
// the component would be invoked with `{}` — no params, no
|
|
334
|
+
// diagnostic, and no way to know which slug it is serving. Pass the
|
|
335
|
+
// URL params through directly instead.
|
|
336
|
+
componentInput = { params: urlParams };
|
|
337
|
+
}
|
|
317
338
|
else if (!isDynamicRoute && typeof mod.getStaticProps === "function") {
|
|
318
339
|
// Static route with `getStaticProps`: call it to fetch build-time
|
|
319
340
|
// data and pass the returned props to the default component. This
|
package/dist/router.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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","sourcesContent":["// `@takazudo/zfb-runtime` — Hono-based page router.\n//\n// `createPageRouter` is the JS-side entry point for ADR-007's SSG-first\n// architecture. The pipeline goes:\n//\n// user pages/ + content/ + layouts/ + components/\n// → esbuild bundle (T3) // single ESM file\n// → embedded V8 host (T6) // same WinterCG surface as CF Workers\n// → createPageRouter({ pages, contentSnapshot, framework })\n// → (request) => Promise<Response>\n//\n// The bundle's Worker entry point shape is documented in the package\n// README. The contract is intentionally minimal: one factory call returns\n// one fetch handler. Hono is an implementation detail — callers should\n// not depend on the Hono types leaking through. The exposed surface is\n// the four types here plus the `createPageRouter` function.\n//\n// Side effect on init: registers the supplied `ContentSnapshot` with\n// `zfb/content`'s module-level snapshot bridge so any page module\n// importing `getCollection(\"...\")` resolves from memory rather than\n// touching the Node `fs` API (the Worker runtime has no `fs`).\n//\n// ## Synthetic `__paths__` endpoint\n//\n// The Rust build pipeline needs to evaluate non-literal `paths()` exports\n// (e.g. those that `await import(\"@takazudo/zfb/content\")` and call `getCollection`)\n// at runtime against the running embedded host. The router exposes a\n// synthetic internal endpoint:\n//\n// GET /__paths__/<percent-encoded-route-key>\n//\n// When a page registered at `route` has a `paths` export, the handler calls\n// it and returns the JSON-serialized array as `application/json`. If the\n// `paths` export is missing or throws, the response is a descriptive 500.\n// This endpoint is only meant for the build pipeline — it is safe to leave\n// registered in production because no user-authored route should start with\n// `/__paths__/` (the build pipeline rejects any route that conflicts).\n\nimport { Hono } from \"hono\";\nimport { setContentSnapshot } from \"@takazudo/zfb/content\";\n\nimport type { FrameworkAdapter } from \"./framework.js\";\nimport type { ContentSnapshot } from \"./snapshot.js\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Heading metadata emitted by the MDX `headings` export (T4). Cross-ref:\n * `crates/zfb-content/src/mdx_jsx_emit.rs`. Optional on the page-module\n * shape — non-MDX pages won't carry it.\n */\nexport interface PageHeading {\n readonly depth: number;\n readonly slug: string;\n readonly text: string;\n}\n\n/**\n * The shape every page module must export.\n *\n * - `default`: the JSX page component. Called with the props returned by\n * `getStaticProps` (if exported) or the `props` from the matching\n * `paths()` entry (for dynamic routes). The return value is fed straight\n * to the framework adapter's `renderToString`.\n * - `prerender`: literal `false` opts a route OUT of build-time SSG (T5\n * contract). The page router still serves it under the embedded V8 host\n * so dev mode behaves identically; SSG callers filter the route list before\n * driving the renderer.\n * - `contentType`: optional override for non-HTML routes (e.g.\n * `application/xml` for `rss.xml.tsx`). Default is\n * `text/html; charset=utf-8`. Cross-ref shipped #49.\n * - `headings`: optional list emitted by MDX (T4).\n * - `paths`: optional dynamic-route enumerator. Called at build time by\n * the `__paths__` synthetic endpoint to produce the concrete URL list\n * for this route template. May be async. Returns an array of\n * `{ params, props? }` objects identical in shape to the Astro/zfb\n * `paths()` contract.\n * **Evaluated once per router instance** — the result is memoised and\n * shared across the `/__paths__` handler and all per-page render\n * requests. This matches the build-time-enumerator contract: every\n * entry rendered within a single build sees the same paths() snapshot.\n * Dev mode is safe because each file-save triggers a fresh bundle,\n * which creates a new router instance with a clean memo.\n * - `getStaticProps`: optional async function for static routes that need\n * to fetch data at build/render time. Called once per request (before\n * `default`). Must return `{ props: Record<string, unknown> }`. The\n * returned `props` are spread into the `default` component's props.\n */\nexport interface PageModule {\n readonly default: (props: Record<string, unknown>) => unknown;\n readonly prerender?: boolean;\n readonly contentType?: string;\n readonly headings?: readonly PageHeading[];\n readonly paths?: () => unknown[] | Promise<unknown[]>;\n readonly getStaticProps?: () => Promise<{ props: Record<string, unknown> }>;\n}\n\n/**\n * One page registered with the router.\n *\n * `route` is a Hono path pattern (e.g. `/`, `/blog/:slug`,\n * `/blog/page/:page`). `module` is a thunk so the bundle can use code\n * splitting if it wants to — today the bundler emits everything as one\n * ESM file and the thunk simply returns the already-loaded module.\n */\nexport interface PageDefinition {\n readonly route: string;\n readonly module: () => Promise<PageModule>;\n}\n\n/** Options accepted by [`createPageRouter`]. */\nexport interface CreatePageRouterOptions {\n /** Pages to register. Order does not affect routing — Hono dispatches by path. */\n readonly pages: readonly PageDefinition[];\n /**\n * In-memory content snapshot. Embedded into the bundle by T3; the\n * router hands it to `zfb/content` so user pages reading content via\n * `getCollection(...)` resolve synchronously from memory.\n */\n readonly contentSnapshot: ContentSnapshot;\n /** Framework adapter pinning the SSR call. */\n readonly framework: FrameworkAdapter;\n /**\n * When `true`, the 500 body for render errors includes the full JS stack\n * trace. When `false`, only the message + route are included. When\n * omitted, the runtime checks `globalThis.__zfb.ssrDebug` at request time:\n * that flag is set by the embedded V8 build/dev host (`globals_shim.js`)\n * and is absent on the production Cloudflare Workers runtime.\n *\n * Default is effectively OFF for production (no flag ⇒ message + route only).\n * Useful for explicit injection in unit tests without global mutation.\n */\n readonly includeErrorStack?: boolean;\n}\n\n/**\n * Fetch-handler shape returned by [`createPageRouter`]. Shaped as a plain\n * function (not a Hono `app`) so the consumer's contract is exactly\n * \"Worker-style fetch handler\" with no leaked framework types.\n */\nexport type PageRouter = (request: Request) => Promise<Response>;\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Default content-type when a page module does not override.\n *\n * Aligned with #49's per-page `contentType` convention: the default\n * served for HTML pages is `text/html; charset=utf-8`. Tests pin this\n * verbatim because the embedded V8 host does NOT auto-set a charset.\n */\nconst DEFAULT_CONTENT_TYPE = \"text/html; charset=utf-8\";\n\n/**\n * Issue #530: prepend `<!doctype html>\\n` to SSR-rendered HTML bodies that\n * lack a doctype declaration, mirroring the guard in\n * `crates/zfb-build/src/renderer.rs::render_one_inner` (issue #524).\n *\n * Gate conditions (all must hold to prepend):\n * 1. `contentType` media-type (before `;`) is `text/html` (case-insensitive).\n * 2. The body, after stripping an optional UTF-8 BOM (U+FEFF) and leading\n * ASCII whitespace, starts with `<html` (case-insensitive) — i.e. it is\n * an `<html>`-rooted document, not XML / JSON / a fragment.\n * 3. The body (same stripped prefix) does NOT already begin with `<!doctype`\n * (case-insensitive).\n *\n * Deliberately NOT shared with the Rust side — duplication of a 5-line guard\n * is the right call rather than introducing a new cross-language boundary.\n */\nfunction ensureHtml5Doctype(body: string, contentType: string): string {\n const mediaType = contentType.split(\";\")[0]?.trim().toLowerCase();\n if (mediaType !== \"text/html\") return body;\n // Strip optional BOM and leading whitespace to inspect the root element.\n const stripped = body.replace(/^/, \"\").trimStart();\n if (!stripped.toLowerCase().startsWith(\"<!doctype\")) {\n if (stripped.toLowerCase().startsWith(\"<html\")) {\n return `<!doctype html>\\n${body}`;\n }\n }\n return body;\n}\n\n/**\n * Evaluate `pathsFn` once per `routeKey`, sharing the result across all\n * concurrent and subsequent callers via a Promise stored in `memo`.\n *\n * Rejections are evicted from the memo so that a transient paths() failure\n * does not permanently poison the cache — the next request retries.\n */\nasync function getOrEvalPaths(\n memo: Map<string, Promise<unknown[]>>,\n routeKey: string,\n pathsFn: () => unknown[] | Promise<unknown[]>,\n): Promise<unknown[]> {\n const existing = memo.get(routeKey);\n if (existing !== undefined) {\n return existing;\n }\n const promise = Promise.resolve(pathsFn()).then(\n (v) => v as unknown[],\n (err) => {\n // Evict on rejection so the next request retries.\n memo.delete(routeKey);\n return Promise.reject(err) as Promise<unknown[]>;\n },\n );\n memo.set(routeKey, promise);\n return promise;\n}\n\n/**\n * Build a page router for the SSG-first architecture (ADR-005).\n *\n * Side effects:\n * 1. Registers `opts.contentSnapshot` with the `zfb/content` module so\n * `getCollection(name)` resolves from memory. Idempotent across\n * calls — the latest snapshot wins (matches the documented dev-mode\n * live-reload contract).\n * 2. Constructs an internal Hono app and registers a GET handler per\n * `pages[i].route`. The handler imports the page module, calls\n * `framework.renderToString(module.default({}))`, and returns the\n * string in a `Response` with the appropriate `Content-Type`.\n *\n * The returned function is a plain `(request) => Promise<Response>` so a\n * Worker entry point can `export default { fetch: createPageRouter(...) }`\n * directly.\n */\nexport function createPageRouter(opts: CreatePageRouterOptions): PageRouter {\n setContentSnapshot(opts.contentSnapshot);\n\n const app = new Hono();\n\n // Build a lookup map: Hono route pattern → PageDefinition, for the\n // `__paths__` synthetic endpoint below. The map is keyed on the `route`\n // string exactly as the caller supplied it (e.g. \"/blog/:slug\").\n const pagesByRoute = new Map<string, PageDefinition>();\n for (const page of opts.pages) {\n pagesByRoute.set(page.route, page);\n }\n\n // Per-router-instance memo for paths() results.\n //\n // Keyed on page.route (e.g. \"/blog/:slug\"). Stores the in-flight or\n // settled Promise so that concurrent requests share one evaluation.\n // Rejections are NOT cached: if paths() throws the Promise is removed\n // from the map so the next request retries rather than re-propagating\n // the same error indefinitely.\n //\n // Production SSR isolates evaluate paths() once per isolate, which\n // matches the documented build-time-enumerator contract. Dev mode is\n // safe because each re-bundle creates a fresh router instance\n // (dev.rs reload_renderer), so stale paths() results never persist\n // across a file-save cycle. Watch-time invalidation is explicitly\n // out of scope per issue #507.\n const pathsMemo = new Map<string, Promise<unknown[]>>();\n\n // Sanity check: a user-authored route that happens to start with\n // `/__paths__` (or a top-level catchall like `/:slug{.+}`) would\n // shadow the synthetic endpoint registered below if Hono dispatched\n // by registration order. We register `/__paths__/:routeKey{.+}` first\n // (see directly below) but still warn on collisions so users do not\n // ship a page that conflicts with the build pipeline's wire format.\n for (const page of opts.pages) {\n if (routeShadowsPathsEndpoint(page.route)) {\n // Use console.warn so the message reaches the host's tail logs\n // without bringing down the worker. The build pipeline's\n // /__paths__ requests still resolve correctly because the\n // synthetic handler is registered first.\n // eslint-disable-next-line no-console\n console.warn(\n `[zfb-runtime] route \"${page.route}\" may shadow the synthetic /__paths__ endpoint; rename the page or use a more specific pattern`,\n );\n }\n }\n\n // -------------------------------------------------------------------------\n // Synthetic `/__paths__/<encoded-route-key>` endpoint.\n //\n // Called by the Rust build pipeline (crates/zfb/src/render_pipeline.rs)\n // to evaluate non-literal `paths()` exports at runtime. The route key is\n // the Hono pattern for the page (e.g. `/blog/:slug`) percent-encoded so\n // it survives in the URL path segment. The response is a JSON array of\n // `{ params, props? }` objects identical to the `paths()` contract.\n //\n // Pattern: `/__paths__/:routeKey{.+}` — the `{.+}` quantifier (Hono's\n // regex-segment syntax) allows slashes inside the route key so\n // `/blog/:slug` decodes correctly from `/__paths__/%2Fblog%2F%3Aslug`.\n //\n // IMPORTANT: this handler is registered BEFORE user routes so a\n // user-authored top-level catchall (e.g. `/:wildcard{.+}`) cannot\n // shadow it — Hono dispatches in registration order.\n // -------------------------------------------------------------------------\n app.get(\"/__paths__/:routeKey{.+}\", async (c) => {\n // Hono's `c.req.param(\"routeKey\")` already URL-decodes the captured\n // segment when it contains a `%`, so a single decode is correct\n // here — no explicit `decodeURIComponent` (that would be a\n // double-decode and break literal `%` characters in route keys).\n const routeKey = c.req.param(\"routeKey\");\n\n const page = pagesByRoute.get(routeKey);\n if (!page) {\n return c.body(\n `[zfb-runtime] /__paths__: no page registered for route key \"${routeKey}\"`,\n 404,\n { \"Content-Type\": \"text/plain; charset=utf-8\" },\n );\n }\n\n const mod = await page.module();\n if (typeof mod.paths !== \"function\") {\n return c.body(\n `[zfb-runtime] /__paths__: page module for \"${routeKey}\" has no paths() export`,\n 404,\n { \"Content-Type\": \"text/plain; charset=utf-8\" },\n );\n }\n\n let result: unknown[];\n try {\n result = await getOrEvalPaths(pathsMemo, routeKey, mod.paths);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return c.body(`[zfb-runtime] /__paths__: paths() threw for \"${routeKey}\": ${msg}`, 500, {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n });\n }\n\n return c.body(JSON.stringify(result), 200, {\n \"Content-Type\": \"application/json; charset=utf-8\",\n });\n });\n\n for (const page of opts.pages) {\n // `app.all` (vs `app.get`) so SSR routes whose page handler dispatches\n // by `request.method` (e.g. POST API endpoints like\n // `pages/api/*.tsx`) actually reach the handler. The handler is then\n // responsible for returning a method-appropriate status (e.g. 405 for\n // an unsupported verb). With `app.get` the inner router would 404\n // before the handler ever ran, leaving e.g. `POST /api/foo` looking\n // identical to a missing route.\n app.all(page.route, async (c) => {\n const mod = await page.module();\n if (typeof mod.default !== \"function\") {\n // Surface as a 500 with a well-known message rather than letting\n // Hono swallow the error into a generic body. T6's embedded V8\n // host log-tail / source-map plumbing is what eventually projects\n // page-side errors back to the user's TSX line; until then the\n // pinned message is the contract this layer ships.\n return c.body(\n `[zfb-runtime] page module for \"${page.route}\" did not export a default component`,\n 500,\n { \"Content-Type\": \"text/plain; charset=utf-8\" },\n );\n }\n\n // For dynamic routes that export `paths()`, look up the concrete\n // entry for this URL by matching the URL params against the\n // paths() results. This implements the ADR-002 contract:\n // paths() → [{ params, props? }]\n // render(url) → find matching entry → pass { params, props } to default()\n //\n // For static routes (no `paths()` export, no URL params), we pass\n // an empty object — the component signature has no required props.\n // For dynamic routes whose URL params do not match any paths()\n // entry, we return a 404 rather than rendering with empty props.\n //\n // Whether the route is dynamic is derived from the ROUTE PATTERN,\n // not from the captured params: for an optional catchall\n // (`/docs/:slug{.+}?`), Hono matches the bare `/docs` with NO\n // params captured at all, so `Object.keys(c.req.param())` would\n // be empty and the old gate skipped paths() entirely — rendering\n // with `{}` instead of the entry whose `slug` is `[]`.\n const declaredParams = routeParamSpecs(page.route);\n const rawUrlParams = c.req.param() ?? {};\n const urlParams: Record<string, string> = {};\n for (const [k, v] of Object.entries(rawUrlParams)) {\n // Unmatched optional params may surface as undefined — drop them\n // so \"param absent\" is represented uniformly.\n if (typeof v === \"string\") urlParams[k] = v;\n }\n const isDynamicRoute = declaredParams.length > 0;\n\n let componentInput: Record<string, unknown> = {};\n\n if (isDynamicRoute && typeof mod.paths === \"function\") {\n let pathsResult: unknown;\n try {\n pathsResult = await getOrEvalPaths(pathsMemo, page.route, mod.paths);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return c.body(`[zfb-runtime] paths() threw for \"${page.route}\": ${msg}`, 500, {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n });\n }\n\n if (!Array.isArray(pathsResult)) {\n return c.body(`[zfb-runtime] paths() for \"${page.route}\" did not return an array`, 500, {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n });\n }\n\n // Validate each entry's shape per-entry rather than using a bare\n // cast — matches the strictness the Rust pipeline applies on the\n // same wire format. Any malformed entry surfaces as a 500.\n for (const entry of pathsResult) {\n if (!isPathsEntry(entry)) {\n return c.body(\n `[zfb-runtime] paths() for \"${page.route}\" returned an entry without a valid params object`,\n 500,\n { \"Content-Type\": \"text/plain; charset=utf-8\" },\n );\n }\n }\n\n // Find the entry whose params match the URL params for this\n // request. For catchall params (e.g. slug for\n // /docs/[...slug]), Hono returns a slash-joined string (e.g.\n // \"guides/install\"), so we compare against the paths() entry's\n // params.slug joined with \"/\".\n //\n // Matching iterates the params DECLARED by the route pattern so\n // the zero-segment optional-catchall case is covered: when Hono\n // matched `/docs` for `/docs/:slug{.+}?` no `slug` param exists\n // in the URL, and the matching entry is the one whose param is\n // the explicit empty array (`{ slug: [] }`).\n const match = (pathsResult as PathsEntry[]).find((entry) => {\n return declaredParams.every(({ name, optionalCatchall }) => {\n const urlVal = urlParams[name];\n const paramVal = entry.params[name];\n if (urlVal === undefined) {\n // Param absent from the URL: only valid for an optional\n // catchall, and only against the explicit `[]` entry.\n return optionalCatchall && Array.isArray(paramVal) && paramVal.length === 0;\n }\n if (Array.isArray(paramVal)) {\n // catchall: paths() emits slug as string[] but Hono\n // provides it as a \"/\"-joined string\n return paramVal.join(\"/\") === urlVal;\n }\n return String(paramVal) === urlVal;\n });\n });\n\n if (!match) {\n // The URL params do not correspond to any paths() entry —\n // this is the dev-mode equivalent of a build-time miss.\n // Hono's `c.notFound()` returns the framework's default 404,\n // which is cleaner than fabricating an empty-props render.\n return c.notFound();\n }\n\n // Pass the paths() entry's props directly as component props\n // (spread to top level, matching the Astro/zfb convention).\n // Also include `params` so components can access URL params if\n // needed, but individual prop keys from `props` win on collision.\n componentInput = {\n params: match.params,\n ...(match.props ?? {}),\n };\n } else if (!isDynamicRoute && typeof mod.getStaticProps === \"function\") {\n // Static route with `getStaticProps`: call it to fetch build-time\n // data and pass the returned props to the default component. This\n // supports the `export async function getStaticProps()` pattern\n // used by static pages that need to query content collections (e.g.\n // a homepage listing all blog posts via `getCollection(\"blog\")`).\n let staticPropsResult: unknown;\n try {\n staticPropsResult = await mod.getStaticProps();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return c.body(`[zfb-runtime] getStaticProps() threw for \"${page.route}\": ${msg}`, 500, {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n });\n }\n if (\n typeof staticPropsResult !== \"object\" ||\n staticPropsResult === null ||\n !(\"props\" in staticPropsResult)\n ) {\n return c.body(\n `[zfb-runtime] getStaticProps() for \"${page.route}\" must return { props: {...} }`,\n 500,\n { \"Content-Type\": \"text/plain; charset=utf-8\" },\n );\n }\n componentInput = (staticPropsResult as { props: Record<string, unknown> }).props;\n }\n\n // `await` so async page modules (e.g. API routes typed as\n // `(): Promise<Response>`) resolve before we inspect the value.\n // For sync pages that return a VNode/string the await is a no-op.\n //\n // Wrapped in a try/catch so that any error thrown by the component\n // or by renderToString surfaces as a descriptive 500 rather than\n // escaping to Hono's generic error handler (which discards the real\n // message). Mirrors the getStaticProps catch above.\n try {\n const result = await mod.default(componentInput);\n // API route short-circuit: a page module that returns a Response\n // directly (e.g. `pages/api/*.tsx` handlers that use Web Fetch\n // primitives instead of returning JSX) is responsible for its own\n // status, headers, and body — return it as-is rather than running\n // it through the framework SSR path. A `return` from inside a\n // `try` does not trigger the `catch`, so this passes through\n // correctly without special-casing.\n if (result instanceof Response) {\n return result;\n }\n // Non-HTML routes (e.g. `sitemap.xml.tsx`, `feed.xml.tsx`) commonly\n // return their body as a pre-serialised `string` instead of a\n // VNode. Routing those through `framework.renderToString` would\n // HTML-escape the angle brackets and ampersands, producing\n // garbage XML. Pass strings through verbatim; only wrap actual\n // VNodes.\n const html = typeof result === \"string\" ? result : opts.framework.renderToString(result);\n const contentType = mod.contentType ?? DEFAULT_CONTENT_TYPE;\n return c.body(ensureHtml5Doctype(html, contentType), 200, { \"Content-Type\": contentType });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n // __zfb.ssrDebug is set only by the embedded V8 build/dev host\n // (globals_shim.js), never by the production Workers runtime.\n const includeStack =\n opts.includeErrorStack ??\n (globalThis as { __zfb?: { ssrDebug?: boolean } }).__zfb?.ssrDebug === true;\n const stack = includeStack && err instanceof Error && err.stack ? `\\n${err.stack}` : \"\";\n return c.body(`[zfb-runtime] render threw for \"${page.route}\": ${msg}${stack}`, 500, {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n });\n }\n });\n }\n\n // Hono's `app.fetch` returns `Response | Promise<Response>`. The\n // public router contract is unconditionally async; using an `async`\n // wrapper (rather than `Promise.resolve(...)`) ensures any\n // synchronous throw inside `app.fetch` is converted to a rejected\n // promise instead of escaping the caller's `await`.\n return async (request) => await app.fetch(request);\n}\n\n/**\n * Shape of one entry produced by a page's `paths()` export. The\n * `params` object is required (every entry needs to identify the URL\n * it represents); `props` is optional (the page may render purely from\n * the URL params).\n */\ninterface PathsEntry {\n readonly params: Record<string, unknown>;\n readonly props?: Record<string, unknown>;\n}\n\n/**\n * Type guard for one `paths()` entry. Mirrors the strictness of the\n * Rust pipeline's `paths()` resolver — every entry must be a non-null\n * object whose `params` is itself a non-null object.\n */\nfunction isPathsEntry(x: unknown): x is PathsEntry {\n if (typeof x !== \"object\" || x === null) return false;\n const params = (x as { params?: unknown }).params;\n return typeof params === \"object\" && params !== null;\n}\n\n/**\n * One param declared by a Hono route pattern. `optionalCatchall` is\n * `true` for the `:name{.+}?` form (file-system `[[...name]]`), whose\n * zero-segment match captures no param at all.\n */\ninterface RouteParamSpec {\n readonly name: string;\n readonly optionalCatchall: boolean;\n}\n\n/**\n * Parse the params declared by a Hono route pattern (the `route` string\n * the build pipeline registers, e.g. `/blog/:slug`, `/docs/:slug{.+}`,\n * `/docs/:slug{.+}?`). Static routes return an empty list.\n *\n * Only the pattern shapes emitted by `bracket_to_hono` (zfb-build) are\n * recognised — `:name`, `:name{.+}`, `:name{.+}?` — which is the entire\n * grammar the file router produces.\n */\nfunction routeParamSpecs(route: string): RouteParamSpec[] {\n const specs: RouteParamSpec[] = [];\n for (const seg of route.split(\"/\")) {\n if (!seg.startsWith(\":\")) continue;\n const optionalCatchall = seg.endsWith(\"?\");\n const body = optionalCatchall ? seg.slice(1, -1) : seg.slice(1);\n const braceIdx = body.indexOf(\"{\");\n const name = braceIdx === -1 ? body : body.slice(0, braceIdx);\n if (name.length > 0) {\n specs.push({ name, optionalCatchall });\n }\n }\n return specs;\n}\n\n/**\n * Heuristic check for user-authored routes that may shadow the\n * synthetic `/__paths__/<encoded-route-key>` endpoint.\n *\n * Returns true when the route literal contains `/__paths__` (an\n * obvious collision) or when its first segment is a Hono catchall\n * (`:name{.+}`, optionally `:name{.+}?`) or a file-system catchall\n * (`[...name]` / `[[...name]]`) at the root — those would match\n * `/__paths__/...` once Hono dispatches to them.\n */\nfunction routeShadowsPathsEndpoint(route: string): boolean {\n if (route.includes(\"/__paths__\")) {\n return true;\n }\n // Strip the leading slash and look at the first segment only —\n // anything deeper cannot match `/__paths__` because the literal\n // first segment differs.\n const firstSeg = route.replace(/^\\/+/, \"\").split(\"/\")[0] ?? \"\";\n // Hono regex-quantifier catchall: `:name{.+}` / optional `:name{.+}?`.\n if (/^:[A-Za-z_][\\w]*\\{\\.[+*]\\}\\??$/.test(firstSeg)) {\n return true;\n }\n // File-system catchall (pre-bracket-to-hono): `[...name]` / `[[...name]]`.\n if (/^\\[\\.\\.\\.[A-Za-z_][\\w]*\\]$/.test(firstSeg)) {\n return true;\n }\n if (/^\\[\\[\\.\\.\\.[A-Za-z_][\\w]*\\]\\]$/.test(firstSeg)) {\n return true;\n }\n return false;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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","sourcesContent":["// `@takazudo/zfb-runtime` — Hono-based page router.\n//\n// `createPageRouter` is the JS-side entry point for ADR-007's SSG-first\n// architecture. The pipeline goes:\n//\n// user pages/ + content/ + layouts/ + components/\n// → esbuild bundle (T3) // single ESM file\n// → embedded V8 host (T6) // same WinterCG surface as CF Workers\n// → createPageRouter({ pages, contentSnapshot, framework })\n// → (request) => Promise<Response>\n//\n// The bundle's Worker entry point shape is documented in the package\n// README. The contract is intentionally minimal: one factory call returns\n// one fetch handler. Hono is an implementation detail — callers should\n// not depend on the Hono types leaking through. The exposed surface is\n// the four types here plus the `createPageRouter` function.\n//\n// Side effect on init: registers the supplied `ContentSnapshot` with\n// `zfb/content`'s module-level snapshot bridge so any page module\n// importing `getCollection(\"...\")` resolves from memory rather than\n// touching the Node `fs` API (the Worker runtime has no `fs`).\n//\n// ## Synthetic `__paths__` endpoint\n//\n// The Rust build pipeline needs to evaluate non-literal `paths()` exports\n// (e.g. those that `await import(\"@takazudo/zfb/content\")` and call `getCollection`)\n// at runtime against the running embedded host. The router exposes a\n// synthetic internal endpoint:\n//\n// GET /__paths__/<percent-encoded-route-key>\n//\n// When a page registered at `route` has a `paths` export, the handler calls\n// it and returns the JSON-serialized array as `application/json`. If the\n// `paths` export is missing or throws, the response is a descriptive 500.\n// This endpoint is only meant for the build pipeline — it is safe to leave\n// registered in production because no user-authored route should start with\n// `/__paths__/` (the build pipeline rejects any route that conflicts).\n\nimport { Hono } from \"hono\";\nimport { setContentSnapshot } from \"@takazudo/zfb/content\";\n\nimport type { FrameworkAdapter } from \"./framework.js\";\nimport type { ContentSnapshot } from \"./snapshot.js\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Heading metadata emitted by the MDX `headings` export (T4). Cross-ref:\n * `crates/zfb-content/src/mdx_jsx_emit.rs`. Optional on the page-module\n * shape — non-MDX pages won't carry it.\n */\nexport interface PageHeading {\n readonly depth: number;\n readonly slug: string;\n readonly text: string;\n}\n\n/**\n * The shape every page module must export.\n *\n * - `default`: the JSX page component. Called with the props returned by\n * `getStaticProps` (if exported) or the `props` from the matching\n * `paths()` entry (for dynamic routes). The return value is fed straight\n * to the framework adapter's `renderToString`. A dynamic route with no\n * `paths()` export is called with `{ params: urlParams }` instead of\n * `{}` — see `createPageRouter`'s JSDoc for the full componentInput\n * derivation table.\n * - `prerender`: literal `false` opts a route OUT of build-time SSG (T5\n * contract). The page router still serves it under the embedded V8 host\n * so dev mode behaves identically; SSG callers filter the route list before\n * driving the renderer.\n * - `contentType`: optional override for non-HTML routes (e.g.\n * `application/xml` for `rss.xml.tsx`). Default is\n * `text/html; charset=utf-8`. Cross-ref shipped #49.\n * - `headings`: optional list emitted by MDX (T4).\n * - `paths`: optional dynamic-route enumerator. Called at build time by\n * the `__paths__` synthetic endpoint to produce the concrete URL list\n * for this route template. May be async. Returns an array of\n * `{ params, props? }` objects identical in shape to the Astro/zfb\n * `paths()` contract.\n * **Evaluated once per router instance** — the result is memoised and\n * shared across the `/__paths__` handler and all per-page render\n * requests. This matches the build-time-enumerator contract: every\n * entry rendered within a single build sees the same paths() snapshot.\n * Dev mode is safe because each file-save triggers a fresh bundle,\n * which creates a new router instance with a clean memo.\n * - `getStaticProps`: optional async function for static routes that need\n * to fetch data at build/render time. Called once per request (before\n * `default`). Must return `{ props: Record<string, unknown> }`. The\n * returned `props` are spread into the `default` component's props.\n */\nexport interface PageModule {\n readonly default: (props: Record<string, unknown>) => unknown;\n readonly prerender?: boolean;\n readonly contentType?: string;\n readonly headings?: readonly PageHeading[];\n readonly paths?: () => unknown[] | Promise<unknown[]>;\n readonly getStaticProps?: () => Promise<{ props: Record<string, unknown> }>;\n}\n\n/**\n * One page registered with the router.\n *\n * `route` is a Hono path pattern (e.g. `/`, `/blog/:slug`,\n * `/blog/page/:page`). `module` is a thunk so the bundle can use code\n * splitting if it wants to — today the bundler emits everything as one\n * ESM file and the thunk simply returns the already-loaded module.\n */\nexport interface PageDefinition {\n readonly route: string;\n readonly module: () => Promise<PageModule>;\n}\n\n/** Options accepted by [`createPageRouter`]. */\nexport interface CreatePageRouterOptions {\n /** Pages to register. Order does not affect routing — Hono dispatches by path. */\n readonly pages: readonly PageDefinition[];\n /**\n * In-memory content snapshot. Embedded into the bundle by T3; the\n * router hands it to `zfb/content` so user pages reading content via\n * `getCollection(...)` resolve synchronously from memory.\n */\n readonly contentSnapshot: ContentSnapshot;\n /** Framework adapter pinning the SSR call. */\n readonly framework: FrameworkAdapter;\n /**\n * When `true`, the 500 body for render errors includes the full JS stack\n * trace. When `false`, only the message + route are included. When\n * omitted, the runtime checks `globalThis.__zfb.ssrDebug` at request time:\n * that flag is set by the embedded V8 build/dev host (`globals_shim.js`)\n * and is absent on the production Cloudflare Workers runtime.\n *\n * Default is effectively OFF for production (no flag ⇒ message + route only).\n * Useful for explicit injection in unit tests without global mutation.\n */\n readonly includeErrorStack?: boolean;\n}\n\n/**\n * Fetch-handler shape returned by [`createPageRouter`]. Shaped as a plain\n * function (not a Hono `app`) so the consumer's contract is exactly\n * \"Worker-style fetch handler\" with no leaked framework types.\n */\nexport type PageRouter = (request: Request) => Promise<Response>;\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Default content-type when a page module does not override.\n *\n * Aligned with #49's per-page `contentType` convention: the default\n * served for HTML pages is `text/html; charset=utf-8`. Tests pin this\n * verbatim because the embedded V8 host does NOT auto-set a charset.\n */\nconst DEFAULT_CONTENT_TYPE = \"text/html; charset=utf-8\";\n\n/**\n * Issue #530: prepend `<!doctype html>\\n` to SSR-rendered HTML bodies that\n * lack a doctype declaration, mirroring the guard in\n * `crates/zfb-build/src/renderer.rs::render_one_inner` (issue #524).\n *\n * Gate conditions (all must hold to prepend):\n * 1. `contentType` media-type (before `;`) is `text/html` (case-insensitive).\n * 2. The body, after stripping an optional UTF-8 BOM (U+FEFF) and leading\n * ASCII whitespace, starts with `<html` (case-insensitive) — i.e. it is\n * an `<html>`-rooted document, not XML / JSON / a fragment.\n * 3. The body (same stripped prefix) does NOT already begin with `<!doctype`\n * (case-insensitive).\n *\n * Deliberately NOT shared with the Rust side — duplication of a 5-line guard\n * is the right call rather than introducing a new cross-language boundary.\n */\nfunction ensureHtml5Doctype(body: string, contentType: string): string {\n const mediaType = contentType.split(\";\")[0]?.trim().toLowerCase();\n if (mediaType !== \"text/html\") return body;\n // Strip optional BOM and leading whitespace to inspect the root element.\n const stripped = body.replace(/^/, \"\").trimStart();\n if (!stripped.toLowerCase().startsWith(\"<!doctype\")) {\n if (stripped.toLowerCase().startsWith(\"<html\")) {\n return `<!doctype html>\\n${body}`;\n }\n }\n return body;\n}\n\n/**\n * Evaluate `pathsFn` once per `routeKey`, sharing the result across all\n * concurrent and subsequent callers via a Promise stored in `memo`.\n *\n * Rejections are evicted from the memo so that a transient paths() failure\n * does not permanently poison the cache — the next request retries.\n */\nasync function getOrEvalPaths(\n memo: Map<string, Promise<unknown[]>>,\n routeKey: string,\n pathsFn: () => unknown[] | Promise<unknown[]>,\n): Promise<unknown[]> {\n const existing = memo.get(routeKey);\n if (existing !== undefined) {\n return existing;\n }\n const promise = Promise.resolve(pathsFn()).then(\n (v) => v as unknown[],\n (err) => {\n // Evict on rejection so the next request retries.\n memo.delete(routeKey);\n return Promise.reject(err) as Promise<unknown[]>;\n },\n );\n memo.set(routeKey, promise);\n return promise;\n}\n\n/**\n * Build a page router for the SSG-first architecture (ADR-005).\n *\n * Side effects:\n * 1. Registers `opts.contentSnapshot` with the `zfb/content` module so\n * `getCollection(name)` resolves from memory. Idempotent across\n * calls — the latest snapshot wins (matches the documented dev-mode\n * live-reload contract).\n * 2. Constructs an internal Hono app and registers a GET handler per\n * `pages[i].route`. The handler imports the page module, calls\n * `framework.renderToString(module.default({}))`, and returns the\n * string in a `Response` with the appropriate `Content-Type`.\n *\n * Per-route `componentInput` (the object passed to `module.default(...)`) is\n * derived from the route pattern and the page module's exports:\n * - Dynamic route + `paths()` export: match the URL params against the\n * `paths()` entries and pass `{ params, ...props }` (404 on no match).\n * - Static route + `getStaticProps()` export: pass the returned `props`.\n * - Dynamic route with NO `paths()` export (e.g. a per-request SSR page\n * whose slugs can't be enumerated ahead of time): pass `{ params:\n * urlParams }` so the component still knows which URL params it is\n * serving, rather than being invoked with `{}`.\n * - Anything else (static route, no `getStaticProps`): pass `{}`.\n *\n * The returned function is a plain `(request) => Promise<Response>` so a\n * Worker entry point can `export default { fetch: createPageRouter(...) }`\n * directly.\n */\nexport function createPageRouter(opts: CreatePageRouterOptions): PageRouter {\n setContentSnapshot(opts.contentSnapshot);\n\n const app = new Hono();\n\n // Build a lookup map: Hono route pattern → PageDefinition, for the\n // `__paths__` synthetic endpoint below. The map is keyed on the `route`\n // string exactly as the caller supplied it (e.g. \"/blog/:slug\").\n const pagesByRoute = new Map<string, PageDefinition>();\n for (const page of opts.pages) {\n pagesByRoute.set(page.route, page);\n }\n\n // Per-router-instance memo for paths() results.\n //\n // Keyed on page.route (e.g. \"/blog/:slug\"). Stores the in-flight or\n // settled Promise so that concurrent requests share one evaluation.\n // Rejections are NOT cached: if paths() throws the Promise is removed\n // from the map so the next request retries rather than re-propagating\n // the same error indefinitely.\n //\n // Production SSR isolates evaluate paths() once per isolate, which\n // matches the documented build-time-enumerator contract. Dev mode is\n // safe because each re-bundle creates a fresh router instance\n // (dev.rs reload_renderer), so stale paths() results never persist\n // across a file-save cycle. Watch-time invalidation is explicitly\n // out of scope per issue #507.\n const pathsMemo = new Map<string, Promise<unknown[]>>();\n\n // Sanity check: a user-authored route that happens to start with\n // `/__paths__` (or a top-level catchall like `/:slug{.+}`) would\n // shadow the synthetic endpoint registered below if Hono dispatched\n // by registration order. We register `/__paths__/:routeKey{.+}` first\n // (see directly below) but still warn on collisions so users do not\n // ship a page that conflicts with the build pipeline's wire format.\n for (const page of opts.pages) {\n if (routeShadowsPathsEndpoint(page.route)) {\n // Use console.warn so the message reaches the host's tail logs\n // without bringing down the worker. The build pipeline's\n // /__paths__ requests still resolve correctly because the\n // synthetic handler is registered first.\n // eslint-disable-next-line no-console\n console.warn(\n `[zfb-runtime] route \"${page.route}\" may shadow the synthetic /__paths__ endpoint; rename the page or use a more specific pattern`,\n );\n }\n }\n\n // -------------------------------------------------------------------------\n // Synthetic `/__paths__/<encoded-route-key>` endpoint.\n //\n // Called by the Rust build pipeline (crates/zfb/src/render_pipeline.rs)\n // to evaluate non-literal `paths()` exports at runtime. The route key is\n // the Hono pattern for the page (e.g. `/blog/:slug`) percent-encoded so\n // it survives in the URL path segment. The response is a JSON array of\n // `{ params, props? }` objects identical to the `paths()` contract.\n //\n // Pattern: `/__paths__/:routeKey{.+}` — the `{.+}` quantifier (Hono's\n // regex-segment syntax) allows slashes inside the route key so\n // `/blog/:slug` decodes correctly from `/__paths__/%2Fblog%2F%3Aslug`.\n //\n // IMPORTANT: this handler is registered BEFORE user routes so a\n // user-authored top-level catchall (e.g. `/:wildcard{.+}`) cannot\n // shadow it — Hono dispatches in registration order.\n // -------------------------------------------------------------------------\n app.get(\"/__paths__/:routeKey{.+}\", async (c) => {\n // Hono's `c.req.param(\"routeKey\")` already URL-decodes the captured\n // segment when it contains a `%`, so a single decode is correct\n // here — no explicit `decodeURIComponent` (that would be a\n // double-decode and break literal `%` characters in route keys).\n const routeKey = c.req.param(\"routeKey\");\n\n const page = pagesByRoute.get(routeKey);\n if (!page) {\n return c.body(\n `[zfb-runtime] /__paths__: no page registered for route key \"${routeKey}\"`,\n 404,\n { \"Content-Type\": \"text/plain; charset=utf-8\" },\n );\n }\n\n const mod = await page.module();\n if (typeof mod.paths !== \"function\") {\n return c.body(\n `[zfb-runtime] /__paths__: page module for \"${routeKey}\" has no paths() export`,\n 404,\n { \"Content-Type\": \"text/plain; charset=utf-8\" },\n );\n }\n\n let result: unknown[];\n try {\n result = await getOrEvalPaths(pathsMemo, routeKey, mod.paths);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return c.body(`[zfb-runtime] /__paths__: paths() threw for \"${routeKey}\": ${msg}`, 500, {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n });\n }\n\n return c.body(JSON.stringify(result), 200, {\n \"Content-Type\": \"application/json; charset=utf-8\",\n });\n });\n\n for (const page of opts.pages) {\n // `app.all` (vs `app.get`) so SSR routes whose page handler dispatches\n // by `request.method` (e.g. POST API endpoints like\n // `pages/api/*.tsx`) actually reach the handler. The handler is then\n // responsible for returning a method-appropriate status (e.g. 405 for\n // an unsupported verb). With `app.get` the inner router would 404\n // before the handler ever ran, leaving e.g. `POST /api/foo` looking\n // identical to a missing route.\n app.all(page.route, async (c) => {\n const mod = await page.module();\n if (typeof mod.default !== \"function\") {\n // Surface as a 500 with a well-known message rather than letting\n // Hono swallow the error into a generic body. T6's embedded V8\n // host log-tail / source-map plumbing is what eventually projects\n // page-side errors back to the user's TSX line; until then the\n // pinned message is the contract this layer ships.\n return c.body(\n `[zfb-runtime] page module for \"${page.route}\" did not export a default component`,\n 500,\n { \"Content-Type\": \"text/plain; charset=utf-8\" },\n );\n }\n\n // For dynamic routes that export `paths()`, look up the concrete\n // entry for this URL by matching the URL params against the\n // paths() results. This implements the ADR-002 contract:\n // paths() → [{ params, props? }]\n // render(url) → find matching entry → pass { params, props } to default()\n //\n // For static routes (no `paths()` export, no URL params), we pass\n // an empty object — the component signature has no required props.\n // For dynamic routes whose URL params do not match any paths()\n // entry, we return a 404 rather than rendering with empty props.\n //\n // Whether the route is dynamic is derived from the ROUTE PATTERN,\n // not from the captured params: for an optional catchall\n // (`/docs/:slug{.+}?`), Hono matches the bare `/docs` with NO\n // params captured at all, so `Object.keys(c.req.param())` would\n // be empty and the old gate skipped paths() entirely — rendering\n // with `{}` instead of the entry whose `slug` is `[]`.\n const declaredParams = routeParamSpecs(page.route);\n const rawUrlParams = c.req.param() ?? {};\n const urlParams: Record<string, string> = {};\n for (const [k, v] of Object.entries(rawUrlParams)) {\n // Unmatched optional params may surface as undefined — drop them\n // so \"param absent\" is represented uniformly.\n if (typeof v === \"string\") urlParams[k] = v;\n }\n const isDynamicRoute = declaredParams.length > 0;\n\n let componentInput: Record<string, unknown> = {};\n\n if (isDynamicRoute && typeof mod.paths === \"function\") {\n let pathsResult: unknown;\n try {\n pathsResult = await getOrEvalPaths(pathsMemo, page.route, mod.paths);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return c.body(`[zfb-runtime] paths() threw for \"${page.route}\": ${msg}`, 500, {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n });\n }\n\n if (!Array.isArray(pathsResult)) {\n return c.body(`[zfb-runtime] paths() for \"${page.route}\" did not return an array`, 500, {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n });\n }\n\n // Validate each entry's shape per-entry rather than using a bare\n // cast — matches the strictness the Rust pipeline applies on the\n // same wire format. Any malformed entry surfaces as a 500.\n for (const entry of pathsResult) {\n if (!isPathsEntry(entry)) {\n return c.body(\n `[zfb-runtime] paths() for \"${page.route}\" returned an entry without a valid params object`,\n 500,\n { \"Content-Type\": \"text/plain; charset=utf-8\" },\n );\n }\n }\n\n // Find the entry whose params match the URL params for this\n // request. For catchall params (e.g. slug for\n // /docs/[...slug]), Hono returns a slash-joined string (e.g.\n // \"guides/install\"), so we compare against the paths() entry's\n // params.slug joined with \"/\".\n //\n // Matching iterates the params DECLARED by the route pattern so\n // the zero-segment optional-catchall case is covered: when Hono\n // matched `/docs` for `/docs/:slug{.+}?` no `slug` param exists\n // in the URL, and the matching entry is the one whose param is\n // the explicit empty array (`{ slug: [] }`).\n const match = (pathsResult as PathsEntry[]).find((entry) => {\n return declaredParams.every(({ name, optionalCatchall }) => {\n const urlVal = urlParams[name];\n const paramVal = entry.params[name];\n if (urlVal === undefined) {\n // Param absent from the URL: only valid for an optional\n // catchall, and only against the explicit `[]` entry.\n return optionalCatchall && Array.isArray(paramVal) && paramVal.length === 0;\n }\n if (Array.isArray(paramVal)) {\n // catchall: paths() emits slug as string[] but Hono\n // provides it as a \"/\"-joined string\n return paramVal.join(\"/\") === urlVal;\n }\n return String(paramVal) === urlVal;\n });\n });\n\n if (!match) {\n // The URL params do not correspond to any paths() entry —\n // this is the dev-mode equivalent of a build-time miss.\n // Hono's `c.notFound()` returns the framework's default 404,\n // which is cleaner than fabricating an empty-props render.\n return c.notFound();\n }\n\n // Pass the paths() entry's props directly as component props\n // (spread to top level, matching the Astro/zfb convention).\n // Also include `params` so components can access URL params if\n // needed, but individual prop keys from `props` win on collision.\n componentInput = {\n params: match.params,\n ...(match.props ?? {}),\n };\n } else if (isDynamicRoute) {\n // Dynamic route with no `paths()` export: the natural shape for a\n // per-request SSR page whose slugs can't be enumerated ahead of\n // time. Neither branch above applies (no paths() to match against,\n // and getStaticProps only fires for static routes), so without this\n // the component would be invoked with `{}` — no params, no\n // diagnostic, and no way to know which slug it is serving. Pass the\n // URL params through directly instead.\n componentInput = { params: urlParams };\n } else if (!isDynamicRoute && typeof mod.getStaticProps === \"function\") {\n // Static route with `getStaticProps`: call it to fetch build-time\n // data and pass the returned props to the default component. This\n // supports the `export async function getStaticProps()` pattern\n // used by static pages that need to query content collections (e.g.\n // a homepage listing all blog posts via `getCollection(\"blog\")`).\n let staticPropsResult: unknown;\n try {\n staticPropsResult = await mod.getStaticProps();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return c.body(`[zfb-runtime] getStaticProps() threw for \"${page.route}\": ${msg}`, 500, {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n });\n }\n if (\n typeof staticPropsResult !== \"object\" ||\n staticPropsResult === null ||\n !(\"props\" in staticPropsResult)\n ) {\n return c.body(\n `[zfb-runtime] getStaticProps() for \"${page.route}\" must return { props: {...} }`,\n 500,\n { \"Content-Type\": \"text/plain; charset=utf-8\" },\n );\n }\n componentInput = (staticPropsResult as { props: Record<string, unknown> }).props;\n }\n\n // `await` so async page modules (e.g. API routes typed as\n // `(): Promise<Response>`) resolve before we inspect the value.\n // For sync pages that return a VNode/string the await is a no-op.\n //\n // Wrapped in a try/catch so that any error thrown by the component\n // or by renderToString surfaces as a descriptive 500 rather than\n // escaping to Hono's generic error handler (which discards the real\n // message). Mirrors the getStaticProps catch above.\n try {\n const result = await mod.default(componentInput);\n // API route short-circuit: a page module that returns a Response\n // directly (e.g. `pages/api/*.tsx` handlers that use Web Fetch\n // primitives instead of returning JSX) is responsible for its own\n // status, headers, and body — return it as-is rather than running\n // it through the framework SSR path. A `return` from inside a\n // `try` does not trigger the `catch`, so this passes through\n // correctly without special-casing.\n if (result instanceof Response) {\n return result;\n }\n // Non-HTML routes (e.g. `sitemap.xml.tsx`, `feed.xml.tsx`) commonly\n // return their body as a pre-serialised `string` instead of a\n // VNode. Routing those through `framework.renderToString` would\n // HTML-escape the angle brackets and ampersands, producing\n // garbage XML. Pass strings through verbatim; only wrap actual\n // VNodes.\n const html = typeof result === \"string\" ? result : opts.framework.renderToString(result);\n const contentType = mod.contentType ?? DEFAULT_CONTENT_TYPE;\n return c.body(ensureHtml5Doctype(html, contentType), 200, { \"Content-Type\": contentType });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n // __zfb.ssrDebug is set only by the embedded V8 build/dev host\n // (globals_shim.js), never by the production Workers runtime.\n const includeStack =\n opts.includeErrorStack ??\n (globalThis as { __zfb?: { ssrDebug?: boolean } }).__zfb?.ssrDebug === true;\n const stack = includeStack && err instanceof Error && err.stack ? `\\n${err.stack}` : \"\";\n return c.body(`[zfb-runtime] render threw for \"${page.route}\": ${msg}${stack}`, 500, {\n \"Content-Type\": \"text/plain; charset=utf-8\",\n });\n }\n });\n }\n\n // Hono's `app.fetch` returns `Response | Promise<Response>`. The\n // public router contract is unconditionally async; using an `async`\n // wrapper (rather than `Promise.resolve(...)`) ensures any\n // synchronous throw inside `app.fetch` is converted to a rejected\n // promise instead of escaping the caller's `await`.\n return async (request) => await app.fetch(request);\n}\n\n/**\n * Shape of one entry produced by a page's `paths()` export. The\n * `params` object is required (every entry needs to identify the URL\n * it represents); `props` is optional (the page may render purely from\n * the URL params).\n */\ninterface PathsEntry {\n readonly params: Record<string, unknown>;\n readonly props?: Record<string, unknown>;\n}\n\n/**\n * Type guard for one `paths()` entry. Mirrors the strictness of the\n * Rust pipeline's `paths()` resolver — every entry must be a non-null\n * object whose `params` is itself a non-null object.\n */\nfunction isPathsEntry(x: unknown): x is PathsEntry {\n if (typeof x !== \"object\" || x === null) return false;\n const params = (x as { params?: unknown }).params;\n return typeof params === \"object\" && params !== null;\n}\n\n/**\n * One param declared by a Hono route pattern. `optionalCatchall` is\n * `true` for the `:name{.+}?` form (file-system `[[...name]]`), whose\n * zero-segment match captures no param at all.\n */\ninterface RouteParamSpec {\n readonly name: string;\n readonly optionalCatchall: boolean;\n}\n\n/**\n * Parse the params declared by a Hono route pattern (the `route` string\n * the build pipeline registers, e.g. `/blog/:slug`, `/docs/:slug{.+}`,\n * `/docs/:slug{.+}?`). Static routes return an empty list.\n *\n * Only the pattern shapes emitted by `bracket_to_hono` (zfb-build) are\n * recognised — `:name`, `:name{.+}`, `:name{.+}?` — which is the entire\n * grammar the file router produces.\n */\nfunction routeParamSpecs(route: string): RouteParamSpec[] {\n const specs: RouteParamSpec[] = [];\n for (const seg of route.split(\"/\")) {\n if (!seg.startsWith(\":\")) continue;\n const optionalCatchall = seg.endsWith(\"?\");\n const body = optionalCatchall ? seg.slice(1, -1) : seg.slice(1);\n const braceIdx = body.indexOf(\"{\");\n const name = braceIdx === -1 ? body : body.slice(0, braceIdx);\n if (name.length > 0) {\n specs.push({ name, optionalCatchall });\n }\n }\n return specs;\n}\n\n/**\n * Heuristic check for user-authored routes that may shadow the\n * synthetic `/__paths__/<encoded-route-key>` endpoint.\n *\n * Returns true when the route literal contains `/__paths__` (an\n * obvious collision) or when its first segment is a Hono catchall\n * (`:name{.+}`, optionally `:name{.+}?`) or a file-system catchall\n * (`[...name]` / `[[...name]]`) at the root — those would match\n * `/__paths__/...` once Hono dispatches to them.\n */\nfunction routeShadowsPathsEndpoint(route: string): boolean {\n if (route.includes(\"/__paths__\")) {\n return true;\n }\n // Strip the leading slash and look at the first segment only —\n // anything deeper cannot match `/__paths__` because the literal\n // first segment differs.\n const firstSeg = route.replace(/^\\/+/, \"\").split(\"/\")[0] ?? \"\";\n // Hono regex-quantifier catchall: `:name{.+}` / optional `:name{.+}?`.\n if (/^:[A-Za-z_][\\w]*\\{\\.[+*]\\}\\??$/.test(firstSeg)) {\n return true;\n }\n // File-system catchall (pre-bracket-to-hono): `[...name]` / `[[...name]]`.\n if (/^\\[\\.\\.\\.[A-Za-z_][\\w]*\\]$/.test(firstSeg)) {\n return true;\n }\n if (/^\\[\\[\\.\\.\\.[A-Za-z_][\\w]*\\]\\]$/.test(firstSeg)) {\n return true;\n }\n return false;\n}\n"]}
|