vinojs 0.0.1 → 0.1.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 +172 -0
- package/dist/client.d.mts +41 -0
- package/dist/client.d.mts.map +1 -0
- package/dist/client.mjs +287 -0
- package/dist/client.mjs.map +1 -0
- package/dist/compile-Lxl_tdxL.mjs +155 -0
- package/dist/compile-Lxl_tdxL.mjs.map +1 -0
- package/dist/component-wrap.d.mts +10 -0
- package/dist/component-wrap.d.mts.map +1 -0
- package/dist/component-wrap.mjs +33 -0
- package/dist/component-wrap.mjs.map +1 -0
- package/dist/constants-BVT2bJEd.mjs +27 -0
- package/dist/constants-BVT2bJEd.mjs.map +1 -0
- package/dist/definePage-Ba4r-TFh.mjs +51 -0
- package/dist/definePage-Ba4r-TFh.mjs.map +1 -0
- package/dist/types-CQITdkpF.d.mts +77 -0
- package/dist/types-CQITdkpF.d.mts.map +1 -0
- package/dist/vino.d.mts +144 -0
- package/dist/vino.d.mts.map +1 -0
- package/dist/vino.mjs +197 -0
- package/dist/vino.mjs.map +1 -0
- package/dist/vite.d.mts +10 -0
- package/dist/vite.d.mts.map +1 -0
- package/dist/vite.mjs +729 -0
- package/dist/vite.mjs.map +1 -0
- package/examples/basic/dist/client/.assetsignore +2 -0
- package/examples/basic/dist/client/about.html +1 -0
- package/examples/basic/dist/client/admin.html +1 -0
- package/examples/basic/dist/client/assets/Counter-BvqMZKON.js +1 -0
- package/examples/basic/dist/client/assets/_slug_-CYpOSbrn.js +1 -0
- package/examples/basic/dist/client/assets/about-BZXRzUyv.js +1 -0
- package/examples/basic/dist/client/assets/admin-DRE7uh2s.js +1 -0
- package/examples/basic/dist/client/assets/client-B6nFkut5.js +1 -0
- package/examples/basic/dist/client/assets/client-pages-Bc6cNZAm.js +2 -0
- package/examples/basic/dist/client/assets/components-DX9r6vHn.js +1 -0
- package/examples/basic/dist/client/assets/components-DsWJ6F73.js +2 -0
- package/examples/basic/dist/client/assets/counter-DBpvln_F.js +1 -0
- package/examples/basic/dist/client/assets/dom-B5sHIJ1E.js +1 -0
- package/examples/basic/dist/client/assets/honoto-9UtdLp4J.js +33 -0
- package/examples/basic/dist/client/assets/honoto-client-DEh86_WZ.css +1 -0
- package/examples/basic/dist/client/assets/honoto-client.js +2 -0
- package/examples/basic/dist/client/assets/hooks-Cz6AT21d.js +1 -0
- package/examples/basic/dist/client/assets/jsx-dev-runtime-92VDsVyo.js +1 -0
- package/examples/basic/dist/client/counter.html +1 -0
- package/examples/basic/dist/client/index.html +1 -0
- package/examples/basic/dist/client/jobs.html +1 -0
- package/examples/basic/dist/ssr/.vite/manifest.json +140 -0
- package/examples/basic/dist/ssr/assets/_slug_-CY3-kffX.js +46 -0
- package/examples/basic/dist/ssr/assets/about-CTDjNhkO.js +15 -0
- package/examples/basic/dist/ssr/assets/admin-DvS-UGJx.js +26 -0
- package/examples/basic/dist/ssr/assets/constants-UoriGYcX-CUecBFMT.js +6 -0
- package/examples/basic/dist/ssr/assets/counter-DfIbc9Sp.js +30 -0
- package/examples/basic/dist/ssr/assets/honoto-CA313cVy.js +2129 -0
- package/examples/basic/dist/ssr/assets/jobs-D9hh-UO9.js +42 -0
- package/examples/basic/dist/ssr/assets/jsx-CgYLQazd.js +1080 -0
- package/examples/basic/dist/ssr/assets/jsx-dev-runtime-BlUda7H8.js +742 -0
- package/examples/basic/dist/ssr/assets/jsx-renderer-CzZcZPnS.js +65 -0
- package/examples/basic/dist/ssr/assets/layout-DYvLwr2u.js +53 -0
- package/examples/basic/dist/ssr/assets/pages-DB6jeyrt.js +53 -0
- package/examples/basic/dist/ssr/assets/rolldown-runtime-D7D4PA-g.js +13 -0
- package/examples/basic/dist/ssr/assets/ssr-hooks-CBQr0cdH.js +7 -0
- package/examples/basic/dist/ssr/index.js +22 -0
- package/examples/basic/dist/ssr/wrangler.json +1 -0
- package/examples/full/dist/client/.assetsignore +2 -0
- package/examples/full/dist/client/assets/BookmarkButton-py0xGxgl.js +1 -0
- package/examples/full/dist/client/assets/_slug_-DZUFsE9O.js +1 -0
- package/examples/full/dist/client/assets/auth-client-D2Wtuobe.js +1 -0
- package/examples/full/dist/client/assets/bun-sqlite-dialect-C7ftEUSI-CsHdUKBY.js +1 -0
- package/examples/full/dist/client/assets/client-B6nFkut5.js +1 -0
- package/examples/full/dist/client/assets/client-pages-fshKs747.js +2 -0
- package/examples/full/dist/client/assets/components-DX9r6vHn.js +1 -0
- package/examples/full/dist/client/assets/components-S_-rWrKi.js +2 -0
- package/examples/full/dist/client/assets/d1-sqlite-dialect-BZmaVTp8-FPCaMZqX.js +1 -0
- package/examples/full/dist/client/assets/default-query-compiler-CUdqIu1h.js +1 -0
- package/examples/full/dist/client/assets/dist-BoGTNvkC.js +1 -0
- package/examples/full/dist/client/assets/dom-DCd3wYW7.js +1 -0
- package/examples/full/dist/client/assets/factory-Bgf4EtLx.js +8 -0
- package/examples/full/dist/client/assets/honoto-client-BSvNAnxN.css +1 -0
- package/examples/full/dist/client/assets/honoto-client.js +2 -0
- package/examples/full/dist/client/assets/hooks-Cz6AT21d.js +1 -0
- package/examples/full/dist/client/assets/jsx-dev-runtime-92VDsVyo.js +1 -0
- package/examples/full/dist/client/assets/kysely-adapter-BHhasAnc.js +1 -0
- package/examples/full/dist/client/assets/kysely-migration-tables-DFfmKhq--U5ujlMwE.js +1 -0
- package/examples/full/dist/client/assets/node-sqlite-dialect-DJDXH5Ot.js +1 -0
- package/examples/full/dist/client/assets/pages-CUQx6Eus.js +1 -0
- package/examples/full/dist/client/assets/saved-jIi74ncJ.js +1 -0
- package/examples/full/dist/client/assets/session-B-accyM3.js +611 -0
- package/examples/full/dist/client/assets/sign-in-BelI3gSs.js +1 -0
- package/examples/full/dist/client/assets/sign-up-pR94lfdu.js +1 -0
- package/examples/full/dist/client/index.html +1 -0
- package/examples/full/dist/client/saved.html +1 -0
- package/examples/full/dist/client/sign-in.html +1 -0
- package/examples/full/dist/client/sign-up.html +1 -0
- package/examples/full/dist/ssr/.dev.vars +1 -0
- package/examples/full/dist/ssr/.vite/manifest.json +219 -0
- package/examples/full/dist/ssr/assets/BookmarkButton-KS7z5_vi.js +298 -0
- package/examples/full/dist/ssr/assets/_slug_-rRStRaAN.js +89 -0
- package/examples/full/dist/ssr/assets/auth-DSFZ4iak.js +31434 -0
- package/examples/full/dist/ssr/assets/auth-client-BFSBVoML.js +894 -0
- package/examples/full/dist/ssr/assets/bun-sqlite-dialect-C7ftEUSI-BeXpIqKl.js +162 -0
- package/examples/full/dist/ssr/assets/compiled-query-DmoyCh28.js +8410 -0
- package/examples/full/dist/ssr/assets/d1-sqlite-dialect-BZmaVTp8-CjJdvL94.js +114 -0
- package/examples/full/dist/ssr/assets/default-query-compiler-D7xOzCCJ.js +1727 -0
- package/examples/full/dist/ssr/assets/dist-LmmDoNdk.js +6832 -0
- package/examples/full/dist/ssr/assets/dist-kFDN1l4Z.js +342 -0
- package/examples/full/dist/ssr/assets/factory-AD6s9SIW.js +2079 -0
- package/examples/full/dist/ssr/assets/jsx-renderer-mMLf5XLN.js +1926 -0
- package/examples/full/dist/ssr/assets/kysely-adapter-BCLenpM8.js +2 -0
- package/examples/full/dist/ssr/assets/kysely-migration-tables-DFfmKhq--BtVr6hRe.js +17 -0
- package/examples/full/dist/ssr/assets/layout-mVA7qP4j.js +44 -0
- package/examples/full/dist/ssr/assets/node-sqlite-dialect-D-wtYAD7.js +162 -0
- package/examples/full/dist/ssr/assets/pages-Do5kR_Ia.js +147 -0
- package/examples/full/dist/ssr/assets/saved-Dr4pP6B5.js +111 -0
- package/examples/full/dist/ssr/assets/session-BvIpX7tr.js +13 -0
- package/examples/full/dist/ssr/assets/sign-in-22pzTnJE.js +115 -0
- package/examples/full/dist/ssr/assets/sign-up-CN4j8Qg1.js +117 -0
- package/examples/full/dist/ssr/assets/sqlite-adapter-DVlms5wO.js +79 -0
- package/examples/full/dist/ssr/index.js +97 -0
- package/examples/full/dist/ssr/wrangler.json +1 -0
- package/package.json +62 -6
- package/index.js +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Vino
|
|
2
|
+
|
|
3
|
+
Hono + Vite meta-framework designed for Cloudflare Workers.
|
|
4
|
+
|
|
5
|
+
Early WIP - core changes will happen
|
|
6
|
+
|
|
7
|
+
- **Pages** — filesystem routes in `src/pages` using Nitro path syntax
|
|
8
|
+
- **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`
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add vinojs hono
|
|
19
|
+
pnpm add -D vite @cloudflare/vite-plugin wrangler
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick start
|
|
23
|
+
|
|
24
|
+
`src/server.ts` — you own the Hono app; extra routes stay on it:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { Hono } from "hono";
|
|
28
|
+
import { createVino, mountVino } from "vinojs";
|
|
29
|
+
import { api } from "./api";
|
|
30
|
+
|
|
31
|
+
const app = new Hono().route("/api", api);
|
|
32
|
+
mountVino(app);
|
|
33
|
+
export default app;
|
|
34
|
+
export type AppType = typeof app;
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`vite.config.ts`:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { defineConfig } from "vite";
|
|
41
|
+
import { cloudflare } from "@cloudflare/vite-plugin";
|
|
42
|
+
import { vino } from "vinojs/vite";
|
|
43
|
+
|
|
44
|
+
export default defineConfig({
|
|
45
|
+
plugins: [vino(), cloudflare({ viteEnvironment: { name: "ssr" } })],
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`wrangler.jsonc`:
|
|
50
|
+
|
|
51
|
+
```jsonc
|
|
52
|
+
{
|
|
53
|
+
"name": "my-app",
|
|
54
|
+
"main": "./src/server.ts",
|
|
55
|
+
"compatibility_date": "2026-08-18",
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Routing (`src/pages`)
|
|
60
|
+
|
|
61
|
+
| File | URL |
|
|
62
|
+
| ---------------------- | ------------- |
|
|
63
|
+
| `index.tsx` | `/` |
|
|
64
|
+
| `about.tsx` | `/about` |
|
|
65
|
+
| `(marketing)/jobs.tsx` | `/jobs` |
|
|
66
|
+
| `blog/[slug].tsx` | `/blog/:slug` |
|
|
67
|
+
| `docs/[...slug].tsx` | `/docs/*` |
|
|
68
|
+
|
|
69
|
+
The file **is** the page. Export a default component, `definePage(...)`, `defineServerPage(...)`, a Hono sub-app, or named `GET` / `POST` handlers.
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { definePage } from "vinojs";
|
|
73
|
+
import { useState } from "hono/jsx";
|
|
74
|
+
|
|
75
|
+
export default definePage(() => {
|
|
76
|
+
const [n, setN] = useState(0);
|
|
77
|
+
return <button onClick={() => setN(n + 1)}>{n}</button>;
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { definePage } from "vinojs";
|
|
83
|
+
|
|
84
|
+
export default definePage({
|
|
85
|
+
render: ({ data }) => <h1>{data.title}</h1>,
|
|
86
|
+
data: async (c) => ({ title: "Hello" }),
|
|
87
|
+
config: { prerender: "full", clientRouting: true },
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`definePage` SSRs with `hono/jsx`, then hydrates `#vino-page` with `hono/jsx/dom`. `data` and `config` are colocated on the page.
|
|
92
|
+
|
|
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'`).
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
// [slug].server.ts
|
|
97
|
+
import { defineServerFn } from "vinojs";
|
|
98
|
+
|
|
99
|
+
export default defineServerFn((c) => {
|
|
100
|
+
const slug = c.req.param("slug") ?? "";
|
|
101
|
+
return { slug, title: "Hello" };
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
// [slug].tsx
|
|
107
|
+
import { definePage } from "vinojs";
|
|
108
|
+
import data from "./[slug].server";
|
|
109
|
+
|
|
110
|
+
export default definePage({
|
|
111
|
+
data,
|
|
112
|
+
render: ({ data }) => <h1>{data.title}</h1>,
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`*.server.ts` files are not routes. Client bundles get stubs instead of the implementation.
|
|
117
|
+
|
|
118
|
+
Use `defineServerPage` for HTML that is never hydrated:
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import { defineServerPage } from "vinojs";
|
|
122
|
+
|
|
123
|
+
export default defineServerPage(() => <h1>About</h1>);
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`prerender` may be `'full'` (complete HTML at build), `'partial'` (layout shell + empty `#vino-page`; client fetches content), or `false`.
|
|
127
|
+
|
|
128
|
+
### Layouts (`src/layout`)
|
|
129
|
+
|
|
130
|
+
Layout files are compiled with the same path syntax as pages and wrap matching routes (root → leaf). `index.tsx` is the HTML document.
|
|
131
|
+
|
|
132
|
+
| File | Wraps |
|
|
133
|
+
| ----------- | --------------------- |
|
|
134
|
+
| `index.tsx` | every page |
|
|
135
|
+
| `blog.tsx` | `/blog` and `/blog/*` |
|
|
136
|
+
| `admin.tsx` | `/admin` |
|
|
137
|
+
|
|
138
|
+
### Hydratable pages
|
|
139
|
+
|
|
140
|
+
`definePage(...)` SSRs with `hono/jsx`, then hydrates `#vino-page` with `hono/jsx/dom`. `defineServerPage(...)` SSRs only.
|
|
141
|
+
|
|
142
|
+
### Hydratable components
|
|
143
|
+
|
|
144
|
+
Put widgets in `src/components/` (or name `$Widget.tsx` / `Widget.component.tsx`). They SSR as `<vino-component>` and hydrate on the client. Pass JSON-serializable props only.
|
|
145
|
+
|
|
146
|
+
### Navigation
|
|
147
|
+
|
|
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.
|
|
149
|
+
|
|
150
|
+
## API
|
|
151
|
+
|
|
152
|
+
- `createVino()` — `vinojs`
|
|
153
|
+
- `mountVino(app)` — `vinojs`
|
|
154
|
+
- `definePage(fn | { render, data?, config? })` — hydrate the page (`vinojs`)
|
|
155
|
+
- `defineServerPage(...)` — SSR only, never hydrate (`vinojs`)
|
|
156
|
+
- `defineServerFn(fn)` — server-only function; pass as `definePage({ data })` (`vinojs`)
|
|
157
|
+
- `createClient()` — `vinojs/client` (injected automatically)
|
|
158
|
+
- `vino({ pagesDir, layoutsDir, clientRouting, prerender })` — `vinojs/vite`
|
|
159
|
+
|
|
160
|
+
APIs stay on your Hono app. Use `hono/client` `hc` against those routes; page HTML is never fetched through `hc`.
|
|
161
|
+
|
|
162
|
+
## Example
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
pnpm install
|
|
166
|
+
pnpm build
|
|
167
|
+
pnpm --filter @vinojs/example-basic dev
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { u as VinoPayload } from "./types-CQITdkpF.mjs";
|
|
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
|
+
type ComponentLoaders = Record<string, () => Promise<Record<string, unknown>>>;
|
|
8
|
+
type ClientPageLoaders = Record<string, () => Promise<{
|
|
9
|
+
default?: unknown;
|
|
10
|
+
}>>;
|
|
11
|
+
type VinoHmrPayload = {
|
|
12
|
+
kind: "component";
|
|
13
|
+
id: string;
|
|
14
|
+
mod?: Record<string, unknown>;
|
|
15
|
+
} | {
|
|
16
|
+
kind: "client-page";
|
|
17
|
+
id: string;
|
|
18
|
+
mod?: {
|
|
19
|
+
default?: unknown;
|
|
20
|
+
};
|
|
21
|
+
} | {
|
|
22
|
+
kind: "ssr";
|
|
23
|
+
} | {
|
|
24
|
+
kind: "layout";
|
|
25
|
+
} | {
|
|
26
|
+
kind: "registry";
|
|
27
|
+
components?: ComponentLoaders;
|
|
28
|
+
clientPages?: ClientPageLoaders;
|
|
29
|
+
};
|
|
30
|
+
type HmrFn = (payload: VinoHmrPayload) => void;
|
|
31
|
+
/**
|
|
32
|
+
* Fill partial shells, hydrate `#vino-page` / `<vino-component>`, and intercept
|
|
33
|
+
* same-origin links with content-only swaps when `clientRouting` is enabled.
|
|
34
|
+
*/
|
|
35
|
+
declare function createClient(options?: CreateClientOptions): {
|
|
36
|
+
ready: Promise<VinoPayload | null>;
|
|
37
|
+
hmr: HmrFn;
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
export { type CreateClientOptions, type VinoHmrPayload, createClient };
|
|
41
|
+
//# sourceMappingURL=client.d.mts.map
|
|
@@ -0,0 +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"}
|
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
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 { s as resolvePageRender } from "./definePage-Ba4r-TFh.mjs";
|
|
3
|
+
//#region src/client/create-client.ts
|
|
4
|
+
const islands = /* @__PURE__ */ new Map();
|
|
5
|
+
let pageRoot = null;
|
|
6
|
+
let componentLoaders;
|
|
7
|
+
let clientPageLoaders;
|
|
8
|
+
let hmrListener;
|
|
9
|
+
const hmrPending = [];
|
|
10
|
+
const hmrGlobal = globalThis;
|
|
11
|
+
function dispatchHmr(payload) {
|
|
12
|
+
if (hmrListener) hmrListener(payload);
|
|
13
|
+
else hmrPending.push(payload);
|
|
14
|
+
}
|
|
15
|
+
hmrGlobal.__VINO_HMR__ = dispatchHmr;
|
|
16
|
+
function bindHmr(fn) {
|
|
17
|
+
hmrListener = fn;
|
|
18
|
+
const queued = hmrPending.splice(0);
|
|
19
|
+
for (const payload of queued) fn(payload);
|
|
20
|
+
}
|
|
21
|
+
function pruneDisconnected() {
|
|
22
|
+
for (const [el, rec] of islands) {
|
|
23
|
+
if (el.isConnected) continue;
|
|
24
|
+
rec.root?.unmount();
|
|
25
|
+
islands.delete(el);
|
|
26
|
+
}
|
|
27
|
+
if (pageRoot && !pageRoot.el.isConnected) {
|
|
28
|
+
pageRoot.root?.unmount();
|
|
29
|
+
pageRoot = null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function readPayload(root = document) {
|
|
33
|
+
const el = root.querySelector(`#${PAYLOAD_SCRIPT_ID}`);
|
|
34
|
+
if (!el?.textContent) return null;
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(el.textContent);
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function writePayload(payload) {
|
|
42
|
+
let el = document.getElementById(PAYLOAD_SCRIPT_ID);
|
|
43
|
+
if (!el) {
|
|
44
|
+
el = document.createElement("script");
|
|
45
|
+
el.id = PAYLOAD_SCRIPT_ID;
|
|
46
|
+
el.type = "application/json";
|
|
47
|
+
document.body.appendChild(el);
|
|
48
|
+
}
|
|
49
|
+
el.textContent = JSON.stringify(payload).replaceAll("<", "\\u003c");
|
|
50
|
+
}
|
|
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;
|
|
61
|
+
}
|
|
62
|
+
async function getClientPageLoaders() {
|
|
63
|
+
clientPageLoaders ??= (await import("virtual:vino/client-pages")).clientPages;
|
|
64
|
+
return clientPageLoaders;
|
|
65
|
+
}
|
|
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
|
+
}
|
|
74
|
+
if (options.hydrate) {
|
|
75
|
+
await options.hydrate(vnode, el);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const { hydrateRoot } = await loadDom();
|
|
79
|
+
return hydrateRoot(el, vnode);
|
|
80
|
+
}
|
|
81
|
+
async function hydrateComponents(options, root = document) {
|
|
82
|
+
const components = await getComponentLoaders();
|
|
83
|
+
const { createElement } = await loadDom();
|
|
84
|
+
const nodes = root.querySelectorAll(COMPONENT_TAG);
|
|
85
|
+
for (const el of nodes) {
|
|
86
|
+
const id = el.getAttribute("data-component");
|
|
87
|
+
const exportName = el.getAttribute("data-export") ?? "default";
|
|
88
|
+
const propsJson = el.getAttribute("data-props") ?? "{}";
|
|
89
|
+
if (!id) continue;
|
|
90
|
+
const loader = components[id];
|
|
91
|
+
if (!loader) {
|
|
92
|
+
console.warn(`[vino] missing hydratable component ${id}`);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const Component = (await loader())[exportName];
|
|
96
|
+
if (typeof Component !== "function") continue;
|
|
97
|
+
const props = JSON.parse(propsJson);
|
|
98
|
+
const vnode = vnodeFor(options, createElement, Component, props);
|
|
99
|
+
const previous = islands.get(el);
|
|
100
|
+
if (previous?.root) previous.root.unmount();
|
|
101
|
+
const mounted = await mountVnode(options, el, vnode, null);
|
|
102
|
+
islands.set(el, {
|
|
103
|
+
id,
|
|
104
|
+
exportName,
|
|
105
|
+
props,
|
|
106
|
+
root: mounted
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
async function hydrateClientPage(payload, options) {
|
|
111
|
+
if (!payload.clientPage) return;
|
|
112
|
+
const root = document.getElementById(PAGE_SLOT_ID);
|
|
113
|
+
if (!root) return;
|
|
114
|
+
const load = (await getClientPageLoaders())[payload.clientPage];
|
|
115
|
+
if (!load) return;
|
|
116
|
+
const mod = await load();
|
|
117
|
+
await renderClientPage(payload.clientPage, mod, payload, options, root);
|
|
118
|
+
}
|
|
119
|
+
async function renderClientPage(path, mod, payload, options, root) {
|
|
120
|
+
const Page = resolvePageRender(mod.default);
|
|
121
|
+
if (!Page) return;
|
|
122
|
+
const { createElement } = await loadDom();
|
|
123
|
+
const vnode = vnodeFor(options, createElement, Page, {
|
|
124
|
+
data: payload.data,
|
|
125
|
+
params: payload.params
|
|
126
|
+
});
|
|
127
|
+
const reuse = pageRoot?.el === root ? pageRoot.root : null;
|
|
128
|
+
if (pageRoot && pageRoot.el !== root) pageRoot.root?.unmount();
|
|
129
|
+
pageRoot = {
|
|
130
|
+
el: root,
|
|
131
|
+
path,
|
|
132
|
+
root: await mountVnode(options, root, vnode, reuse)
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
async function patchComponent(id, mod, options) {
|
|
136
|
+
const { createElement } = await loadDom();
|
|
137
|
+
for (const [el, rec] of islands) {
|
|
138
|
+
if (rec.id !== id) continue;
|
|
139
|
+
if (!el.isConnected) {
|
|
140
|
+
rec.root?.unmount();
|
|
141
|
+
islands.delete(el);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const Component = mod[rec.exportName];
|
|
145
|
+
if (typeof Component !== "function") continue;
|
|
146
|
+
rec.root = await mountVnode(options, el, vnodeFor(options, createElement, Component, rec.props), rec.root);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function patchClientPage(path, mod, options) {
|
|
150
|
+
const payload = readPayload();
|
|
151
|
+
if (!payload?.clientPage || payload.clientPage !== path) return;
|
|
152
|
+
const root = document.getElementById(PAGE_SLOT_ID);
|
|
153
|
+
if (!root) return;
|
|
154
|
+
await renderClientPage(path, mod, payload, options, root);
|
|
155
|
+
}
|
|
156
|
+
function syncNav(path) {
|
|
157
|
+
const links = document.querySelectorAll("nav a[href]");
|
|
158
|
+
for (const link of links) {
|
|
159
|
+
if (!(link instanceof HTMLAnchorElement)) continue;
|
|
160
|
+
const href = link.getAttribute("href");
|
|
161
|
+
if (!href || href.startsWith("http")) continue;
|
|
162
|
+
if (href === path || href !== "/" && (path === href || path.startsWith(`${href}/`))) link.setAttribute("aria-current", "page");
|
|
163
|
+
else link.removeAttribute("aria-current");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async function hydrateSlot(options, payload) {
|
|
167
|
+
pruneDisconnected();
|
|
168
|
+
if (payload) await hydrateClientPage(payload, options);
|
|
169
|
+
await hydrateComponents(options, document.getElementById("vino-page") ?? document);
|
|
170
|
+
if (payload?.path) syncNav(payload.path);
|
|
171
|
+
return payload;
|
|
172
|
+
}
|
|
173
|
+
function parseContentFragment(html) {
|
|
174
|
+
const doc = new DOMParser().parseFromString(`<!DOCTYPE html><html><body>${html}</body></html>`, "text/html");
|
|
175
|
+
const payload = readPayload(doc);
|
|
176
|
+
return {
|
|
177
|
+
slot: doc.getElementById(PAGE_SLOT_ID),
|
|
178
|
+
payload,
|
|
179
|
+
title: payload?.title
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async function fetchContent(href) {
|
|
183
|
+
const url = new URL(href, location.href);
|
|
184
|
+
return parseContentFragment(await (await fetch(url.href, { headers: { [NAV_HEADER]: "1" } })).text());
|
|
185
|
+
}
|
|
186
|
+
async function applyContent(href, push, options, fragment) {
|
|
187
|
+
const { slot: nextSlot, payload: nextPayload, title } = fragment;
|
|
188
|
+
if (nextPayload?.clientRouting === false && push) {
|
|
189
|
+
location.href = href;
|
|
190
|
+
return nextPayload;
|
|
191
|
+
}
|
|
192
|
+
const current = document.getElementById(PAGE_SLOT_ID);
|
|
193
|
+
if (current && nextSlot) current.replaceWith(nextSlot);
|
|
194
|
+
else if (nextSlot) document.body.appendChild(nextSlot);
|
|
195
|
+
if (nextPayload) writePayload(nextPayload);
|
|
196
|
+
if (title) document.title = title;
|
|
197
|
+
else if (nextPayload?.title) document.title = nextPayload.title;
|
|
198
|
+
const url = new URL(href, location.href);
|
|
199
|
+
if (push) history.pushState({ vino: true }, "", url.href);
|
|
200
|
+
return hydrateSlot(options, nextPayload ?? readPayload());
|
|
201
|
+
}
|
|
202
|
+
async function navigate(href, push, options) {
|
|
203
|
+
return applyContent(href, push, options, await fetchContent(href));
|
|
204
|
+
}
|
|
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
|
+
function bindRouting(options, payload) {
|
|
217
|
+
if (!payload?.clientRouting) return;
|
|
218
|
+
document.addEventListener("click", (event) => {
|
|
219
|
+
const target = event.target;
|
|
220
|
+
if (!(target instanceof Element)) return;
|
|
221
|
+
const anchor = target.closest("a");
|
|
222
|
+
if (!anchor || !shouldIntercept(anchor) || isModifiedClick(event)) return;
|
|
223
|
+
if (!document.getElementById("vino-page")) return;
|
|
224
|
+
event.preventDefault();
|
|
225
|
+
navigate(anchor.href, true, options);
|
|
226
|
+
});
|
|
227
|
+
window.addEventListener("popstate", () => {
|
|
228
|
+
navigate(location.href, false, options);
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
function isModifiedClick(event) {
|
|
232
|
+
return event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0;
|
|
233
|
+
}
|
|
234
|
+
function shouldIntercept(anchor) {
|
|
235
|
+
if (anchor.hasAttribute("download") || anchor.target && anchor.target !== "_self") return false;
|
|
236
|
+
if (anchor.origin !== location.origin) return false;
|
|
237
|
+
if (anchor.getAttribute("rel")?.includes("external")) return false;
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
async function handleHmr(payload, options) {
|
|
241
|
+
if (payload.kind === "registry") {
|
|
242
|
+
if (payload.components) componentLoaders = payload.components;
|
|
243
|
+
if (payload.clientPages) clientPageLoaders = payload.clientPages;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (payload.kind === "layout") {
|
|
247
|
+
location.reload();
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (payload.kind === "ssr") {
|
|
251
|
+
if (!document.getElementById("vino-page")) {
|
|
252
|
+
location.reload();
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
await navigate(location.href, false, options);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (payload.kind === "component" && payload.mod) {
|
|
259
|
+
await patchComponent(payload.id, payload.mod, options);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (payload.kind === "client-page" && payload.mod) await patchClientPage(payload.id, payload.mod, options);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Fill partial shells, hydrate `#vino-page` / `<vino-component>`, and intercept
|
|
266
|
+
* same-origin links with content-only swaps when `clientRouting` is enabled.
|
|
267
|
+
*/
|
|
268
|
+
function createClient(options = {}) {
|
|
269
|
+
const hmr = (payload) => {
|
|
270
|
+
handleHmr(payload, options);
|
|
271
|
+
};
|
|
272
|
+
bindHmr(hmr);
|
|
273
|
+
return {
|
|
274
|
+
ready: (async () => {
|
|
275
|
+
let payload = readPayload();
|
|
276
|
+
if (payload?.partial) payload = await fillPartialShell(options, payload) ?? payload;
|
|
277
|
+
else payload = await hydrateSlot(options, payload) ?? payload;
|
|
278
|
+
bindRouting(options, payload);
|
|
279
|
+
return payload;
|
|
280
|
+
})(),
|
|
281
|
+
hmr
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
//#endregion
|
|
285
|
+
export { createClient };
|
|
286
|
+
|
|
287
|
+
//# sourceMappingURL=client.mjs.map
|
|
@@ -0,0 +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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"}
|