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.
Files changed (60) hide show
  1. package/README.md +23 -18
  2. package/dist/client.d.mts +74 -8
  3. package/dist/client.d.mts.map +1 -1
  4. package/dist/client.mjs +194 -53
  5. package/dist/client.mjs.map +1 -1
  6. package/dist/component-wrap.mjs +1 -1
  7. package/dist/config-CXHX9qZs.mjs +17 -0
  8. package/dist/config-CXHX9qZs.mjs.map +1 -0
  9. package/dist/{constants-BVT2bJEd.mjs → constants-DyXAsId-.mjs} +2 -3
  10. package/dist/constants-DyXAsId-.mjs.map +1 -0
  11. package/dist/definePage-DC6gJKwW.mjs +468 -0
  12. package/dist/definePage-DC6gJKwW.mjs.map +1 -0
  13. package/dist/{defineServerFn-H-bz7xBX.mjs → defineServerFn-DH6B99bH.mjs} +2 -17
  14. package/dist/defineServerFn-DH6B99bH.mjs.map +1 -0
  15. package/dist/factory-0LjmIY5F.mjs +123 -0
  16. package/dist/factory-0LjmIY5F.mjs.map +1 -0
  17. package/dist/factory.d.mts +38 -2
  18. package/dist/factory.d.mts.map +1 -0
  19. package/dist/factory.mjs +4 -132
  20. package/dist/hydration.d.mts +3 -0
  21. package/dist/hydration.mjs +2 -0
  22. package/dist/jsx-dev-runtime.client.d.mts +2 -0
  23. package/dist/jsx-dev-runtime.client.mjs +2 -0
  24. package/dist/jsx-dev-runtime.d.mts +2 -0
  25. package/dist/jsx-dev-runtime.mjs +2 -0
  26. package/dist/jsx-runtime-5OIvn_Qa.mjs +335 -0
  27. package/dist/jsx-runtime-5OIvn_Qa.mjs.map +1 -0
  28. package/dist/jsx-runtime-BLN--agM.d.mts +20 -0
  29. package/dist/jsx-runtime-BLN--agM.d.mts.map +1 -0
  30. package/dist/jsx-runtime.client.d.mts +2 -0
  31. package/dist/jsx-runtime.client.mjs +2 -0
  32. package/dist/jsx-runtime.d.mts +2 -0
  33. package/dist/jsx-runtime.mjs +2 -0
  34. package/dist/mount.d.mts +17 -0
  35. package/dist/mount.d.mts.map +1 -0
  36. package/dist/mount.mjs +107 -0
  37. package/dist/mount.mjs.map +1 -0
  38. package/dist/render-Cif1RZMC.d.mts +169 -0
  39. package/dist/render-Cif1RZMC.d.mts.map +1 -0
  40. package/dist/{types-CQITdkpF.d.mts → types-jfsZLl9U.d.mts} +10 -8
  41. package/dist/types-jfsZLl9U.d.mts.map +1 -0
  42. package/dist/vino.d.mts +1 -37
  43. package/dist/vino.d.mts.map +1 -1
  44. package/dist/vino.mjs +2 -208
  45. package/dist/vite.d.mts +1 -1
  46. package/dist/vite.d.mts.map +1 -1
  47. package/dist/vite.mjs +175 -40
  48. package/dist/vite.mjs.map +1 -1
  49. package/package.json +29 -6
  50. package/dist/compile-BGVCPc4H.mjs +0 -111
  51. package/dist/compile-BGVCPc4H.mjs.map +0 -1
  52. package/dist/constants-BVT2bJEd.mjs.map +0 -1
  53. package/dist/definePage-CcqUZAiy.mjs +0 -159
  54. package/dist/definePage-CcqUZAiy.mjs.map +0 -1
  55. package/dist/defineServerFn-H-bz7xBX.mjs.map +0 -1
  56. package/dist/factory-Dxz3nh-p.d.mts +0 -182
  57. package/dist/factory-Dxz3nh-p.d.mts.map +0 -1
  58. package/dist/factory.mjs.map +0 -1
  59. package/dist/types-CQITdkpF.d.mts.map +0 -1
  60. package/dist/vino.mjs.map +0 -1
package/README.md CHANGED
@@ -1,16 +1,22 @@
1
1
  # Vino
2
2
 
3
3
  Hono + Vite meta-framework designed for Cloudflare Workers.
4
+ Vino extends your server, it will simply mounts pages onto it
4
5
 
5
6
  Early WIP - core changes will happen
6
7
 
8
+ ## Vino extends hono with the following features
9
+
10
+ - Hydration - `definePage` for a whole page, `src/components/` for widgets (`hono/jsx/dom`)
11
+ - Client routing - Optionally with `clientRouting: true`, same-origin navigations fetch only new content
12
+ - Page factory - Create new pages with server data fetching builtin
13
+
14
+ ## Optional vite plugin
15
+
7
16
  - **Pages** — filesystem routes in `src/pages` using Nitro path syntax
8
17
  - **Layouts** — `src/layout` files wrap matching routes
9
- - **Server** — your Hono app; Vino mounts pages onto it
10
- - **SSR** — `hono/jsx`
11
- - **Hydration** — `definePage` for a whole page, `src/components/` for widgets (`hono/jsx/dom`)
12
- - **SSG** — prerender static routes (and dynamic routes with `ssgParams`) at build time; `partial` writes layout shells, `full` writes complete HTML
13
- - **Dev / deploy** — `@cloudflare/vite-plugin`
18
+ - **SSG** — prerender static routes (and dynamic routes with `ssgParams`) at build time as full HTML
19
+ - **Dev / deploy** — Integrates with `@cloudflare/vite-plugin`
14
20
 
15
21
  ## Install
16
22
 
@@ -21,11 +27,11 @@ pnpm add -D vite @cloudflare/vite-plugin wrangler
21
27
 
22
28
  ## Quick start
23
29
 
24
- `src/server.ts` — you own the Hono app; extra routes stay on it:
30
+ `src/server.ts`
25
31
 
26
32
  ```ts
27
33
  import { Hono } from "hono";
28
- import { createVino, mountVino } from "vinojs";
34
+ import { mountVino } from "vinojs/mount";
29
35
  import { api } from "./api";
30
36
 
31
37
  const app = new Hono().route("/api", api);
@@ -66,7 +72,7 @@ export default defineConfig({
66
72
  | `blog/[slug].tsx` | `/blog/:slug` |
67
73
  | `docs/[...slug].tsx` | `/docs/*` |
68
74
 
69
- The file **is** the page. Export a default component, `definePage(...)`, `defineServerPage(...)`, a Hono sub-app, or named `GET` / `POST` handlers.
75
+ You export a default component, `definePage(...)`, `defineServerPage(...)`
70
76
 
71
77
  ```tsx
72
78
  import { definePage } from "vinojs";
@@ -82,15 +88,15 @@ export default definePage(() => {
82
88
  import { definePage } from "vinojs";
83
89
 
84
90
  export default definePage({
85
- render: ({ data }) => <h1>{data.title}</h1>,
91
+ config: { prerender: true, clientRouting: true },
86
92
  data: async (c) => ({ title: "Hello" }),
87
- config: { prerender: "full", clientRouting: true },
93
+ render: ({ data }) => <h1>{data.title}</h1>,
88
94
  });
89
95
  ```
90
96
 
91
97
  `definePage` SSRs with `hono/jsx`, then hydrates `#vino-page` with `hono/jsx/dom`. `data` and `config` are colocated on the page.
92
98
 
93
- Colocate loaders in `*.server.ts` and pass them through `defineServerFn`. The result is a `data` loader today; the same wrapper is how server functions will be called later (no `'use server'`).
99
+ Colocate loaders in `*.server.ts` and pass them through `defineServerFn`. The result is a `data` loader today; the same wrapper is how server functions will be called later.
94
100
 
95
101
  ```ts
96
102
  // [slug].server.ts
@@ -123,7 +129,7 @@ import { defineServerPage } from "vinojs";
123
129
  export default defineServerPage(() => <h1>About</h1>);
124
130
  ```
125
131
 
126
- `prerender` may be `'full'` (complete HTML at build), `'partial'` (layout shell + empty `#vino-page`; client fetches content), or `false`.
132
+ `prerender: true` includes the page in SSG (full HTML at build). Omit or set `false` to skip.
127
133
 
128
134
  ### Layouts (`src/layout`)
129
135
 
@@ -145,17 +151,16 @@ Put widgets in `src/components/` (or name `$Widget.tsx` / `Widget.component.tsx`
145
151
 
146
152
  ### Navigation
147
153
 
148
- `clientRouting: true` in `definePage({ config })` or `vino({ clientRouting: true })` intercepts same-origin links, fetches page content only (`X-Vino-Nav`), swaps `#vino-page`, and rehydrates. Layout stays mounted. Default is on via the Vite plugin.
154
+ `clientRouting: true` in `definePage({ config })`, `vino({ clientRouting: true })`, or `hydration({ clientRouting: true })` intercepts same-origin links, fetches page content only (`X-Vino-Nav`), swaps `#vino-page`, and rehydrates. Layout stays mounted. Default is on via the Vite plugin.
149
155
 
150
156
  ## API
151
157
 
152
- - `createVino()` — `vinojs`
153
- - `mountVino(app)` — `vinojs`
158
+ - `createVino()` / `mountVino(app)` — `vinojs/mount` (Vite filesystem routes; uses virtual modules)
154
159
  - `definePage(fn | { render, data?, config? })` — hydrate the page (`vinojs`)
155
160
  - `defineServerPage(...)` — SSR only, never hydrate (`vinojs`)
156
161
  - `defineServerFn(fn)` — server-only function; pass as `definePage({ data })` (`vinojs`)
157
- - `createClient()` — `vinojs/client` (injected automatically)
158
- - `vino({ pagesDir, layoutsDir, clientRouting, prerender })` — `vinojs/vite`
162
+ - `createClient()` — `vinojs/client` (injected automatically by the Vite plugin; hydrates and runs client routing)
163
+ - `vino({ pagesDir, layoutsDir, clientRouting, prerender })` — `vinojs/vite` (`prerender: true` writes full HTML at build)
159
164
 
160
165
  APIs stay on your Hono app. Use `hono/client` `hc` against those routes; page HTML is never fetched through `hc`.
161
166
 
@@ -164,7 +169,7 @@ APIs stay on your Hono app. Use `hono/client` `hc` against those routes; page HT
164
169
  ```bash
165
170
  pnpm install
166
171
  pnpm build
167
- pnpm --filter @vinojs/example-basic dev
172
+ pnpm --filter @vinojs/example-basic-vite dev
168
173
  ```
169
174
 
170
175
  ## License
package/dist/client.d.mts CHANGED
@@ -1,13 +1,19 @@
1
- import { u as VinoPayload } from "./types-CQITdkpF.mjs";
1
+ import { l as VinoPayload } from "./types-jfsZLl9U.mjs";
2
2
  //#region src/client/create-client.d.ts
3
- interface CreateClientOptions {
4
- hydrate?: (vnode: unknown, el: Element) => void | Promise<void>;
5
- createElement?: (type: unknown, props: unknown) => unknown;
6
- }
7
3
  type ComponentLoaders = Record<string, () => Promise<Record<string, unknown>>>;
8
4
  type ClientPageLoaders = Record<string, () => Promise<{
9
5
  default?: unknown;
10
6
  }>>;
7
+ interface CreateClientOptions {
8
+ /** Hydratable component loaders (from Vite `virtual:vino/components`). */
9
+ components?: ComponentLoaders;
10
+ /** Client-page loaders (from Vite `virtual:vino/client-pages`). */
11
+ clientPages?: ClientPageLoaders;
12
+ /** Escape hatch: custom hydrate when not using the signal runtime. */
13
+ hydrate?: (vnode: unknown, el: Element) => void | Promise<void>;
14
+ /** Escape hatch: build a vnode for custom `hydrate` (legacy hono/jsx/dom path). */
15
+ createElement?: (type: unknown, props: unknown) => unknown;
16
+ }
11
17
  type VinoHmrPayload = {
12
18
  kind: "component";
13
19
  id: string;
@@ -29,13 +35,73 @@ type VinoHmrPayload = {
29
35
  };
30
36
  type HmrFn = (payload: VinoHmrPayload) => void;
31
37
  /**
32
- * Fill partial shells, hydrate `#vino-page` / `<vino-component>`, and intercept
33
- * same-origin links with content-only swaps when `clientRouting` is enabled.
38
+ * Hydrate `#vino-page` / `<vino-component>`, and intercept same-origin links
39
+ * with content-only swaps when `clientRouting` is enabled in the payload.
34
40
  */
35
41
  declare function createClient(options?: CreateClientOptions): {
36
42
  ready: Promise<VinoPayload | null>;
37
43
  hmr: HmrFn;
38
44
  };
39
45
  //#endregion
40
- export { type CreateClientOptions, type VinoHmrPayload, createClient };
46
+ //#region src/client/runtime/signal.d.ts
47
+ type Accessor<T> = {
48
+ (): T;
49
+ readonly [SIGNAL]: true;
50
+ };
51
+ type Setter<T> = (value: T | ((prev: T) => T)) => T;
52
+ type Signal<T> = [get: Accessor<T>, set: Setter<T>];
53
+ declare const SIGNAL: unique symbol;
54
+ declare function isSignalAccessor(value: unknown): value is Accessor<unknown>;
55
+ declare function createSignal<T>(initial: T): Signal<T>;
56
+ declare function createEffect(fn: () => void | (() => void)): void;
57
+ declare function onCleanup(fn: () => void): void;
58
+ declare function createRoot<T>(fn: (dispose: () => void) => T): T;
59
+ declare function untrack<T>(fn: () => T): T;
60
+ //#endregion
61
+ //#region src/client/runtime/mount.d.ts
62
+ type Mountable = (props: never) => unknown;
63
+ interface MountRoot {
64
+ render: (component: Mountable, props?: Record<string, unknown>) => void;
65
+ unmount: () => void;
66
+ }
67
+ /**
68
+ * Mount a setup-style component into `el`.
69
+ * Prefers hydrating a single SSR root element; otherwise replaces children.
70
+ */
71
+ declare function mountComponent(el: Element, component: Mountable, props?: Record<string, unknown>): MountRoot;
72
+ /** Alias for page slot mounting. */
73
+ declare function hydratePage(el: Element, page: Mountable, props?: Record<string, unknown>): MountRoot;
74
+ //#endregion
75
+ //#region src/client/runtime/control.d.ts
76
+ type ShowProps = {
77
+ when: boolean | Accessor<boolean> | (() => boolean);
78
+ children?: unknown;
79
+ fallback?: unknown;
80
+ };
81
+ /**
82
+ * Conditionally mount `children` or `fallback` based on a reactive `when`.
83
+ * On the server (no `document`), returns the active branch for `hono/jsx` SSR.
84
+ */
85
+ declare function Show(props: ShowProps): unknown;
86
+ //#endregion
87
+ //#region src/client/runtime/control-for.d.ts
88
+ type ForProps<T> = {
89
+ each: T[] | Accessor<T[]> | (() => T[]);
90
+ children: (item: T, index: number) => unknown;
91
+ };
92
+ /**
93
+ * Reactive list. On the server, maps `each` once for `hono/jsx` SSR.
94
+ */
95
+ declare function For<T>(props: ForProps<T>): unknown;
96
+ //#endregion
97
+ //#region src/client/runtime/hooks.d.ts
98
+ /** Signal-backed `useState` compatible with stock JSX (`value={count}`, `{count}` as child). */
99
+ declare function useState<T>(initial: T | (() => T)): [Accessor<T>, Setter<T>];
100
+ /** Runs `fn` in a reactive effect. Dependency arrays are ignored (Solid-style). */
101
+ declare function useEffect(fn: () => void | (() => void), _deps?: unknown[]): void;
102
+ declare function useRef<T>(initial: T): {
103
+ current: T;
104
+ };
105
+ //#endregion
106
+ export { type Accessor, type CreateClientOptions, For, type ForProps, type MountRoot, type Mountable, type Setter, Show, type ShowProps, type Signal, type VinoHmrPayload, createClient, createEffect, createRoot, createSignal, hydratePage, isSignalAccessor, mountComponent, onCleanup, untrack, useEffect, useRef, useState };
41
107
  //# sourceMappingURL=client.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.mts","names":[],"sources":["../src/client/create-client.ts"],"mappings":";;UAIiB;EACf,WAAW,gBAAgB,IAAI,mBAAmB;EAClD,iBAAiB,eAAe;;KAG7B,mBAAmB,qBAAqB,QAAQ;KAChD,oBAAoB,qBAAqB;EAAU;;KAc5C;EACN;EAAmB;EAAY,MAAM;;EACrC;EAAqB;EAAY;IAAQ;;;EACzC;;EACA;;EACA;EAAkB,aAAa;EAAkB,cAAc;;KAEhE,SAAS,SAAS;;;;;iBAgVP,aAAa,UAAS"}
1
+ {"version":3,"file":"client.d.mts","names":[],"sources":["../src/client/create-client.ts","../src/client/runtime/signal.ts","../src/client/runtime/mount.ts","../src/client/runtime/control.ts","../src/client/runtime/control-for.ts","../src/client/runtime/hooks.ts"],"mappings":";;KAKK,mBAAmB,qBAAqB,QAAQ;KAChD,oBAAoB,qBAAqB;EAAU;;UAEvC;;EAEf,aAAa;;EAEb,cAAc;;EAEd,WAAW,gBAAgB,IAAI,mBAAmB;;EAElD,iBAAiB,eAAe;;KAUtB;EACN;EAAmB;EAAY,MAAM;;EACrC;EAAqB;EAAY;IAAQ;;;EACzC;;EACA;;EACA;EAAkB,aAAa;EAAkB,cAAc;;KAEhE,SAAS,SAAS;;;;;iBAqTP,aAAa,UAAS;;;;;;KCtV1B,SAAS;MACf;YACM;;KAGA,OAAO,MAAM,OAAO,MAAM,MAAM,MAAM,OAAO;KAE7C,OAAO,MAAM,KAAK,SAAS,IAAI,KAAK,OAAO;cAE1C;iBA0BG,iBAAiB,iBAAiB,SAAS;iBAI3C,aAAa,GAAG,SAAS,IAAI,OAAO;iBAiCpC,aAAa;iBA0Bb,UAAU;iBAUV,WAAW,GAAG,KAAK,wBAAwB,IAAI;iBA0B/C,QAAQ,GAAG,UAAU,IAAI;;;KCnI7B,aAAa;UAER;EACf,SAAS,WAAW,WAAW,QAAQ;EACvC;;;;;;iBAoDc,eACd,IAAI,SACJ,WAAW,WACX,QAAO,0BACN;;iBA8Ba,YACd,IAAI,SACJ,MAAM,WACN,QAAO,0BACN;;;KC/FS;EACV,gBAAgB;EAChB;EACA;;;;;;iBAiCc,KAAK,OAAO;;;KCpChB,SAAS;EACnB,MAAM,MAAM,SAAS,cAAc;EACnC,WAAW,MAAM,GAAG;;;;;iBAgCN,IAAI,GAAG,OAAO,SAAS;;;;iBCjCvB,SAAS,GAAG,SAAS,WAAW,MAAM,SAAS,IAAI,OAAO;;iBAK1D,UAAU,+BAA+B;iBAOzC,OAAO,GAAG,SAAS;EAAM,SAAS"}
package/dist/client.mjs CHANGED
@@ -1,5 +1,73 @@
1
- import { c as PAGE_SLOT_ID, l as PAYLOAD_SCRIPT_ID, o as NAV_HEADER, t as COMPONENT_TAG } from "./constants-BVT2bJEd.mjs";
2
- import { u as resolvePageRender } from "./definePage-CcqUZAiy.mjs";
1
+ import { c as PAGE_SLOT_ID, l as PAYLOAD_SCRIPT_ID, o as NAV_HEADER, t as COMPONENT_TAG } from "./constants-DyXAsId-.mjs";
2
+ import { f as resolvePageRender } from "./definePage-DC6gJKwW.mjs";
3
+ import { a as pushHydrateCursor, c as createSignal, d as untrack, i as popHydrateCursor, l as isSignalAccessor, o as createEffect, s as createRoot, u as onCleanup } from "./jsx-runtime-5OIvn_Qa.mjs";
4
+ //#region src/client/runtime/mount.ts
5
+ function isDomNode$2(value) {
6
+ return typeof value === "object" && value !== null && "nodeType" in value && typeof value.nodeType === "number";
7
+ }
8
+ function toNodes$2(value) {
9
+ if (value == null || value === false || value === true) return [];
10
+ if (typeof value === "string" || typeof value === "number") return [document.createTextNode(String(value))];
11
+ if (isDomNode$2(value)) return [value];
12
+ if (Array.isArray(value)) return value.flatMap(toNodes$2);
13
+ return [];
14
+ }
15
+ function elementChildCount(el) {
16
+ return Array.from(el.childNodes).filter((n) => !(n.nodeType === 3 && !n.textContent?.trim())).length;
17
+ }
18
+ /**
19
+ * Hydrate into existing SSR DOM when the slot has a single element child
20
+ * and `jsx()` claims that node. Stock `react-jsx` evaluates nested `jsx()`
21
+ * before the parent, so a mismatch falls back to create-mode instead of
22
+ * keeping an empty claimed root.
23
+ */
24
+ function mountInto(el, component, props) {
25
+ if (elementChildCount(el) === 1) {
26
+ pushHydrateCursor(el);
27
+ try {
28
+ const live = toNodes$2(component(props))[0];
29
+ const ssr = Array.from(el.childNodes).find((n) => n.nodeType === 1);
30
+ if (live && ssr && live === ssr) return;
31
+ } finally {
32
+ popHydrateCursor();
33
+ }
34
+ }
35
+ el.replaceChildren(...toNodes$2(component(props)));
36
+ }
37
+ /**
38
+ * Mount a setup-style component into `el`.
39
+ * Prefers hydrating a single SSR root element; otherwise replaces children.
40
+ */
41
+ function mountComponent(el, component, props = {}) {
42
+ let dispose;
43
+ const run = (next, nextProps) => {
44
+ dispose?.();
45
+ createRoot((d) => {
46
+ dispose = d;
47
+ mountInto(el, next, nextProps);
48
+ });
49
+ };
50
+ run(component, props);
51
+ return {
52
+ render: (next, nextProps) => {
53
+ dispose?.();
54
+ createRoot((d) => {
55
+ dispose = d;
56
+ el.replaceChildren(...toNodes$2(next(nextProps ?? props)));
57
+ });
58
+ },
59
+ unmount: () => {
60
+ dispose?.();
61
+ dispose = void 0;
62
+ el.replaceChildren();
63
+ }
64
+ };
65
+ }
66
+ /** Alias for page slot mounting. */
67
+ function hydratePage(el, page, props = {}) {
68
+ return mountComponent(el, page, props);
69
+ }
70
+ //#endregion
3
71
  //#region src/client/create-client.ts
4
72
  const islands = /* @__PURE__ */ new Map();
5
73
  let pageRoot = null;
@@ -48,39 +116,26 @@ function writePayload(payload) {
48
116
  }
49
117
  el.textContent = JSON.stringify(payload).replaceAll("<", "\\u003c");
50
118
  }
51
- async function loadDom() {
52
- const [{ hydrateRoot }, { createElement }] = await Promise.all([import("hono/jsx/dom/client"), import("hono/jsx/dom")]);
53
- return {
54
- hydrateRoot,
55
- createElement
56
- };
57
- }
58
- async function getComponentLoaders() {
59
- componentLoaders ??= (await import("virtual:vino/components")).components;
60
- return componentLoaders;
119
+ function getComponentLoaders() {
120
+ return componentLoaders ?? {};
61
121
  }
62
- async function getClientPageLoaders() {
63
- clientPageLoaders ??= (await import("virtual:vino/client-pages")).clientPages;
64
- return clientPageLoaders;
122
+ function getClientPageLoaders() {
123
+ return clientPageLoaders ?? {};
65
124
  }
66
- function vnodeFor(options, createElement, type, props) {
67
- return options.createElement ? options.createElement(type, props) : createElement(type, props);
68
- }
69
- async function mountVnode(options, el, vnode, existing) {
70
- if (existing) {
71
- existing.render(vnode);
72
- return existing;
73
- }
125
+ async function mountSetup(options, el, component, props, existing) {
74
126
  if (options.hydrate) {
127
+ const vnode = options.createElement ? options.createElement(component, props) : component(props);
75
128
  await options.hydrate(vnode, el);
76
129
  return null;
77
130
  }
78
- const { hydrateRoot } = await loadDom();
79
- return hydrateRoot(el, vnode);
131
+ if (existing) {
132
+ existing.render(component, props);
133
+ return existing;
134
+ }
135
+ return hydratePage(el, component, props);
80
136
  }
81
137
  async function hydrateComponents(options, root = document) {
82
- const components = await getComponentLoaders();
83
- const { createElement } = await loadDom();
138
+ const components = getComponentLoaders();
84
139
  const nodes = root.querySelectorAll(COMPONENT_TAG);
85
140
  for (const el of nodes) {
86
141
  const id = el.getAttribute("data-component");
@@ -95,10 +150,9 @@ async function hydrateComponents(options, root = document) {
95
150
  const Component = (await loader())[exportName];
96
151
  if (typeof Component !== "function") continue;
97
152
  const props = JSON.parse(propsJson);
98
- const vnode = vnodeFor(options, createElement, Component, props);
99
153
  const previous = islands.get(el);
100
154
  if (previous?.root) previous.root.unmount();
101
- const mounted = await mountVnode(options, el, vnode, null);
155
+ const mounted = await mountSetup(options, el, Component, props, null);
102
156
  islands.set(el, {
103
157
  id,
104
158
  exportName,
@@ -111,7 +165,7 @@ async function hydrateClientPage(payload, options) {
111
165
  if (!payload.clientPage) return;
112
166
  const root = document.getElementById(PAGE_SLOT_ID);
113
167
  if (!root) return;
114
- const load = (await getClientPageLoaders())[payload.clientPage];
168
+ const load = getClientPageLoaders()[payload.clientPage];
115
169
  if (!load) return;
116
170
  const mod = await load();
117
171
  await renderClientPage(payload.clientPage, mod, payload, options, root);
@@ -119,21 +173,19 @@ async function hydrateClientPage(payload, options) {
119
173
  async function renderClientPage(path, mod, payload, options, root) {
120
174
  const Page = resolvePageRender(mod.default);
121
175
  if (!Page) return;
122
- const { createElement } = await loadDom();
123
- const vnode = vnodeFor(options, createElement, Page, {
176
+ const props = {
124
177
  data: payload.data,
125
178
  params: payload.params
126
- });
179
+ };
127
180
  const reuse = pageRoot?.el === root ? pageRoot.root : null;
128
181
  if (pageRoot && pageRoot.el !== root) pageRoot.root?.unmount();
129
182
  pageRoot = {
130
183
  el: root,
131
184
  path,
132
- root: await mountVnode(options, root, vnode, reuse)
185
+ root: await mountSetup(options, root, Page, props, reuse)
133
186
  };
134
187
  }
135
188
  async function patchComponent(id, mod, options) {
136
- const { createElement } = await loadDom();
137
189
  for (const [el, rec] of islands) {
138
190
  if (rec.id !== id) continue;
139
191
  if (!el.isConnected) {
@@ -143,7 +195,7 @@ async function patchComponent(id, mod, options) {
143
195
  }
144
196
  const Component = mod[rec.exportName];
145
197
  if (typeof Component !== "function") continue;
146
- rec.root = await mountVnode(options, el, vnodeFor(options, createElement, Component, rec.props), rec.root);
198
+ rec.root = await mountSetup(options, el, Component, rec.props, rec.root);
147
199
  }
148
200
  }
149
201
  async function patchClientPage(path, mod, options) {
@@ -202,17 +254,6 @@ async function applyContent(href, push, options, fragment) {
202
254
  async function navigate(href, push, options) {
203
255
  return applyContent(href, push, options, await fetchContent(href));
204
256
  }
205
- async function fillPartialShell(options, payload) {
206
- if (!payload?.partial) return payload;
207
- const slot = document.getElementById(PAGE_SLOT_ID);
208
- if (!slot) return payload;
209
- if (slot.childNodes.length > 0) return hydrateSlot(options, {
210
- ...payload,
211
- partial: false
212
- });
213
- const fragment = await fetchContent(location.href);
214
- return applyContent(location.href, false, options, fragment);
215
- }
216
257
  function bindRouting(options, payload) {
217
258
  if (!payload?.clientRouting) return;
218
259
  document.addEventListener("click", (event) => {
@@ -262,19 +303,19 @@ async function handleHmr(payload, options) {
262
303
  if (payload.kind === "client-page" && payload.mod) await patchClientPage(payload.id, payload.mod, options);
263
304
  }
264
305
  /**
265
- * Fill partial shells, hydrate `#vino-page` / `<vino-component>`, and intercept
266
- * same-origin links with content-only swaps when `clientRouting` is enabled.
306
+ * Hydrate `#vino-page` / `<vino-component>`, and intercept same-origin links
307
+ * with content-only swaps when `clientRouting` is enabled in the payload.
267
308
  */
268
309
  function createClient(options = {}) {
310
+ if (options.components) componentLoaders = options.components;
311
+ if (options.clientPages) clientPageLoaders = options.clientPages;
269
312
  const hmr = (payload) => {
270
313
  handleHmr(payload, options);
271
314
  };
272
315
  bindHmr(hmr);
273
316
  return {
274
317
  ready: (async () => {
275
- let payload = readPayload();
276
- if (payload?.partial) payload = await fillPartialShell(options, payload) ?? payload;
277
- else payload = await hydrateSlot(options, payload) ?? payload;
318
+ const payload = await hydrateSlot(options, readPayload()) ?? readPayload();
278
319
  bindRouting(options, payload);
279
320
  return payload;
280
321
  })(),
@@ -282,6 +323,106 @@ function createClient(options = {}) {
282
323
  };
283
324
  }
284
325
  //#endregion
285
- export { createClient };
326
+ //#region src/client/runtime/control.ts
327
+ function readWhen(when) {
328
+ if (isSignalAccessor(when)) return Boolean(when());
329
+ if (typeof when === "function") return Boolean(when());
330
+ return Boolean(when);
331
+ }
332
+ function isDomNode$1(value) {
333
+ return typeof value === "object" && value !== null && "nodeType" in value && typeof value.nodeType === "number";
334
+ }
335
+ function toNodes$1(value) {
336
+ if (value == null || value === false || value === true) return [];
337
+ if (typeof value === "string" || typeof value === "number") return [document.createTextNode(String(value))];
338
+ if (isDomNode$1(value)) return [value];
339
+ if (Array.isArray(value)) return value.flatMap(toNodes$1);
340
+ if (typeof value === "function") return toNodes$1(value());
341
+ return [];
342
+ }
343
+ /**
344
+ * Conditionally mount `children` or `fallback` based on a reactive `when`.
345
+ * On the server (no `document`), returns the active branch for `hono/jsx` SSR.
346
+ */
347
+ function Show(props) {
348
+ if (typeof document === "undefined") return readWhen(props.when) ? props.children : props.fallback;
349
+ const frag = document.createDocumentFragment();
350
+ const start = document.createComment("vino-show");
351
+ const end = document.createComment("/vino-show");
352
+ frag.append(start, end);
353
+ let current = [];
354
+ const clear = () => {
355
+ for (const node of current) node.parentNode?.removeChild(node);
356
+ current = [];
357
+ };
358
+ const insert = (nodes) => {
359
+ const parent = start.parentNode;
360
+ if (!parent) return;
361
+ for (const node of nodes) parent.insertBefore(node, end);
362
+ current = nodes;
363
+ };
364
+ createEffect(() => {
365
+ clear();
366
+ insert(toNodes$1(readWhen(props.when) ? props.children : props.fallback));
367
+ });
368
+ return frag;
369
+ }
370
+ //#endregion
371
+ //#region src/client/runtime/control-for.ts
372
+ function readEach(each) {
373
+ if (isSignalAccessor(each)) return each();
374
+ if (typeof each === "function") return each();
375
+ return each;
376
+ }
377
+ function isDomNode(value) {
378
+ return typeof value === "object" && value !== null && "nodeType" in value && typeof value.nodeType === "number";
379
+ }
380
+ function toNodes(value) {
381
+ if (value == null || value === false || value === true) return [];
382
+ if (typeof value === "string" || typeof value === "number") return [document.createTextNode(String(value))];
383
+ if (isDomNode(value)) return [value];
384
+ if (Array.isArray(value)) return value.flatMap(toNodes);
385
+ if (typeof value === "function") return toNodes(value());
386
+ return [];
387
+ }
388
+ /**
389
+ * Reactive list. On the server, maps `each` once for `hono/jsx` SSR.
390
+ */
391
+ function For(props) {
392
+ if (typeof document === "undefined") return readEach(props.each).map((item, i) => props.children(item, i));
393
+ const frag = document.createDocumentFragment();
394
+ const start = document.createComment("vino-for");
395
+ const end = document.createComment("/vino-for");
396
+ frag.append(start, end);
397
+ let current = [];
398
+ createEffect(() => {
399
+ for (const node of current) node.parentNode?.removeChild(node);
400
+ current = [];
401
+ const parent = start.parentNode;
402
+ if (!parent) return;
403
+ const nodes = readEach(props.each).flatMap((item, i) => toNodes(props.children(item, i)));
404
+ for (const node of nodes) parent.insertBefore(node, end);
405
+ current = nodes;
406
+ });
407
+ return frag;
408
+ }
409
+ //#endregion
410
+ //#region src/client/runtime/hooks.ts
411
+ /** Signal-backed `useState` compatible with stock JSX (`value={count}`, `{count}` as child). */
412
+ function useState(initial) {
413
+ return createSignal(typeof initial === "function" ? initial() : initial);
414
+ }
415
+ /** Runs `fn` in a reactive effect. Dependency arrays are ignored (Solid-style). */
416
+ function useEffect(fn, _deps) {
417
+ createEffect(() => {
418
+ const cleanup = fn();
419
+ if (typeof cleanup === "function") onCleanup(cleanup);
420
+ });
421
+ }
422
+ function useRef(initial) {
423
+ return { current: initial };
424
+ }
425
+ //#endregion
426
+ export { For, Show, createClient, createEffect, createRoot, createSignal, hydratePage, isSignalAccessor, mountComponent, onCleanup, untrack, useEffect, useRef, useState };
286
427
 
287
428
  //# sourceMappingURL=client.mjs.map