vinojs 0.1.3 → 0.2.0
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 +23 -18
- package/dist/client.d.mts +74 -8
- package/dist/client.d.mts.map +1 -1
- package/dist/client.mjs +194 -53
- package/dist/client.mjs.map +1 -1
- package/dist/component-wrap.mjs +1 -1
- package/dist/config-CXHX9qZs.mjs +17 -0
- package/dist/config-CXHX9qZs.mjs.map +1 -0
- package/dist/{constants-BVT2bJEd.mjs → constants-DyXAsId-.mjs} +2 -3
- package/dist/constants-DyXAsId-.mjs.map +1 -0
- package/dist/definePage-DC6gJKwW.mjs +468 -0
- package/dist/definePage-DC6gJKwW.mjs.map +1 -0
- package/dist/{defineServerFn-H-bz7xBX.mjs → defineServerFn-DH6B99bH.mjs} +2 -17
- package/dist/defineServerFn-DH6B99bH.mjs.map +1 -0
- package/dist/factory-0LjmIY5F.mjs +123 -0
- package/dist/factory-0LjmIY5F.mjs.map +1 -0
- package/dist/factory.d.mts +38 -2
- package/dist/factory.d.mts.map +1 -0
- package/dist/factory.mjs +4 -132
- package/dist/hydration.d.mts +3 -0
- package/dist/hydration.mjs +2 -0
- package/dist/jsx-dev-runtime.client.d.mts +2 -0
- package/dist/jsx-dev-runtime.client.mjs +2 -0
- package/dist/jsx-dev-runtime.d.mts +2 -0
- package/dist/jsx-dev-runtime.mjs +2 -0
- package/dist/jsx-runtime-5OIvn_Qa.mjs +335 -0
- package/dist/jsx-runtime-5OIvn_Qa.mjs.map +1 -0
- package/dist/jsx-runtime-BLN--agM.d.mts +20 -0
- package/dist/jsx-runtime-BLN--agM.d.mts.map +1 -0
- package/dist/jsx-runtime.client.d.mts +2 -0
- package/dist/jsx-runtime.client.mjs +2 -0
- package/dist/jsx-runtime.d.mts +2 -0
- package/dist/jsx-runtime.mjs +2 -0
- package/dist/mount.d.mts +17 -0
- package/dist/mount.d.mts.map +1 -0
- package/dist/mount.mjs +107 -0
- package/dist/mount.mjs.map +1 -0
- package/dist/render-Cif1RZMC.d.mts +169 -0
- package/dist/render-Cif1RZMC.d.mts.map +1 -0
- package/dist/{types-CQITdkpF.d.mts → types-jfsZLl9U.d.mts} +10 -8
- package/dist/types-jfsZLl9U.d.mts.map +1 -0
- package/dist/vino.d.mts +1 -37
- package/dist/vino.d.mts.map +1 -1
- package/dist/vino.mjs +2 -208
- package/dist/vite.d.mts +1 -1
- package/dist/vite.d.mts.map +1 -1
- package/dist/vite.mjs +175 -40
- package/dist/vite.mjs.map +1 -1
- package/package.json +29 -6
- package/dist/compile-BGVCPc4H.mjs +0 -111
- package/dist/compile-BGVCPc4H.mjs.map +0 -1
- package/dist/constants-BVT2bJEd.mjs.map +0 -1
- package/dist/definePage-CcqUZAiy.mjs +0 -159
- package/dist/definePage-CcqUZAiy.mjs.map +0 -1
- package/dist/defineServerFn-H-bz7xBX.mjs.map +0 -1
- package/dist/factory-Dxz3nh-p.d.mts +0 -182
- package/dist/factory-Dxz3nh-p.d.mts.map +0 -1
- package/dist/factory.mjs.map +0 -1
- package/dist/types-CQITdkpF.d.mts.map +0 -1
- package/dist/vino.mjs.map +0 -1
package/dist/client.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.mjs","names":[],"sources":["../src/client/create-client.ts"],"sourcesContent":["import { COMPONENT_TAG, NAV_HEADER, PAGE_SLOT_ID, PAYLOAD_SCRIPT_ID } from \"../vino/constants.ts\";\nimport { resolvePageRender } from \"../vino/definePage.ts\";\nimport type { VinoPayload } from \"../vino/types.ts\";\n\nexport interface CreateClientOptions {\n hydrate?: (vnode: unknown, el: Element) => void | Promise<void>;\n createElement?: (type: unknown, props: unknown) => unknown;\n}\n\ntype ComponentLoaders = Record<string, () => Promise<Record<string, unknown>>>;\ntype ClientPageLoaders = Record<string, () => Promise<{ default?: unknown }>>;\n\ninterface DomRoot {\n render: (vnode: unknown) => void;\n unmount: () => void;\n}\n\ninterface IslandRecord {\n id: string;\n exportName: string;\n props: Record<string, unknown>;\n root: DomRoot | null;\n}\n\nexport type VinoHmrPayload =\n | { kind: \"component\"; id: string; mod?: Record<string, unknown> }\n | { kind: \"client-page\"; id: string; mod?: { default?: unknown } }\n | { kind: \"ssr\" }\n | { kind: \"layout\" }\n | { kind: \"registry\"; components?: ComponentLoaders; clientPages?: ClientPageLoaders };\n\ntype HmrFn = (payload: VinoHmrPayload) => void;\n\nconst islands = new Map<Element, IslandRecord>();\nlet pageRoot: { el: Element; path: string; root: DomRoot | null } | null = null;\nlet componentLoaders: ComponentLoaders | undefined;\nlet clientPageLoaders: ClientPageLoaders | undefined;\nlet hmrListener: HmrFn | undefined;\nconst hmrPending: VinoHmrPayload[] = [];\n\nconst hmrGlobal = globalThis as typeof globalThis & { __VINO_HMR__?: HmrFn };\n\nfunction dispatchHmr(payload: VinoHmrPayload) {\n if (hmrListener) hmrListener(payload);\n else hmrPending.push(payload);\n}\n\nhmrGlobal.__VINO_HMR__ = dispatchHmr;\n\nfunction bindHmr(fn: HmrFn) {\n hmrListener = fn;\n const queued = hmrPending.splice(0);\n for (const payload of queued) fn(payload);\n}\n\nfunction pruneDisconnected() {\n for (const [el, rec] of islands) {\n if (el.isConnected) continue;\n rec.root?.unmount();\n islands.delete(el);\n }\n if (pageRoot && !pageRoot.el.isConnected) {\n pageRoot.root?.unmount();\n pageRoot = null;\n }\n}\n\nfunction readPayload(root: ParentNode = document): VinoPayload | null {\n const el = root.querySelector(`#${PAYLOAD_SCRIPT_ID}`);\n if (!el?.textContent) return null;\n try {\n return JSON.parse(el.textContent) as VinoPayload;\n } catch {\n return null;\n }\n}\n\nfunction writePayload(payload: VinoPayload) {\n let el = document.getElementById(PAYLOAD_SCRIPT_ID) as HTMLScriptElement | null;\n if (!el) {\n el = document.createElement(\"script\");\n el.id = PAYLOAD_SCRIPT_ID;\n el.type = \"application/json\";\n document.body.appendChild(el);\n }\n el.textContent = JSON.stringify(payload).replaceAll(\"<\", \"\\\\u003c\");\n}\n\nasync function loadDom() {\n const [{ hydrateRoot }, { createElement }] = await Promise.all([\n import(\"hono/jsx/dom/client\"),\n import(\"hono/jsx/dom\"),\n ]);\n return { hydrateRoot, createElement };\n}\n\nasync function getComponentLoaders(): Promise<ComponentLoaders> {\n componentLoaders ??= (await import(\"virtual:vino/components\")).components;\n return componentLoaders;\n}\n\nasync function getClientPageLoaders(): Promise<ClientPageLoaders> {\n clientPageLoaders ??= (await import(\"virtual:vino/client-pages\")).clientPages;\n return clientPageLoaders;\n}\n\nfunction vnodeFor(\n options: CreateClientOptions,\n createElement: (type: never, props: never) => unknown,\n type: unknown,\n props: unknown,\n) {\n return options.createElement\n ? options.createElement(type, props)\n : createElement(type as never, props as never);\n}\n\nasync function mountVnode(\n options: CreateClientOptions,\n el: Element,\n vnode: unknown,\n existing: DomRoot | null,\n): Promise<DomRoot | null> {\n if (existing) {\n existing.render(vnode);\n return existing;\n }\n if (options.hydrate) {\n await options.hydrate(vnode, el);\n return null;\n }\n const { hydrateRoot } = await loadDom();\n return hydrateRoot(el as HTMLElement, vnode as never) as DomRoot;\n}\n\nasync function hydrateComponents(options: CreateClientOptions, root: ParentNode = document) {\n const components = await getComponentLoaders();\n const { createElement } = await loadDom();\n const nodes = root.querySelectorAll(COMPONENT_TAG);\n for (const el of nodes) {\n const id = el.getAttribute(\"data-component\");\n const exportName = el.getAttribute(\"data-export\") ?? \"default\";\n const propsJson = el.getAttribute(\"data-props\") ?? \"{}\";\n if (!id) continue;\n const loader = components[id];\n if (!loader) {\n console.warn(`[vino] missing hydratable component ${id}`);\n continue;\n }\n const mod = await loader();\n const Component = mod[exportName];\n if (typeof Component !== \"function\") continue;\n const props = JSON.parse(propsJson) as Record<string, unknown>;\n const vnode = vnodeFor(options, createElement, Component, props);\n const previous = islands.get(el);\n if (previous?.root) previous.root.unmount();\n const mounted = await mountVnode(options, el, vnode, null);\n islands.set(el, { id, exportName, props, root: mounted });\n }\n}\n\nasync function hydrateClientPage(payload: VinoPayload, options: CreateClientOptions) {\n if (!payload.clientPage) return;\n const root = document.getElementById(PAGE_SLOT_ID);\n if (!root) return;\n const clientPages = await getClientPageLoaders();\n const load = clientPages[payload.clientPage];\n if (!load) return;\n const mod = await load();\n await renderClientPage(payload.clientPage, mod, payload, options, root);\n}\n\nasync function renderClientPage(\n path: string,\n mod: { default?: unknown },\n payload: VinoPayload,\n options: CreateClientOptions,\n root: Element,\n) {\n const Page = resolvePageRender(mod.default);\n if (!Page) return;\n const { createElement } = await loadDom();\n const props = { data: payload.data, params: payload.params };\n const vnode = vnodeFor(options, createElement, Page, props);\n const reuse = pageRoot?.el === root ? pageRoot.root : null;\n if (pageRoot && pageRoot.el !== root) pageRoot.root?.unmount();\n const mounted = await mountVnode(options, root, vnode, reuse);\n pageRoot = { el: root, path, root: mounted };\n}\n\nasync function patchComponent(id: string, mod: Record<string, unknown>, options: CreateClientOptions) {\n const { createElement } = await loadDom();\n for (const [el, rec] of islands) {\n if (rec.id !== id) continue;\n if (!el.isConnected) {\n rec.root?.unmount();\n islands.delete(el);\n continue;\n }\n const Component = mod[rec.exportName];\n if (typeof Component !== \"function\") continue;\n const vnode = vnodeFor(options, createElement, Component, rec.props);\n rec.root = await mountVnode(options, el, vnode, rec.root);\n }\n}\n\nasync function patchClientPage(\n path: string,\n mod: { default?: unknown },\n options: CreateClientOptions,\n) {\n const payload = readPayload();\n if (!payload?.clientPage || payload.clientPage !== path) return;\n const root = document.getElementById(PAGE_SLOT_ID);\n if (!root) return;\n await renderClientPage(path, mod, payload, options, root);\n}\n\nfunction syncNav(path: string) {\n const links = document.querySelectorAll(\"nav a[href]\");\n for (const link of links) {\n if (!(link instanceof HTMLAnchorElement)) continue;\n const href = link.getAttribute(\"href\");\n if (!href || href.startsWith(\"http\")) continue;\n const active =\n href === path || (href !== \"/\" && (path === href || path.startsWith(`${href}/`)));\n if (active) link.setAttribute(\"aria-current\", \"page\");\n else link.removeAttribute(\"aria-current\");\n }\n}\n\nasync function hydrateSlot(options: CreateClientOptions, payload: VinoPayload | null) {\n pruneDisconnected();\n if (payload) await hydrateClientPage(payload, options);\n const slot = document.getElementById(PAGE_SLOT_ID);\n await hydrateComponents(options, slot ?? document);\n if (payload?.path) syncNav(payload.path);\n return payload;\n}\n\nfunction parseContentFragment(html: string): {\n slot: Element | null;\n payload: VinoPayload | null;\n title?: string;\n} {\n const doc = new DOMParser().parseFromString(\n `<!DOCTYPE html><html><body>${html}</body></html>`,\n \"text/html\",\n );\n const payload = readPayload(doc);\n const slot = doc.getElementById(PAGE_SLOT_ID);\n return { slot, payload, title: payload?.title };\n}\n\nasync function fetchContent(\n href: string,\n): Promise<{ slot: Element | null; payload: VinoPayload | null; title?: string }> {\n const url = new URL(href, location.href);\n const res = await fetch(url.href, { headers: { [NAV_HEADER]: \"1\" } });\n const html = await res.text();\n return parseContentFragment(html);\n}\n\nasync function applyContent(\n href: string,\n push: boolean,\n options: CreateClientOptions,\n fragment: { slot: Element | null; payload: VinoPayload | null; title?: string },\n) {\n const { slot: nextSlot, payload: nextPayload, title } = fragment;\n if (nextPayload?.clientRouting === false && push) {\n location.href = href;\n return nextPayload;\n }\n\n const current = document.getElementById(PAGE_SLOT_ID);\n if (current && nextSlot) {\n current.replaceWith(nextSlot);\n } else if (nextSlot) {\n document.body.appendChild(nextSlot);\n }\n\n if (nextPayload) writePayload(nextPayload);\n if (title) document.title = title;\n else if (nextPayload?.title) document.title = nextPayload.title;\n\n const url = new URL(href, location.href);\n if (push) history.pushState({ vino: true }, \"\", url.href);\n\n return hydrateSlot(options, nextPayload ?? readPayload());\n}\n\nasync function navigate(href: string, push: boolean, options: CreateClientOptions) {\n const fragment = await fetchContent(href);\n return applyContent(href, push, options, fragment);\n}\n\nasync function fillPartialShell(options: CreateClientOptions, payload: VinoPayload | null) {\n if (!payload?.partial) return payload;\n const slot = document.getElementById(PAGE_SLOT_ID);\n if (!slot) return payload;\n if (slot.childNodes.length > 0) return hydrateSlot(options, { ...payload, partial: false });\n\n const fragment = await fetchContent(location.href);\n return applyContent(location.href, false, options, fragment);\n}\n\nfunction bindRouting(options: CreateClientOptions, payload: VinoPayload | null) {\n if (!payload?.clientRouting) return;\n document.addEventListener(\"click\", (event) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n const anchor = target.closest(\"a\");\n if (!anchor || !shouldIntercept(anchor) || isModifiedClick(event)) return;\n // Prefer slot-swap when the page slot exists.\n if (!document.getElementById(PAGE_SLOT_ID)) return;\n event.preventDefault();\n void navigate(anchor.href, true, options);\n });\n window.addEventListener(\"popstate\", () => {\n void navigate(location.href, false, options);\n });\n}\n\nfunction isModifiedClick(event: MouseEvent) {\n return event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0;\n}\n\nfunction shouldIntercept(anchor: HTMLAnchorElement) {\n if (anchor.hasAttribute(\"download\") || (anchor.target && anchor.target !== \"_self\")) return false;\n if (anchor.origin !== location.origin) return false;\n const rel = anchor.getAttribute(\"rel\");\n if (rel?.includes(\"external\")) return false;\n return true;\n}\n\nasync function handleHmr(payload: VinoHmrPayload, options: CreateClientOptions) {\n if (payload.kind === \"registry\") {\n if (payload.components) componentLoaders = payload.components;\n if (payload.clientPages) clientPageLoaders = payload.clientPages;\n return;\n }\n if (payload.kind === \"layout\") {\n location.reload();\n return;\n }\n if (payload.kind === \"ssr\") {\n if (!document.getElementById(PAGE_SLOT_ID)) {\n location.reload();\n return;\n }\n await navigate(location.href, false, options);\n return;\n }\n if (payload.kind === \"component\" && payload.mod) {\n await patchComponent(payload.id, payload.mod, options);\n return;\n }\n if (payload.kind === \"client-page\" && payload.mod) {\n await patchClientPage(payload.id, payload.mod, options);\n }\n}\n\n/**\n * Fill partial shells, hydrate `#vino-page` / `<vino-component>`, and intercept\n * same-origin links with content-only swaps when `clientRouting` is enabled.\n */\nexport function createClient(options: CreateClientOptions = {}) {\n const hmr: HmrFn = (payload) => {\n void handleHmr(payload, options);\n };\n bindHmr(hmr);\n const ready = (async () => {\n let payload = readPayload();\n if (payload?.partial) {\n payload = (await fillPartialShell(options, payload)) ?? payload;\n } else {\n payload = (await hydrateSlot(options, payload)) ?? payload;\n }\n bindRouting(options, payload);\n return payload;\n })();\n return { ready, hmr };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.mjs","names":["isDomNode","toNodes","isDomNode","toNodes"],"sources":["../src/client/runtime/mount.ts","../src/client/create-client.ts","../src/client/runtime/control.ts","../src/client/runtime/control-for.ts","../src/client/runtime/hooks.ts"],"sourcesContent":["import { createRoot } from \"./signal.ts\";\nimport { popHydrateCursor, pushHydrateCursor } from \"./jsx-runtime.ts\";\n\nexport type Mountable = (props: never) => unknown;\n\nexport interface MountRoot {\n render: (component: Mountable, props?: Record<string, unknown>) => void;\n unmount: () => void;\n}\n\nfunction isDomNode(value: unknown): value is Node {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"nodeType\" in value &&\n typeof (value as Node).nodeType === \"number\"\n );\n}\n\nfunction toNodes(value: unknown): Node[] {\n if (value == null || value === false || value === true) return [];\n if (typeof value === \"string\" || typeof value === \"number\") {\n return [document.createTextNode(String(value))];\n }\n if (isDomNode(value)) return [value];\n if (Array.isArray(value)) return value.flatMap(toNodes);\n return [];\n}\n\nfunction elementChildCount(el: Element): number {\n return Array.from(el.childNodes).filter((n) => !(n.nodeType === 3 && !n.textContent?.trim())).length;\n}\n\n/**\n * Hydrate into existing SSR DOM when the slot has a single element child\n * and `jsx()` claims that node. Stock `react-jsx` evaluates nested `jsx()`\n * before the parent, so a mismatch falls back to create-mode instead of\n * keeping an empty claimed root.\n */\nfunction mountInto(el: Element, component: Mountable, props: Record<string, unknown>): void {\n if (elementChildCount(el) === 1) {\n pushHydrateCursor(el);\n try {\n const result = component(props as never);\n const live = toNodes(result)[0];\n const ssr = Array.from(el.childNodes).find((n) => n.nodeType === 1);\n if (live && ssr && live === ssr) return;\n } finally {\n popHydrateCursor();\n }\n }\n\n el.replaceChildren(...toNodes(component(props as never)));\n}\n\n/**\n * Mount a setup-style component into `el`.\n * Prefers hydrating a single SSR root element; otherwise replaces children.\n */\nexport function mountComponent(\n el: Element,\n component: Mountable,\n props: Record<string, unknown> = {},\n): MountRoot {\n let dispose: (() => void) | undefined;\n\n const run = (next: Mountable, nextProps: Record<string, unknown>) => {\n dispose?.();\n createRoot((d) => {\n dispose = d;\n mountInto(el, next, nextProps);\n });\n };\n\n run(component, props);\n\n return {\n render: (next, nextProps) => {\n dispose?.();\n createRoot((d) => {\n dispose = d;\n el.replaceChildren(...toNodes(next((nextProps ?? props) as never)));\n });\n },\n unmount: () => {\n dispose?.();\n dispose = undefined;\n el.replaceChildren();\n },\n };\n}\n\n/** Alias for page slot mounting. */\nexport function hydratePage(\n el: Element,\n page: Mountable,\n props: Record<string, unknown> = {},\n): MountRoot {\n return mountComponent(el, page, props);\n}\n","import { COMPONENT_TAG, NAV_HEADER, PAGE_SLOT_ID, PAYLOAD_SCRIPT_ID } from \"../vino/constants.ts\";\nimport { resolvePageRender } from \"../factory/definePage.ts\";\nimport type { VinoPayload } from \"../hydration/types.ts\";\nimport { hydratePage, type MountRoot } from \"./runtime/mount.ts\";\n\ntype ComponentLoaders = Record<string, () => Promise<Record<string, unknown>>>;\ntype ClientPageLoaders = Record<string, () => Promise<{ default?: unknown }>>;\n\nexport interface CreateClientOptions {\n /** Hydratable component loaders (from Vite `virtual:vino/components`). */\n components?: ComponentLoaders;\n /** Client-page loaders (from Vite `virtual:vino/client-pages`). */\n clientPages?: ClientPageLoaders;\n /** Escape hatch: custom hydrate when not using the signal runtime. */\n hydrate?: (vnode: unknown, el: Element) => void | Promise<void>;\n /** Escape hatch: build a vnode for custom `hydrate` (legacy hono/jsx/dom path). */\n createElement?: (type: unknown, props: unknown) => unknown;\n}\n\ninterface IslandRecord {\n id: string;\n exportName: string;\n props: Record<string, unknown>;\n root: MountRoot | null;\n}\n\nexport type VinoHmrPayload =\n | { kind: \"component\"; id: string; mod?: Record<string, unknown> }\n | { kind: \"client-page\"; id: string; mod?: { default?: unknown } }\n | { kind: \"ssr\" }\n | { kind: \"layout\" }\n | { kind: \"registry\"; components?: ComponentLoaders; clientPages?: ClientPageLoaders };\n\ntype HmrFn = (payload: VinoHmrPayload) => void;\n\nconst islands = new Map<Element, IslandRecord>();\nlet pageRoot: { el: Element; path: string; root: MountRoot | null } | null = null;\nlet componentLoaders: ComponentLoaders | undefined;\nlet clientPageLoaders: ClientPageLoaders | undefined;\nlet hmrListener: HmrFn | undefined;\nconst hmrPending: VinoHmrPayload[] = [];\n\nconst hmrGlobal = globalThis as typeof globalThis & { __VINO_HMR__?: HmrFn };\n\nfunction dispatchHmr(payload: VinoHmrPayload) {\n if (hmrListener) hmrListener(payload);\n else hmrPending.push(payload);\n}\n\nhmrGlobal.__VINO_HMR__ = dispatchHmr;\n\nfunction bindHmr(fn: HmrFn) {\n hmrListener = fn;\n const queued = hmrPending.splice(0);\n for (const payload of queued) fn(payload);\n}\n\nfunction pruneDisconnected() {\n for (const [el, rec] of islands) {\n if (el.isConnected) continue;\n rec.root?.unmount();\n islands.delete(el);\n }\n if (pageRoot && !pageRoot.el.isConnected) {\n pageRoot.root?.unmount();\n pageRoot = null;\n }\n}\n\nfunction readPayload(root: ParentNode = document): VinoPayload | null {\n const el = root.querySelector(`#${PAYLOAD_SCRIPT_ID}`);\n if (!el?.textContent) return null;\n try {\n return JSON.parse(el.textContent) as VinoPayload;\n } catch {\n return null;\n }\n}\n\nfunction writePayload(payload: VinoPayload) {\n let el = document.getElementById(PAYLOAD_SCRIPT_ID) as HTMLScriptElement | null;\n if (!el) {\n el = document.createElement(\"script\");\n el.id = PAYLOAD_SCRIPT_ID;\n el.type = \"application/json\";\n document.body.appendChild(el);\n }\n el.textContent = JSON.stringify(payload).replaceAll(\"<\", \"\\\\u003c\");\n}\n\nfunction getComponentLoaders(): ComponentLoaders {\n return componentLoaders ?? {};\n}\n\nfunction getClientPageLoaders(): ClientPageLoaders {\n return clientPageLoaders ?? {};\n}\n\nasync function mountSetup(\n options: CreateClientOptions,\n el: Element,\n component: (props: never) => unknown,\n props: Record<string, unknown>,\n existing: MountRoot | null,\n): Promise<MountRoot | null> {\n if (options.hydrate) {\n const vnode = options.createElement\n ? options.createElement(component, props)\n : component(props as never);\n await options.hydrate(vnode, el);\n return null;\n }\n\n if (existing) {\n existing.render(component, props);\n return existing;\n }\n\n return hydratePage(el, component, props);\n}\n\nasync function hydrateComponents(options: CreateClientOptions, root: ParentNode = document) {\n const components = getComponentLoaders();\n const nodes = root.querySelectorAll(COMPONENT_TAG);\n for (const el of nodes) {\n const id = el.getAttribute(\"data-component\");\n const exportName = el.getAttribute(\"data-export\") ?? \"default\";\n const propsJson = el.getAttribute(\"data-props\") ?? \"{}\";\n if (!id) continue;\n const loader = components[id];\n if (!loader) {\n console.warn(`[vino] missing hydratable component ${id}`);\n continue;\n }\n const mod = await loader();\n const Component = mod[exportName];\n if (typeof Component !== \"function\") continue;\n const props = JSON.parse(propsJson) as Record<string, unknown>;\n const previous = islands.get(el);\n if (previous?.root) previous.root.unmount();\n const mounted = await mountSetup(options, el, Component as (props: never) => unknown, props, null);\n islands.set(el, { id, exportName, props, root: mounted });\n }\n}\n\nasync function hydrateClientPage(payload: VinoPayload, options: CreateClientOptions) {\n if (!payload.clientPage) return;\n const root = document.getElementById(PAGE_SLOT_ID);\n if (!root) return;\n const clientPages = getClientPageLoaders();\n const load = clientPages[payload.clientPage];\n if (!load) return;\n const mod = await load();\n await renderClientPage(payload.clientPage, mod, payload, options, root);\n}\n\nasync function renderClientPage(\n path: string,\n mod: { default?: unknown },\n payload: VinoPayload,\n options: CreateClientOptions,\n root: Element,\n) {\n const Page = resolvePageRender(mod.default);\n if (!Page) return;\n const props = { data: payload.data, params: payload.params };\n const reuse = pageRoot?.el === root ? pageRoot.root : null;\n if (pageRoot && pageRoot.el !== root) pageRoot.root?.unmount();\n const mounted = await mountSetup(options, root, Page as (props: never) => unknown, props, reuse);\n pageRoot = { el: root, path, root: mounted };\n}\n\nasync function patchComponent(id: string, mod: Record<string, unknown>, options: CreateClientOptions) {\n for (const [el, rec] of islands) {\n if (rec.id !== id) continue;\n if (!el.isConnected) {\n rec.root?.unmount();\n islands.delete(el);\n continue;\n }\n const Component = mod[rec.exportName];\n if (typeof Component !== \"function\") continue;\n rec.root = await mountSetup(\n options,\n el,\n Component as (props: never) => unknown,\n rec.props,\n rec.root,\n );\n }\n}\n\nasync function patchClientPage(\n path: string,\n mod: { default?: unknown },\n options: CreateClientOptions,\n) {\n const payload = readPayload();\n if (!payload?.clientPage || payload.clientPage !== path) return;\n const root = document.getElementById(PAGE_SLOT_ID);\n if (!root) return;\n await renderClientPage(path, mod, payload, options, root);\n}\n\nfunction syncNav(path: string) {\n const links = document.querySelectorAll(\"nav a[href]\");\n for (const link of links) {\n if (!(link instanceof HTMLAnchorElement)) continue;\n const href = link.getAttribute(\"href\");\n if (!href || href.startsWith(\"http\")) continue;\n const active =\n href === path || (href !== \"/\" && (path === href || path.startsWith(`${href}/`)));\n if (active) link.setAttribute(\"aria-current\", \"page\");\n else link.removeAttribute(\"aria-current\");\n }\n}\n\nasync function hydrateSlot(options: CreateClientOptions, payload: VinoPayload | null) {\n pruneDisconnected();\n if (payload) await hydrateClientPage(payload, options);\n const slot = document.getElementById(PAGE_SLOT_ID);\n await hydrateComponents(options, slot ?? document);\n if (payload?.path) syncNav(payload.path);\n return payload;\n}\n\nfunction parseContentFragment(html: string): {\n slot: Element | null;\n payload: VinoPayload | null;\n title?: string;\n} {\n const doc = new DOMParser().parseFromString(\n `<!DOCTYPE html><html><body>${html}</body></html>`,\n \"text/html\",\n );\n const payload = readPayload(doc);\n const slot = doc.getElementById(PAGE_SLOT_ID);\n return { slot, payload, title: payload?.title };\n}\n\nasync function fetchContent(\n href: string,\n): Promise<{ slot: Element | null; payload: VinoPayload | null; title?: string }> {\n const url = new URL(href, location.href);\n const res = await fetch(url.href, { headers: { [NAV_HEADER]: \"1\" } });\n const html = await res.text();\n return parseContentFragment(html);\n}\n\nasync function applyContent(\n href: string,\n push: boolean,\n options: CreateClientOptions,\n fragment: { slot: Element | null; payload: VinoPayload | null; title?: string },\n) {\n const { slot: nextSlot, payload: nextPayload, title } = fragment;\n if (nextPayload?.clientRouting === false && push) {\n location.href = href;\n return nextPayload;\n }\n\n const current = document.getElementById(PAGE_SLOT_ID);\n if (current && nextSlot) {\n current.replaceWith(nextSlot);\n } else if (nextSlot) {\n document.body.appendChild(nextSlot);\n }\n\n if (nextPayload) writePayload(nextPayload);\n if (title) document.title = title;\n else if (nextPayload?.title) document.title = nextPayload.title;\n\n const url = new URL(href, location.href);\n if (push) history.pushState({ vino: true }, \"\", url.href);\n\n return hydrateSlot(options, nextPayload ?? readPayload());\n}\n\nasync function navigate(href: string, push: boolean, options: CreateClientOptions) {\n const fragment = await fetchContent(href);\n return applyContent(href, push, options, fragment);\n}\n\nfunction bindRouting(options: CreateClientOptions, payload: VinoPayload | null) {\n if (!payload?.clientRouting) return;\n document.addEventListener(\"click\", (event) => {\n const target = event.target;\n if (!(target instanceof Element)) return;\n const anchor = target.closest(\"a\");\n if (!anchor || !shouldIntercept(anchor) || isModifiedClick(event)) return;\n if (!document.getElementById(PAGE_SLOT_ID)) return;\n event.preventDefault();\n void navigate(anchor.href, true, options);\n });\n window.addEventListener(\"popstate\", () => {\n void navigate(location.href, false, options);\n });\n}\n\nfunction isModifiedClick(event: MouseEvent) {\n return event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0;\n}\n\nfunction shouldIntercept(anchor: HTMLAnchorElement) {\n if (anchor.hasAttribute(\"download\") || (anchor.target && anchor.target !== \"_self\")) return false;\n if (anchor.origin !== location.origin) return false;\n const rel = anchor.getAttribute(\"rel\");\n if (rel?.includes(\"external\")) return false;\n return true;\n}\n\nasync function handleHmr(payload: VinoHmrPayload, options: CreateClientOptions) {\n if (payload.kind === \"registry\") {\n if (payload.components) componentLoaders = payload.components;\n if (payload.clientPages) clientPageLoaders = payload.clientPages;\n return;\n }\n if (payload.kind === \"layout\") {\n location.reload();\n return;\n }\n if (payload.kind === \"ssr\") {\n if (!document.getElementById(PAGE_SLOT_ID)) {\n location.reload();\n return;\n }\n await navigate(location.href, false, options);\n return;\n }\n if (payload.kind === \"component\" && payload.mod) {\n await patchComponent(payload.id, payload.mod, options);\n return;\n }\n if (payload.kind === \"client-page\" && payload.mod) {\n await patchClientPage(payload.id, payload.mod, options);\n }\n}\n\n/**\n * Hydrate `#vino-page` / `<vino-component>`, and intercept same-origin links\n * with content-only swaps when `clientRouting` is enabled in the payload.\n */\nexport function createClient(options: CreateClientOptions = {}) {\n if (options.components) componentLoaders = options.components;\n if (options.clientPages) clientPageLoaders = options.clientPages;\n const hmr: HmrFn = (payload) => {\n void handleHmr(payload, options);\n };\n bindHmr(hmr);\n const ready = (async () => {\n const payload = (await hydrateSlot(options, readPayload())) ?? readPayload();\n bindRouting(options, payload);\n return payload;\n })();\n return { ready, hmr };\n}\n","import { createEffect, isSignalAccessor, type Accessor } from \"./signal.ts\";\n\nexport type ShowProps = {\n when: boolean | Accessor<boolean> | (() => boolean);\n children?: unknown;\n fallback?: unknown;\n};\n\nfunction readWhen(when: ShowProps[\"when\"]): boolean {\n if (isSignalAccessor(when)) return Boolean(when());\n if (typeof when === \"function\") return Boolean((when as () => boolean)());\n return Boolean(when);\n}\n\nfunction isDomNode(value: unknown): value is Node {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"nodeType\" in value &&\n typeof (value as Node).nodeType === \"number\"\n );\n}\n\nfunction toNodes(value: unknown): Node[] {\n if (value == null || value === false || value === true) return [];\n if (typeof value === \"string\" || typeof value === \"number\") {\n return [document.createTextNode(String(value))];\n }\n if (isDomNode(value)) return [value];\n if (Array.isArray(value)) return value.flatMap(toNodes);\n if (typeof value === \"function\") return toNodes((value as () => unknown)());\n return [];\n}\n\n/**\n * Conditionally mount `children` or `fallback` based on a reactive `when`.\n * On the server (no `document`), returns the active branch for `hono/jsx` SSR.\n */\nexport function Show(props: ShowProps): unknown {\n if (typeof document === \"undefined\") {\n return readWhen(props.when) ? props.children : props.fallback;\n }\n\n const frag = document.createDocumentFragment();\n const start = document.createComment(\"vino-show\");\n const end = document.createComment(\"/vino-show\");\n frag.append(start, end);\n\n let current: Node[] = [];\n\n const clear = () => {\n for (const node of current) node.parentNode?.removeChild(node);\n current = [];\n };\n\n const insert = (nodes: Node[]) => {\n const parent = start.parentNode;\n if (!parent) return;\n for (const node of nodes) parent.insertBefore(node, end);\n current = nodes;\n };\n\n createEffect(() => {\n clear();\n insert(toNodes(readWhen(props.when) ? props.children : props.fallback));\n });\n\n return frag;\n}\n","import { createEffect, isSignalAccessor, type Accessor } from \"./signal.ts\";\n\nexport type ForProps<T> = {\n each: T[] | Accessor<T[]> | (() => T[]);\n children: (item: T, index: number) => unknown;\n};\n\nfunction readEach<T>(each: ForProps<T>[\"each\"]): T[] {\n if (isSignalAccessor(each)) return each() as T[];\n if (typeof each === \"function\") return (each as () => T[])();\n return each;\n}\n\nfunction isDomNode(value: unknown): value is Node {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"nodeType\" in value &&\n typeof (value as Node).nodeType === \"number\"\n );\n}\n\nfunction toNodes(value: unknown): Node[] {\n if (value == null || value === false || value === true) return [];\n if (typeof value === \"string\" || typeof value === \"number\") {\n return [document.createTextNode(String(value))];\n }\n if (isDomNode(value)) return [value];\n if (Array.isArray(value)) return value.flatMap(toNodes);\n if (typeof value === \"function\") return toNodes((value as () => unknown)());\n return [];\n}\n\n/**\n * Reactive list. On the server, maps `each` once for `hono/jsx` SSR.\n */\nexport function For<T>(props: ForProps<T>): unknown {\n if (typeof document === \"undefined\") {\n return readEach(props.each).map((item, i) => props.children(item, i));\n }\n\n const frag = document.createDocumentFragment();\n const start = document.createComment(\"vino-for\");\n const end = document.createComment(\"/vino-for\");\n frag.append(start, end);\n\n let current: Node[] = [];\n\n createEffect(() => {\n for (const node of current) node.parentNode?.removeChild(node);\n current = [];\n const parent = start.parentNode;\n if (!parent) return;\n const nodes = readEach(props.each).flatMap((item, i) => toNodes(props.children(item, i)));\n for (const node of nodes) parent.insertBefore(node, end);\n current = nodes;\n });\n\n return frag;\n}\n","import { createEffect, createSignal, onCleanup, type Accessor, type Setter } from \"./signal.ts\";\n\n/** Signal-backed `useState` compatible with stock JSX (`value={count}`, `{count}` as child). */\nexport function useState<T>(initial: T | (() => T)): [Accessor<T>, Setter<T>] {\n return createSignal(typeof initial === \"function\" ? (initial as () => T)() : initial);\n}\n\n/** Runs `fn` in a reactive effect. Dependency arrays are ignored (Solid-style). */\nexport function useEffect(fn: () => void | (() => void), _deps?: unknown[]): void {\n createEffect(() => {\n const cleanup = fn();\n if (typeof cleanup === \"function\") onCleanup(cleanup);\n });\n}\n\nexport function useRef<T>(initial: T): { current: T } {\n return { current: initial };\n}\n"],"mappings":"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"}
|
package/dist/component-wrap.mjs
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { d as readPageDefinition } from "./definePage-DC6gJKwW.mjs";
|
|
2
|
+
//#region src/vino/config.ts
|
|
3
|
+
function loadMergedConfig(page, pageMod) {
|
|
4
|
+
const merged = { ...page.config };
|
|
5
|
+
const inline = readPageDefinition(pageMod?.default)?.config;
|
|
6
|
+
if (inline) Object.assign(merged, inline);
|
|
7
|
+
return merged;
|
|
8
|
+
}
|
|
9
|
+
async function loadMergedData(_page, c, pageMod) {
|
|
10
|
+
const fn = readPageDefinition(pageMod?.default)?.data;
|
|
11
|
+
if (!fn) return void 0;
|
|
12
|
+
return await fn(c);
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { loadMergedData as n, loadMergedConfig as t };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=config-CXHX9qZs.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-CXHX9qZs.mjs","names":[],"sources":["../src/vino/config.ts"],"sourcesContent":["import { readPageDefinition } from \"../factory/definePage.ts\";\nimport type { DataLoader, ManifestPage, PageConfig, PageModule } from \"../hydration/types.ts\";\n\nexport function loadMergedConfig(page: ManifestPage, pageMod?: PageModule): PageConfig {\n const merged: PageConfig = { ...page.config };\n const inline = readPageDefinition(pageMod?.default)?.config;\n if (inline) Object.assign(merged, inline);\n return merged;\n}\n\nexport async function loadMergedData(\n _page: ManifestPage,\n c: Parameters<DataLoader>[0],\n pageMod?: PageModule,\n): Promise<unknown> {\n const fn = readPageDefinition(pageMod?.default)?.data;\n if (!fn) return undefined;\n return await fn(c);\n}\n"],"mappings":";;AAGA,SAAgB,iBAAiB,MAAoB,SAAkC;CACrF,MAAM,SAAqB,EAAE,GAAG,KAAK,OAAO;CAC5C,MAAM,SAAS,mBAAmB,SAAS,OAAO,CAAC,EAAE;CACrD,IAAI,QAAQ,OAAO,OAAO,QAAQ,MAAM;CACxC,OAAO;AACT;AAEA,eAAsB,eACpB,OACA,GACA,SACkB;CAClB,MAAM,KAAK,mBAAmB,SAAS,OAAO,CAAC,EAAE;CACjD,IAAI,CAAC,IAAI,OAAO,KAAA;CAChB,OAAO,MAAM,GAAG,CAAC;AACnB"}
|
|
@@ -7,7 +7,6 @@ const DEFAULT_LAYOUTS_DIR = "src/layout";
|
|
|
7
7
|
const DEFAULT_COMPONENTS_DIR = "src/components";
|
|
8
8
|
const DEFAULT_ENTRY = "src/server.ts";
|
|
9
9
|
const NAV_HEADER = "X-Vino-Nav";
|
|
10
|
-
const SHELL_HEADER = "X-Vino-Shell";
|
|
11
10
|
const VIRTUAL_MANIFEST = "virtual:vino/manifest";
|
|
12
11
|
const VIRTUAL_COMPONENTS = "virtual:vino/components";
|
|
13
12
|
const VIRTUAL_ASSETS = "virtual:vino/assets";
|
|
@@ -22,6 +21,6 @@ const PAGE_EXTS = [
|
|
|
22
21
|
".mjs"
|
|
23
22
|
];
|
|
24
23
|
//#endregion
|
|
25
|
-
export { DEFAULT_PAGES_DIR as a, PAGE_SLOT_ID as c,
|
|
24
|
+
export { DEFAULT_PAGES_DIR as a, PAGE_SLOT_ID as c, VIRTUAL_CLIENT as d, VIRTUAL_CLIENT_PAGES as f, VIRTUAL_SSR_HOOKS as h, DEFAULT_LAYOUTS_DIR as i, PAYLOAD_SCRIPT_ID as l, VIRTUAL_MANIFEST as m, DEFAULT_COMPONENTS_DIR as n, NAV_HEADER as o, VIRTUAL_COMPONENTS as p, DEFAULT_ENTRY as r, PAGE_EXTS as s, COMPONENT_TAG as t, VIRTUAL_ASSETS as u };
|
|
26
25
|
|
|
27
|
-
//# sourceMappingURL=constants-
|
|
26
|
+
//# sourceMappingURL=constants-DyXAsId-.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants-DyXAsId-.mjs","names":[],"sources":["../src/vino/constants.ts"],"sourcesContent":["export const PAGE_SLOT_ID = \"vino-page\";\nexport const PAYLOAD_SCRIPT_ID = \"__VINO__\";\nexport const COMPONENT_TAG = \"vino-component\";\nexport const DEFAULT_PAGES_DIR = \"src/pages\";\nexport const DEFAULT_LAYOUTS_DIR = \"src/layout\";\nexport const DEFAULT_COMPONENTS_DIR = \"src/components\";\nexport const DEFAULT_ENTRY = \"src/server.ts\";\nexport const NAV_HEADER = \"X-Vino-Nav\";\n\nexport const VIRTUAL_MANIFEST = \"virtual:vino/manifest\";\nexport const VIRTUAL_COMPONENTS = \"virtual:vino/components\";\nexport const VIRTUAL_ASSETS = \"virtual:vino/assets\";\nexport const VIRTUAL_CLIENT = \"virtual:vino/client\";\nexport const VIRTUAL_CLIENT_PAGES = \"virtual:vino/client-pages\";\nexport const VIRTUAL_SSR_HOOKS = \"virtual:vino/ssr-hooks\";\n\nexport const PAGE_EXTS = [\".tsx\", \".ts\", \".jsx\", \".js\", \".mjs\"] as const;\n"],"mappings":";AAAA,MAAa,eAAe;AAC5B,MAAa,oBAAoB;AACjC,MAAa,gBAAgB;AAC7B,MAAa,oBAAoB;AACjC,MAAa,sBAAsB;AACnC,MAAa,yBAAyB;AACtC,MAAa,gBAAgB;AAC7B,MAAa,aAAa;AAE1B,MAAa,mBAAmB;AAChC,MAAa,qBAAqB;AAClC,MAAa,iBAAiB;AAC9B,MAAa,iBAAiB;AAC9B,MAAa,uBAAuB;AACpC,MAAa,oBAAoB;AAEjC,MAAa,YAAY;CAAC;CAAQ;CAAO;CAAQ;CAAO;AAAM"}
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { c as PAGE_SLOT_ID, l as PAYLOAD_SCRIPT_ID } from "./constants-DyXAsId-.mjs";
|
|
2
|
+
import { jsxDEV } from "hono/jsx/jsx-dev-runtime";
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
import { jsxRenderer } from "hono/jsx-renderer";
|
|
5
|
+
//#region src/hydration/render.ts
|
|
6
|
+
function DefaultLayout(props) {
|
|
7
|
+
return jsxDEV("html", { children: [jsxDEV("head", { children: [jsxDEV("meta", { charset: "utf-8" }), jsxDEV("meta", {
|
|
8
|
+
name: "viewport",
|
|
9
|
+
content: "width=device-width, initial-scale=1"
|
|
10
|
+
})] }), jsxDEV("body", { children: props.children })] });
|
|
11
|
+
}
|
|
12
|
+
function isHonoApp(value) {
|
|
13
|
+
return value instanceof Hono;
|
|
14
|
+
}
|
|
15
|
+
function payloadScript(payload) {
|
|
16
|
+
const json = JSON.stringify(payload).replaceAll("<", "\\u003c");
|
|
17
|
+
return `<script id="${PAYLOAD_SCRIPT_ID}" type="application/json">${json}<\/script>`;
|
|
18
|
+
}
|
|
19
|
+
function injectDocument(html, payload, isDev, scriptSrc) {
|
|
20
|
+
const vite = isDev ? `<script type="module" src="/@vite/client"><\/script>` : "";
|
|
21
|
+
const client = `<script type="module" src="${scriptSrc}"><\/script>`;
|
|
22
|
+
const inject = `${vite}${payloadScript(payload)}${client}`;
|
|
23
|
+
if (html.includes("</body>")) return html.replace("</body>", `${inject}</body>`);
|
|
24
|
+
if (html.includes("</html>")) return html.replace("</html>", `${inject}</html>`);
|
|
25
|
+
return `<!DOCTYPE html>${html}${inject}`;
|
|
26
|
+
}
|
|
27
|
+
async function ensureJsxRenderer(c) {
|
|
28
|
+
await jsxRenderer(void 0, { docType: true })(c, async () => {});
|
|
29
|
+
}
|
|
30
|
+
function needsPageSlot(page, config, mode) {
|
|
31
|
+
if (mode === "content") return true;
|
|
32
|
+
if (page.isClientPage) return true;
|
|
33
|
+
if (config.clientRouting === true) return true;
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
/** Page hydrate/swap slot. Use in layouts so nav stays outside `#vino-page`. */
|
|
37
|
+
function vinoRoot(props) {
|
|
38
|
+
return jsxDEV("div", {
|
|
39
|
+
id: PAGE_SLOT_ID,
|
|
40
|
+
children: props.children
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const VinoRoot = vinoRoot;
|
|
44
|
+
function wrapSlot(children) {
|
|
45
|
+
return vinoRoot({ children });
|
|
46
|
+
}
|
|
47
|
+
const SLOT_MARK = `id="${PAGE_SLOT_ID}"`;
|
|
48
|
+
const BODY_INNER = /<body([^>]*)>([\s\S]*)<\/body>/i;
|
|
49
|
+
/** Wrap body children (or a fragment) in `#vino-page` when the slot is missing. */
|
|
50
|
+
function wrapBodySlot(html) {
|
|
51
|
+
if (html.includes(SLOT_MARK)) return html;
|
|
52
|
+
const match = html.match(BODY_INNER);
|
|
53
|
+
if (match) return html.replace(match[0], `<body${match[1]}><div id="${PAGE_SLOT_ID}">${match[2]}</div></body>`);
|
|
54
|
+
return `<div id="${PAGE_SLOT_ID}">${html}</div>`;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Extract the `#vino-page` element (with nested markup) for content-only navigations.
|
|
58
|
+
* Returns null when the slot is missing.
|
|
59
|
+
*/
|
|
60
|
+
function extractPageSlot(html) {
|
|
61
|
+
const marker = `id="${PAGE_SLOT_ID}"`;
|
|
62
|
+
const markerIdx = html.indexOf(marker);
|
|
63
|
+
if (markerIdx === -1) return null;
|
|
64
|
+
const tagStart = html.lastIndexOf("<", markerIdx);
|
|
65
|
+
if (tagStart === -1) return null;
|
|
66
|
+
const openEnd = html.indexOf(">", markerIdx);
|
|
67
|
+
if (openEnd === -1) return null;
|
|
68
|
+
if (html[openEnd - 1] === "/") return html.slice(tagStart, openEnd + 1);
|
|
69
|
+
let depth = 1;
|
|
70
|
+
let i = openEnd + 1;
|
|
71
|
+
while (i < html.length && depth > 0) {
|
|
72
|
+
const nextOpen = html.indexOf("<", i);
|
|
73
|
+
if (nextOpen === -1) break;
|
|
74
|
+
if (html.startsWith("</div", nextOpen)) {
|
|
75
|
+
depth -= 1;
|
|
76
|
+
const closeEnd = html.indexOf(">", nextOpen);
|
|
77
|
+
if (closeEnd === -1) break;
|
|
78
|
+
if (depth === 0) return html.slice(tagStart, closeEnd + 1);
|
|
79
|
+
i = closeEnd + 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (html.startsWith("<div", nextOpen) || html.startsWith("<DIV", nextOpen)) {
|
|
83
|
+
const closeAngle = html.indexOf(">", nextOpen);
|
|
84
|
+
if (closeAngle === -1) break;
|
|
85
|
+
if (html[closeAngle - 1] !== "/") depth += 1;
|
|
86
|
+
i = closeAngle + 1;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
i = nextOpen + 1;
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
function isHydratedDocument(html) {
|
|
94
|
+
return html.includes(`id="${PAYLOAD_SCRIPT_ID}"`);
|
|
95
|
+
}
|
|
96
|
+
function buildPayload(options) {
|
|
97
|
+
const { page, params, data, config, requestPath, title } = options;
|
|
98
|
+
return {
|
|
99
|
+
path: requestPath ?? (page.path === "" ? "/" : page.path),
|
|
100
|
+
params,
|
|
101
|
+
data,
|
|
102
|
+
clientRouting: config.clientRouting === true,
|
|
103
|
+
clientPage: page.isClientPage ? page.path : null,
|
|
104
|
+
title
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
async function renderPageHtml(options) {
|
|
108
|
+
const { c, page, pageNode = null, data, params, config, isDev, clientScriptSrc, mode = "full", title } = options;
|
|
109
|
+
const payload = buildPayload({
|
|
110
|
+
page,
|
|
111
|
+
params,
|
|
112
|
+
data,
|
|
113
|
+
config,
|
|
114
|
+
mode,
|
|
115
|
+
requestPath: c.req.path,
|
|
116
|
+
title
|
|
117
|
+
});
|
|
118
|
+
if (mode === "content") {
|
|
119
|
+
if (pageNode == null) throw new Error("content render requires pageNode");
|
|
120
|
+
const node = wrapSlot(pageNode);
|
|
121
|
+
await ensureJsxRenderer(c);
|
|
122
|
+
let html = await (await c.render(node)).text();
|
|
123
|
+
html = html.replace(/^<!DOCTYPE html>/i, "");
|
|
124
|
+
html = `${html}${payloadScript(payload)}`;
|
|
125
|
+
return {
|
|
126
|
+
html,
|
|
127
|
+
payload
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
let node = pageNode;
|
|
131
|
+
if (needsPageSlot(page, config, mode)) node = wrapSlot(node);
|
|
132
|
+
const layouts = await Promise.all(page.loadLayouts.map((load) => load()));
|
|
133
|
+
for (const layout of [...layouts].reverse()) {
|
|
134
|
+
const Layout = layout.default;
|
|
135
|
+
node = jsxDEV(Layout, {
|
|
136
|
+
data,
|
|
137
|
+
params,
|
|
138
|
+
children: node
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (layouts.length === 0) node = jsxDEV(DefaultLayout, { children: node });
|
|
142
|
+
await ensureJsxRenderer(c);
|
|
143
|
+
let html = await (await c.render(node)).text();
|
|
144
|
+
html = injectDocument(html, payload, isDev, clientScriptSrc);
|
|
145
|
+
if (!html.startsWith("<!")) html = `<!DOCTYPE html>${html}`;
|
|
146
|
+
return {
|
|
147
|
+
html,
|
|
148
|
+
payload
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/hydration/hydrate.ts
|
|
153
|
+
/** Context flag: skip hydration middleware (`defineServerPage`). */
|
|
154
|
+
const SKIP_HYDRATION = "vino.skipHydration";
|
|
155
|
+
const skipped = /* @__PURE__ */ new WeakSet();
|
|
156
|
+
/** Mark this request so `hydration()` leaves the HTML alone. */
|
|
157
|
+
function skipHydration(c) {
|
|
158
|
+
skipped.add(c);
|
|
159
|
+
}
|
|
160
|
+
const pageHydration = /* @__PURE__ */ new WeakMap();
|
|
161
|
+
/** Attach data / client-page id for this request's hydration payload. */
|
|
162
|
+
function setPageHydration(c, payload) {
|
|
163
|
+
pageHydration.set(c, payload);
|
|
164
|
+
}
|
|
165
|
+
function readPageHydration(c) {
|
|
166
|
+
return pageHydration.get(c);
|
|
167
|
+
}
|
|
168
|
+
const defaultHydration = {
|
|
169
|
+
clientScript: false,
|
|
170
|
+
isDev: false,
|
|
171
|
+
clientRouting: false
|
|
172
|
+
};
|
|
173
|
+
const boundHydration = /* @__PURE__ */ new WeakMap();
|
|
174
|
+
/** App-wide defaults for standalone `definePage` hydration (no Vite). */
|
|
175
|
+
function setHydrationDefaults(options) {
|
|
176
|
+
Object.assign(defaultHydration, options);
|
|
177
|
+
}
|
|
178
|
+
function bindHydrationOptions(value, options) {
|
|
179
|
+
if (options && Object.keys(options).length > 0) boundHydration.set(value, options);
|
|
180
|
+
return value;
|
|
181
|
+
}
|
|
182
|
+
function readBoundHydration(value) {
|
|
183
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") return void 0;
|
|
184
|
+
return boundHydration.get(value);
|
|
185
|
+
}
|
|
186
|
+
function resolveHydrationOptions(page) {
|
|
187
|
+
return {
|
|
188
|
+
...defaultHydration,
|
|
189
|
+
...readBoundHydration(page)
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function isHtmlResponse(res) {
|
|
193
|
+
if (res.status < 200 || res.status >= 300) return false;
|
|
194
|
+
return (res.headers.get("content-type") ?? "").includes("text/html");
|
|
195
|
+
}
|
|
196
|
+
function readSkipHydration(c) {
|
|
197
|
+
if (skipped.has(c)) return true;
|
|
198
|
+
try {
|
|
199
|
+
return Boolean(c.get(SKIP_HYDRATION));
|
|
200
|
+
} catch {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async function rewriteHtmlResponse(c, html, status, headers) {
|
|
205
|
+
const next = new Headers(headers);
|
|
206
|
+
next.delete("content-length");
|
|
207
|
+
if (!next.has("content-type")) next.set("content-type", "text/html; charset=UTF-8");
|
|
208
|
+
c.res = new Response(html, {
|
|
209
|
+
status,
|
|
210
|
+
headers: next
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* After the handler, wrap `#vino-page` and inject payload + client script
|
|
215
|
+
* on `text/html` responses. Skips JSON, `defineServerPage`, and docs that
|
|
216
|
+
* already contain the payload script.
|
|
217
|
+
*/
|
|
218
|
+
function hydration(options) {
|
|
219
|
+
const opts = {
|
|
220
|
+
...defaultHydration,
|
|
221
|
+
...options
|
|
222
|
+
};
|
|
223
|
+
return async (c, next) => {
|
|
224
|
+
await next();
|
|
225
|
+
const res = c.res;
|
|
226
|
+
if (!res || !isHtmlResponse(res)) return;
|
|
227
|
+
if (readSkipHydration(c)) return;
|
|
228
|
+
const html = await res.text();
|
|
229
|
+
if (isHydratedDocument(html)) {
|
|
230
|
+
await rewriteHtmlResponse(c, html, res.status, res.headers);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const wrapped = wrapBodySlot(html);
|
|
234
|
+
const mode = resolveRenderMode(c);
|
|
235
|
+
const extra = readPageHydration(c);
|
|
236
|
+
const payload = {
|
|
237
|
+
path: c.req.path,
|
|
238
|
+
params: c.req.param(),
|
|
239
|
+
data: extra?.data,
|
|
240
|
+
clientRouting: opts.clientRouting === true,
|
|
241
|
+
clientPage: extra?.clientPage !== void 0 ? extra.clientPage : c.req.path
|
|
242
|
+
};
|
|
243
|
+
const scriptSrc = opts.clientScript;
|
|
244
|
+
let nextHtml = wrapped;
|
|
245
|
+
if (mode === "content") nextHtml = `${extractPageSlot(wrapped) ?? wrapped}${payloadScript(payload)}`;
|
|
246
|
+
else if (scriptSrc === false) nextHtml = `${wrapped}${payloadScript(payload)}`;
|
|
247
|
+
else if (scriptSrc) nextHtml = injectDocument(wrapped, payload, opts.isDev === true, scriptSrc);
|
|
248
|
+
else nextHtml = `${wrapped}${payloadScript(payload)}`;
|
|
249
|
+
if (mode !== "content" && !nextHtml.startsWith("<!") && nextHtml.includes("<html")) nextHtml = `<!DOCTYPE html>${nextHtml}`;
|
|
250
|
+
await rewriteHtmlResponse(c, nextHtml, res.status, res.headers);
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function hydrationRenderer(componentOrOptions, options) {
|
|
254
|
+
const objectForm = componentOrOptions !== null && typeof componentOrOptions === "object";
|
|
255
|
+
const component = objectForm ? componentOrOptions.component : componentOrOptions;
|
|
256
|
+
const { clientScript, isDev, clientRouting, docType = true, stream } = objectForm ? componentOrOptions : options ?? {};
|
|
257
|
+
const hydrateMw = hydration({
|
|
258
|
+
clientScript,
|
|
259
|
+
isDev,
|
|
260
|
+
clientRouting
|
|
261
|
+
});
|
|
262
|
+
const renderMw = jsxRenderer(component, {
|
|
263
|
+
docType,
|
|
264
|
+
stream
|
|
265
|
+
});
|
|
266
|
+
return async (c, next) => {
|
|
267
|
+
await hydrateMw(c, async () => {
|
|
268
|
+
await renderMw(c, next);
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function resolveRenderMode(c) {
|
|
273
|
+
if (c.req.header("X-Vino-Nav")) return "content";
|
|
274
|
+
return "full";
|
|
275
|
+
}
|
|
276
|
+
function buildStandalonePayload(options) {
|
|
277
|
+
const { path, params, data, config = {}, mode = "full", title } = options;
|
|
278
|
+
return {
|
|
279
|
+
path,
|
|
280
|
+
params,
|
|
281
|
+
data,
|
|
282
|
+
clientRouting: config.clientRouting === true,
|
|
283
|
+
clientPage: path,
|
|
284
|
+
title
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
async function renderNode(c, node) {
|
|
288
|
+
try {
|
|
289
|
+
return (await c.render(node)).text();
|
|
290
|
+
} catch {
|
|
291
|
+
return c.html(node);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async function renderHydratedPage(c, fields, hydration) {
|
|
295
|
+
const mode = resolveRenderMode(c);
|
|
296
|
+
const params = c.req.param();
|
|
297
|
+
const data = fields.data ? await fields.data(c) : void 0;
|
|
298
|
+
const path = c.req.path;
|
|
299
|
+
const payload = buildStandalonePayload({
|
|
300
|
+
path,
|
|
301
|
+
params,
|
|
302
|
+
data,
|
|
303
|
+
config: fields.config,
|
|
304
|
+
mode
|
|
305
|
+
});
|
|
306
|
+
const pageNode = fields.render({
|
|
307
|
+
data,
|
|
308
|
+
params
|
|
309
|
+
});
|
|
310
|
+
if (mode === "content") {
|
|
311
|
+
const html = `${await renderNode(c, wrapSlot(pageNode))}${payloadScript(payload)}`;
|
|
312
|
+
return c.html(html);
|
|
313
|
+
}
|
|
314
|
+
let html = await renderNode(c, wrapSlot(pageNode));
|
|
315
|
+
const scriptSrc = hydration.clientScript;
|
|
316
|
+
if (scriptSrc) html = injectDocument(html, payload, hydration.isDev === true, scriptSrc);
|
|
317
|
+
else html = `${html}${payloadScript(payload)}`;
|
|
318
|
+
if (!html.startsWith("<!")) html = `<!DOCTYPE html>${html}`;
|
|
319
|
+
return c.html(html);
|
|
320
|
+
}
|
|
321
|
+
/** Terminal handler for `definePage`: SSR + slot, payload, and optional client script. */
|
|
322
|
+
function createHydratablePageHandler(fields, page) {
|
|
323
|
+
return async (c) => {
|
|
324
|
+
const run = readPageRun(c);
|
|
325
|
+
if (run) return run();
|
|
326
|
+
return renderHydratedPage(c, fields, resolveHydrationOptions(page));
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
//#endregion
|
|
330
|
+
//#region src/factory/definePage.ts
|
|
331
|
+
const PAGE_KIND = "vino:page";
|
|
332
|
+
const SERVER_PAGE_KIND = "vino:server-page";
|
|
333
|
+
const pageRuns = /* @__PURE__ */ new WeakMap();
|
|
334
|
+
/** Bind the filesystem-route renderer used by the terminal page handler. */
|
|
335
|
+
function bindPageRun(c, run) {
|
|
336
|
+
pageRuns.set(c, run);
|
|
337
|
+
}
|
|
338
|
+
/** Bound `mountVino` renderer for the current request, if any. */
|
|
339
|
+
function readPageRun(c) {
|
|
340
|
+
return pageRuns.get(c);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Strip page metadata so the list spreads onto `app.get(path, ...handlers)`.
|
|
344
|
+
*/
|
|
345
|
+
function asHandlers(page) {
|
|
346
|
+
return page;
|
|
347
|
+
}
|
|
348
|
+
function requestParams(c) {
|
|
349
|
+
try {
|
|
350
|
+
return c.req.param();
|
|
351
|
+
} catch {
|
|
352
|
+
return {};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async function renderStandalone(c, fields) {
|
|
356
|
+
const params = requestParams(c);
|
|
357
|
+
const data = fields.data ? await fields.data(c) : void 0;
|
|
358
|
+
const node = fields.render({
|
|
359
|
+
data,
|
|
360
|
+
params
|
|
361
|
+
});
|
|
362
|
+
try {
|
|
363
|
+
return await c.render(node);
|
|
364
|
+
} catch {
|
|
365
|
+
return c.html(node);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function createPageHandler(fields) {
|
|
369
|
+
return async (c) => {
|
|
370
|
+
skipHydration(c);
|
|
371
|
+
const run = readPageRun(c);
|
|
372
|
+
if (run) return run();
|
|
373
|
+
return renderStandalone(c, fields);
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Run a `createHandlers`-style list on the current context.
|
|
378
|
+
*/
|
|
379
|
+
async function dispatchHandlers(handlers, c, next) {
|
|
380
|
+
let index = -1;
|
|
381
|
+
const dispatch = async (i) => {
|
|
382
|
+
if (i <= index) throw new Error("next() called multiple times");
|
|
383
|
+
index = i;
|
|
384
|
+
const handler = handlers[i];
|
|
385
|
+
if (handler) {
|
|
386
|
+
const out = await handler(c, () => dispatch(i + 1));
|
|
387
|
+
if (out instanceof Response && !c.finalized) c.res = out;
|
|
388
|
+
} else if (next) await next();
|
|
389
|
+
};
|
|
390
|
+
await dispatch(0);
|
|
391
|
+
return c.res;
|
|
392
|
+
}
|
|
393
|
+
function isPageDefinition(value) {
|
|
394
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") return false;
|
|
395
|
+
const candidate = value;
|
|
396
|
+
return (candidate.kind === "vino:page" || candidate.kind === "vino:server-page") && typeof candidate.render === "function";
|
|
397
|
+
}
|
|
398
|
+
function isClientPageDefinition(value) {
|
|
399
|
+
return isPageDefinition(value) && value.kind === "vino:page";
|
|
400
|
+
}
|
|
401
|
+
function isServerPageDefinition(value) {
|
|
402
|
+
return isPageDefinition(value) && value.kind === "vino:server-page";
|
|
403
|
+
}
|
|
404
|
+
function readPageDefinition(value) {
|
|
405
|
+
return isPageDefinition(value) ? value : void 0;
|
|
406
|
+
}
|
|
407
|
+
function resolvePageRender(value) {
|
|
408
|
+
if (isPageDefinition(value)) return value.render;
|
|
409
|
+
if (typeof value === "function") return value;
|
|
410
|
+
}
|
|
411
|
+
/** True when source calls `definePage(...)` (hydratable pages). */
|
|
412
|
+
function usesDefinePage(code) {
|
|
413
|
+
return /(?:^|[^\w$])definePage\s*(?:<[^>]*>\s*)?\(/.test(code);
|
|
414
|
+
}
|
|
415
|
+
function isPageSpec(value) {
|
|
416
|
+
return typeof value === "function" || value !== null && typeof value === "object" && !Array.isArray(value);
|
|
417
|
+
}
|
|
418
|
+
function createHandlers(...handlers) {
|
|
419
|
+
return handlers.filter((handler) => handler !== void 0);
|
|
420
|
+
}
|
|
421
|
+
function asPageDefinition(fields, leading) {
|
|
422
|
+
const list = [];
|
|
423
|
+
const terminal = fields.client ? createHydratablePageHandler(fields, list) : createPageHandler(fields);
|
|
424
|
+
list.push(...createHandlers(...leading, terminal));
|
|
425
|
+
if (list.length === 0) throw new TypeError("[vino] definePage() produced no handlers");
|
|
426
|
+
return Object.assign(list, fields);
|
|
427
|
+
}
|
|
428
|
+
function pageDefinition(args, kind, fnName) {
|
|
429
|
+
const spec = args.at(-1);
|
|
430
|
+
if (args.length === 0 || !isPageSpec(spec)) throw new TypeError(`[vino] ${fnName}() requires a JSX function or { render }`);
|
|
431
|
+
const leading = args.slice(0, -1).filter((handler) => typeof handler === "function");
|
|
432
|
+
if (typeof spec === "function") return asPageDefinition({
|
|
433
|
+
kind,
|
|
434
|
+
client: kind === PAGE_KIND,
|
|
435
|
+
render: spec
|
|
436
|
+
}, leading);
|
|
437
|
+
const render = spec.render ?? spec.page;
|
|
438
|
+
if (typeof render !== "function") throw new TypeError(`[vino] ${fnName}() requires a JSX function or { render }`);
|
|
439
|
+
return asPageDefinition({
|
|
440
|
+
kind,
|
|
441
|
+
client: kind === PAGE_KIND,
|
|
442
|
+
render,
|
|
443
|
+
data: spec.data,
|
|
444
|
+
config: spec.config
|
|
445
|
+
}, [...leading, ...spec.use ?? []]);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Declare a page that SSRs with `hono/jsx`, then hydrates `#vino-page`
|
|
449
|
+
* with the vino client signal runtime (`vinojs/client`).
|
|
450
|
+
*
|
|
451
|
+
* Returns a Hono handler list (same shape as `createHandlers`) with `render` /
|
|
452
|
+
* `data` / `config` attached.
|
|
453
|
+
*
|
|
454
|
+
* @example
|
|
455
|
+
* export default definePage(() => {
|
|
456
|
+
* const [n, setN] = createSignal(0)
|
|
457
|
+
* return <button onClick={() => setN(n() + 1)}>{n}</button>
|
|
458
|
+
* })
|
|
459
|
+
*/
|
|
460
|
+
const definePage = ((...args) => pageDefinition(args, PAGE_KIND, "definePage"));
|
|
461
|
+
/**
|
|
462
|
+
* Declare a page that SSRs only. The output is never hydrated.
|
|
463
|
+
*/
|
|
464
|
+
const defineServerPage = ((...args) => pageDefinition(args, SERVER_PAGE_KIND, "defineServerPage"));
|
|
465
|
+
//#endregion
|
|
466
|
+
export { VinoRoot as C, skipHydration as S, renderPageHtml as T, hydrationRenderer as _, definePage as a, setHydrationDefaults as b, isClientPageDefinition as c, readPageDefinition as d, resolvePageRender as f, hydration as g, bindHydrationOptions as h, bindPageRun as i, isPageDefinition as l, SKIP_HYDRATION as m, SERVER_PAGE_KIND as n, defineServerPage as o, usesDefinePage as p, asHandlers as r, dispatchHandlers as s, PAGE_KIND as t, isServerPageDefinition as u, readBoundHydration as v, isHonoApp as w, setPageHydration as x, readPageHydration as y };
|
|
467
|
+
|
|
468
|
+
//# sourceMappingURL=definePage-DC6gJKwW.mjs.map
|