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/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
|
-
- **
|
|
10
|
-
- **
|
|
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`
|
|
30
|
+
`src/server.ts`
|
|
25
31
|
|
|
26
32
|
```ts
|
|
27
33
|
import { Hono } from "hono";
|
|
28
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
91
|
+
config: { prerender: true, clientRouting: true },
|
|
86
92
|
data: async (c) => ({ title: "Hello" }),
|
|
87
|
-
|
|
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
|
|
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`
|
|
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 `
|
|
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 {
|
|
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
|
-
*
|
|
33
|
-
*
|
|
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
|
-
|
|
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
|
package/dist/client.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.mts","names":[],"sources":["../src/client/create-client.ts"
|
|
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-
|
|
2
|
-
import {
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
63
|
-
clientPageLoaders
|
|
64
|
-
return clientPageLoaders;
|
|
122
|
+
function getClientPageLoaders() {
|
|
123
|
+
return clientPageLoaders ?? {};
|
|
65
124
|
}
|
|
66
|
-
function
|
|
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
|
-
|
|
79
|
-
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
266
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|