@pajecawav/yamf 0.0.4 → 0.0.6

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 (46) hide show
  1. package/README.md +262 -0
  2. package/dist/client/index.d.ts +1 -1
  3. package/dist/components/Head.d.ts +0 -1
  4. package/dist/components/Head.d.ts.map +1 -1
  5. package/dist/context/ssr.d.ts +1 -1
  6. package/dist/context/ssr.d.ts.map +1 -1
  7. package/dist/hooks/useEvent.d.ts +0 -1
  8. package/dist/hooks/useEvent.d.ts.map +1 -1
  9. package/dist/hooks/useHead.d.ts +0 -1
  10. package/dist/hooks/useHead.d.ts.map +1 -1
  11. package/dist/island/client.js +2 -0
  12. package/dist/island/client.js.map +1 -1
  13. package/dist/island/types.d.ts +1 -1
  14. package/dist/island/types.d.ts.map +1 -1
  15. package/dist/page.d.ts +2 -3
  16. package/dist/page.d.ts.map +1 -1
  17. package/dist/page.js +6 -1
  18. package/dist/page.js.map +1 -1
  19. package/dist/server/entry.d.mts +0 -1
  20. package/dist/server/entry.d.mts.map +1 -1
  21. package/dist/server/island/server.d.mts +0 -1
  22. package/dist/server/island/server.d.mts.map +1 -1
  23. package/dist/server/island/server.mjs.map +1 -1
  24. package/dist/server/island/types.d.mts +1 -1
  25. package/dist/server/island/types.d.mts.map +1 -1
  26. package/dist/server/shared/assets.d.mts.map +1 -1
  27. package/dist/server/shared/head.d.mts +0 -1
  28. package/dist/server/shared/head.d.mts.map +1 -1
  29. package/dist/shared/assets.d.ts.map +1 -1
  30. package/dist/shared/head.d.ts +0 -1
  31. package/dist/shared/head.d.ts.map +1 -1
  32. package/dist/vite/index.d.mts +0 -1
  33. package/dist/vite/index.d.mts.map +1 -1
  34. package/dist/vite/index.mjs +15 -1
  35. package/dist/vite/index.mjs.map +1 -1
  36. package/dist/vite/virtual-pages.mjs +3 -2
  37. package/dist/vite/virtual-pages.mjs.map +1 -1
  38. package/dist/vite/virtual-template.mjs +1 -0
  39. package/dist/vite/virtual-template.mjs.map +1 -1
  40. package/package.json +23 -20
  41. package/src/island/client.tsx +5 -2
  42. package/src/island/types.ts +8 -1
  43. package/src/page.tsx +5 -2
  44. package/src/vite/index.ts +17 -1
  45. package/src/vite/virtual-pages.ts +3 -2
  46. package/src/vite/virtual-template.ts +1 -2
package/README.md ADDED
@@ -0,0 +1,262 @@
1
+ # yamf
2
+
3
+ SSR meta-framework on top of [Vite](https://vite.dev), [Nitro](https://nitro.build/), and [Hono JSX](https://hono.dev/docs/guides/jsx). File-based routing for HTML pages, islands architecture for client interactivity, head/SEO via [unhead](https://unhead.unjs.io/), and full Nitro feature set (presets, caching, middleware, API routes).
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @pajecawav/yamf hono vite
9
+ # or
10
+ yarn add @pajecawav/yamf hono vite
11
+ # or
12
+ pnpm add @pajecawav/yamf hono vite
13
+ ```
14
+
15
+ ## Project structure
16
+
17
+ ```
18
+ src/
19
+ server.tsx # server entry with export default defineServerEntry(...)
20
+ client/index.ts # client entry with import "@pajecawav/yamf/client"
21
+ pages/*.page.tsx # file-based routes (.page suffix required)
22
+ root/index.tsx # optional root layout
23
+ template.html # optional HTML shell with <!--ssr-outlet-->
24
+ routes/ # optional nitro API routes
25
+ vite.config.ts
26
+ ```
27
+
28
+ ## Vite plugin
29
+
30
+ ```ts
31
+ // vite.config.ts
32
+ import yamf from "@pajecawav/yamf/vite";
33
+ import { defineConfig } from "vite";
34
+
35
+ export default defineConfig({
36
+ plugins: [yamf()],
37
+ });
38
+ ```
39
+
40
+ ## Server entry
41
+
42
+ ```tsx
43
+ // src/server.tsx
44
+ import { defineServerEntry } from "@pajecawav/yamf/server";
45
+
46
+ export default defineServerEntry({
47
+ head: {
48
+ titleTemplate: "%s | my app",
49
+ htmlAttrs: { lang: "en" },
50
+ },
51
+ });
52
+ ```
53
+
54
+ ## Pages
55
+
56
+ ```tsx
57
+ // src/pages/index.page.tsx
58
+ import { definePage } from "@pajecawav/yamf";
59
+
60
+ export default definePage({
61
+ render: (event, { head }) => {
62
+ head.push({ title: "Home" });
63
+
64
+ return <h1>Hello, {event.url.hostname}!</h1>;
65
+ },
66
+ });
67
+ ```
68
+
69
+ ### File routing
70
+
71
+ Files in `src/pages/` with `.page` suffix are mapped to routes following `nitro` conventions:
72
+
73
+ | File | Route |
74
+ | ------------------------- | --------------- |
75
+ | `index.page.tsx` | `/` |
76
+ | `about.page.tsx` | `/about` |
77
+ | `[owner].page.tsx` | `/:owner` |
78
+ | `post/[postId].page.tsx` | `/post/:postId` |
79
+ | `docs/[...rest].page.tsx` | `/docs/**` |
80
+
81
+ ### Redirects and non-HTML responses
82
+
83
+ ```tsx
84
+ import { definePage } from "@pajecawav/yamf";
85
+ import { HTTPResponse, redirect } from "nitro/h3";
86
+
87
+ export default definePage({
88
+ render: async () => {
89
+ return redirect("/calc");
90
+
91
+ // or
92
+
93
+ return new HTTPResponse(null, {
94
+ status: 302,
95
+ headers: { location: "/calc" },
96
+ });
97
+ },
98
+ });
99
+ ```
100
+
101
+ ## Islands
102
+
103
+ Any file matching `*.island.{tsx,ts,jsx,js}` is automatically wrapped. Each exported function becomes an island that server-renders inside `<yamf-island>` and hydrates on the client.
104
+
105
+ ```tsx
106
+ // src/components/Counter.island.tsx
107
+ import { type IslandProps, useHead } from "@pajecawav/yamf";
108
+ import { useState } from "hono/jsx";
109
+
110
+ export interface CounterProps extends IslandProps {
111
+ initialValue?: number;
112
+ }
113
+
114
+ export const Counter = ({ initialValue = 0 }: CounterProps) => {
115
+ const [value, setValue] = useState(initialValue);
116
+
117
+ useHead({ title: `Counter: ${value}` });
118
+
119
+ return <button onClick={() => setValue(value + 1)}>{value}</button>;
120
+ };
121
+ ```
122
+
123
+ ```tsx
124
+ // src/pages/index.page.tsx
125
+ import { Counter } from "~/components/Counter.island";
126
+
127
+ export default definePage({
128
+ render: () => (
129
+ <>
130
+ <Counter initialValue={2} />
131
+ <Counter initialValue={5} />
132
+ <Counter yamf-client="visible" />
133
+ <Counter yamf-client="skip" />
134
+ </>
135
+ ),
136
+ });
137
+ ```
138
+
139
+ ### Hydration directives
140
+
141
+ `yamf-client` prop controls when hydration happens:
142
+
143
+ | Value | Behavior |
144
+ | ---------------- | ----------------------------------- |
145
+ | `load` (default) | Hydrate immediately on connection. |
146
+ | `idle` | Defer via `requestIdleCallback`. |
147
+ | `visible` | Hydrate when scrolled into view. |
148
+ | `skip` | Server-rendered only, no hydration. |
149
+
150
+ Props are serialized with `devalue` (supports `Date`, `Map`, `Set`, `URL`, `RegExp`, `Error`, `BigInt`, cycles).
151
+
152
+ ## Client entry
153
+
154
+ ```ts
155
+ // src/client/index.ts
156
+ import "@pajecawav/yamf/client";
157
+ ```
158
+
159
+ Side-effect import. Registers the `yamf-island` custom element and initializes `window.__UNHEAD__`. Required for island hydration and client-side `useHead`.
160
+
161
+ ## Head and SEO
162
+
163
+ ```tsx
164
+ import { useHead, useSeoMeta } from "@pajecawav/yamf";
165
+
166
+ // In any component inside the render tree:
167
+ useHead({
168
+ title: "Page title",
169
+ meta: [{ name: "description", content: "..." }],
170
+ link: [{ rel: "canonical", href: "https://..." }],
171
+ });
172
+
173
+ useSeoMeta({
174
+ title: "Page title",
175
+ ogTitle: "Page title",
176
+ ogImage: "https://example.com/og.png",
177
+ });
178
+ ```
179
+
180
+ In the `render` function itself, use the `head` argument directly (SSR context is not set up yet):
181
+
182
+ ```tsx
183
+ export default definePage({
184
+ render: (event, { head }) => {
185
+ head.push({ title: "Page" });
186
+
187
+ return <Content />;
188
+ },
189
+ });
190
+ ```
191
+
192
+ Default head from `defineServerEntry` is applied first, then page-specific head overrides individual fields.
193
+
194
+ ## Root layout
195
+
196
+ ```tsx
197
+ // src/root/index.tsx
198
+ import type { PropsWithChildren } from "hono/jsx";
199
+ import { useEvent } from "@pajecawav/yamf";
200
+ import "./index.css";
201
+
202
+ export default function Root({ children }: PropsWithChildren) {
203
+ const event = useEvent();
204
+
205
+ return (
206
+ <>
207
+ <nav>...</nav>
208
+ <main>{children}</main>
209
+ </>
210
+ );
211
+ }
212
+ ```
213
+
214
+ Optional. Wraps every page's content. CSS imported here is included in the asset manifest automatically. `useEvent()` works here because root renders inside the SSR context.
215
+
216
+ ## HTML template
217
+
218
+ ```html
219
+ <!-- src/template.html -->
220
+ <!doctype html>
221
+ <html>
222
+ <head></head>
223
+ <body>
224
+ <!--ssr-outlet-->
225
+ </body>
226
+ </html>
227
+ ```
228
+
229
+ `<!--ssr-outlet-->` is replaced with rendered content. Head tags are injected by unhead. Falls back to a minimal default if the file is missing.
230
+
231
+ ## Hooks
232
+
233
+ - `useEvent()` — current `H3Event`. Works in components inside the render tree and root layout, not in `render` itself.
234
+ - `useSSRContext()` — returns `{ head, event } | null`.
235
+ - `useHead(input)` — push head tags.
236
+ - `useSeoMeta(input)` — shorthand for SEO meta.
237
+
238
+ ## API routes and error handling
239
+
240
+ Handled by Nitro directly. Place files in `src/routes/`:
241
+
242
+ ```ts
243
+ // src/routes/api/badge.get.ts
244
+ import { defineHandler, getQuery, setHeader } from "nitro/h3";
245
+
246
+ export default defineHandler(event => {
247
+ setHeader(event, "cache-control", "public, max-age=60");
248
+
249
+ return "Cached response";
250
+ });
251
+ ```
252
+
253
+ ```ts
254
+ // src/error.ts
255
+ import { defineErrorHandler } from "nitro";
256
+
257
+ export default defineErrorHandler(error => {
258
+ console.error(error);
259
+
260
+ return new Response(`${error.statusCode} ${error.statusMessage}`);
261
+ });
262
+ ```
@@ -1 +1 @@
1
- export { };
1
+ export {}
@@ -1,5 +1,4 @@
1
1
  import { ResolvableHead } from "unhead/types";
2
-
3
2
  //#region src/components/Head.d.ts
4
3
  type HeadProps = ResolvableHead;
5
4
  declare const Head: (props: HeadProps) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"Head.d.ts","names":[],"sources":["../../src/components/Head.tsx"],"mappings":";;;KAGY,SAAA,GAAY,cAAA;AAAA,cAEX,IAAA,GAAQ,KAAO,EAAA,SAAA"}
1
+ {"version":3,"file":"Head.d.ts","names":[],"sources":["../../src/components/Head.tsx"],"mappings":";;KAGY,YAAY;cAEX,OAAQ,OAAO"}
@@ -1,6 +1,6 @@
1
+ import "hono/jsx";
1
2
  import { ServerUnhead } from "unhead/server";
2
3
  import { H3Event } from "nitro";
3
-
4
4
  //#region src/context/ssr.d.ts
5
5
  interface SSRContextValue {
6
6
  head: ServerUnhead;
@@ -1 +1 @@
1
- {"version":3,"file":"ssr.d.ts","names":[],"sources":["../../src/context/ssr.ts"],"mappings":";;;;UAIU,eAAA;EACT,IAAA,EAAM,YAAA;EACN,KAAA,EAAO,OAAA;AAAA;AAAA,cAOK,aAAA,QAAoB,eAAA"}
1
+ {"version":3,"file":"ssr.d.ts","names":[],"sources":["../../src/context/ssr.ts"],"mappings":";;;;UAIU;EACT,MAAM;EACN,OAAO;;cAOK,qBAAoB"}
@@ -1,5 +1,4 @@
1
1
  import { H3Event } from "nitro";
2
-
3
2
  //#region src/hooks/useEvent.d.ts
4
3
  declare const useEvent: () => H3Event;
5
4
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"useEvent.d.ts","names":[],"sources":["../../src/hooks/useEvent.tsx"],"mappings":";;;cAGa,QAAA,QAAe,OAAA"}
1
+ {"version":3,"file":"useEvent.d.ts","names":[],"sources":["../../src/hooks/useEvent.tsx"],"mappings":";;cAGa,gBAAe"}
@@ -1,6 +1,5 @@
1
1
  import { ClientUnhead } from "unhead/client";
2
2
  import { ResolvableHead, UseSeoMetaInput } from "unhead/types";
3
-
4
3
  //#region src/hooks/useHead.d.ts
5
4
  declare global {
6
5
  interface Window {
@@ -1 +1 @@
1
- {"version":3,"file":"useHead.d.ts","names":[],"sources":["../../src/hooks/useHead.tsx"],"mappings":";;;;;YAOW,MAAA;IACT,UAAA,GAAa,YAAA;EAAA;AAAA;AAAA,cAQF,OAAA,GAAW,KAAQ,GAAA,cAAA;AAAA,cAYnB,UAAA,GAAc,KAAQ,GAAA,eAAA"}
1
+ {"version":3,"file":"useHead.d.ts","names":[],"sources":["../../src/hooks/useHead.tsx"],"mappings":";;;QAMe;YACJ;IACT,aAAa;;;cAQF,UAAW,QAAQ;cAYnB,aAAc,QAAQ"}
@@ -36,6 +36,7 @@ customElements.define("yamf-island", class extends HTMLElement {
36
36
  __island_raw_import__(src).then(hydrateIsland);
37
37
  };
38
38
  switch (islandClient) {
39
+ case "true":
39
40
  case "load":
40
41
  initIsland();
41
42
  break;
@@ -46,6 +47,7 @@ customElements.define("yamf-island", class extends HTMLElement {
46
47
  if (this.firstElementChild) observe(this.firstElementChild, initIsland);
47
48
  else initIsland();
48
49
  break;
50
+ case "false":
49
51
  case "skip": break;
50
52
  default: throw new Error(`Invalid island-client value: ${islandClient}`);
51
53
  }
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../../src/island/client.tsx"],"sourcesContent":["import { parse } from \"devalue\";\nimport type { FC } from \"hono/jsx\";\nimport { hydrateRoot } from \"hono/jsx/dom/client\";\nimport { withLeadingSlash } from \"ufo\";\nimport type { IslandClientDirective } from \"./types\";\n\ndeclare let __island_raw_import__: <T>(file: string) => Promise<T>;\n\nconst listeners = new WeakMap<Element, VoidFunction>();\n\nconst observer = new IntersectionObserver(entries => {\n\tfor (const entry of entries) {\n\t\tif (entry.isIntersecting) {\n\t\t\tlisteners.get(entry.target)?.();\n\t\t\tunobserve(entry.target);\n\t\t}\n\t}\n});\n\nconst observe = (target: Element, cb: VoidFunction) => {\n\tobserver.observe(target);\n\tlisteners.set(target, cb);\n};\n\nconst unobserve = (target: Element) => {\n\tobserver.unobserve(target);\n\tlisteners.delete(target);\n};\n\ncustomElements.define(\n\t\"yamf-island\",\n\tclass extends HTMLElement {\n\t\tpublic connectedCallback() {\n\t\t\tconst islandProps = parse(this.getAttribute(\"island-props\") ?? \"{}\");\n\t\t\tconst islandSrc = this.getAttribute(\"island-src\");\n\t\t\tconst islandEntry = this.getAttribute(\"island-entry\");\n\t\t\t// oxlint-disable-next-line typescript/no-unsafe-type-assertion\n\t\t\tconst islandClient = (this.getAttribute(\"island-client\") ??\n\t\t\t\t\"load\") as IslandClientDirective;\n\n\t\t\tif (!islandSrc) {\n\t\t\t\tthrow new Error(\"Missing island-src attribute\");\n\t\t\t}\n\n\t\t\tif (!islandEntry) {\n\t\t\t\tthrow new Error(\"Missing island-entry attribute\");\n\t\t\t}\n\n\t\t\tconst hydrateIsland = (mod: Record<string, FC>) => {\n\t\t\t\tconst Comp = mod[islandEntry];\n\n\t\t\t\tif (!Comp) {\n\t\t\t\t\tthrow new Error(`Missing island entry ${islandEntry} in ${islandSrc}`);\n\t\t\t\t}\n\n\t\t\t\thydrateRoot(this, <Comp {...islandProps} />);\n\t\t\t};\n\n\t\t\tconst initIsland = () => {\n\t\t\t\tconst src = withLeadingSlash(islandSrc);\n\t\t\t\tvoid __island_raw_import__<Record<string, FC>>(src).then(hydrateIsland);\n\t\t\t};\n\n\t\t\tswitch (islandClient) {\n\t\t\t\tcase \"load\":\n\t\t\t\t\tinitIsland();\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"idle\":\n\t\t\t\t\trequestIdleCallback(initIsland);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"visible\":\n\t\t\t\t\t// yamf-island has `display: contents` which breaks IntersectionObserver\n\t\t\t\t\t// so we have to observe the first child instead if it exists\n\t\t\t\t\tif (this.firstElementChild) {\n\t\t\t\t\t\tobserve(this.firstElementChild, initIsland);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tinitIsland();\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"skip\":\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\t// oxlint-disable-next-line typescript/restrict-template-expressions\n\t\t\t\t\tthrow new Error(`Invalid island-client value: ${islandClient}`);\n\t\t\t}\n\t\t}\n\n\t\tpublic disconnectedCallback() {\n\t\t\tunobserve(this);\n\t\t}\n\t},\n);\n"],"mappings":";;;;;AAQA,MAAM,4BAAY,IAAI,QAA+B;AAErD,MAAM,WAAW,IAAI,sBAAqB,YAAW;CACpD,KAAK,MAAM,SAAS,SACnB,IAAI,MAAM,gBAAgB;EACzB,UAAU,IAAI,MAAM,MAAM,CAAC,GAAG;EAC9B,UAAU,MAAM,MAAM;CACvB;AAEF,CAAC;AAED,MAAM,WAAW,QAAiB,OAAqB;CACtD,SAAS,QAAQ,MAAM;CACvB,UAAU,IAAI,QAAQ,EAAE;AACzB;AAEA,MAAM,aAAa,WAAoB;CACtC,SAAS,UAAU,MAAM;CACzB,UAAU,OAAO,MAAM;AACxB;AAEA,eAAe,OACd,eACA,cAAc,YAAY;CACzB,oBAA2B;EAC1B,MAAM,cAAc,MAAM,KAAK,aAAa,cAAc,KAAK,IAAI;EACnE,MAAM,YAAY,KAAK,aAAa,YAAY;EAChD,MAAM,cAAc,KAAK,aAAa,cAAc;EAEpD,MAAM,eAAgB,KAAK,aAAa,eAAe,KACtD;EAED,IAAI,CAAC,WACJ,MAAM,IAAI,MAAM,8BAA8B;EAG/C,IAAI,CAAC,aACJ,MAAM,IAAI,MAAM,gCAAgC;EAGjD,MAAM,iBAAiB,QAA4B;GAClD,MAAM,OAAO,IAAI;GAEjB,IAAI,CAAC,MACJ,MAAM,IAAI,MAAM,wBAAwB,YAAY,MAAM,WAAW;GAGtE,YAAY,MAAM,oBAAC,MAAD,EAAM,GAAI,YAAc,CAAA,CAAC;EAC5C;EAEA,MAAM,mBAAmB;GACxB,MAAM,MAAM,iBAAiB,SAAS;GACtC,sBAA+C,GAAG,CAAC,CAAC,KAAK,aAAa;EACvE;EAEA,QAAQ,cAAR;GACC,KAAK;IACJ,WAAW;IACX;GACD,KAAK;IACJ,oBAAoB,UAAU;IAC9B;GACD,KAAK;IAGJ,IAAI,KAAK,mBACR,QAAQ,KAAK,mBAAmB,UAAU;SAE1C,WAAW;IAEZ;GACD,KAAK,QACJ;GACD,SAEC,MAAM,IAAI,MAAM,gCAAgC,cAAc;EAChE;CACD;CAEA,uBAA8B;EAC7B,UAAU,IAAI;CACf;AACD,CACD"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../../src/island/client.tsx"],"sourcesContent":["import { parse } from \"devalue\";\nimport type { FC } from \"hono/jsx\";\nimport { hydrateRoot } from \"hono/jsx/dom/client\";\nimport { withLeadingSlash } from \"ufo\";\nimport type { IslandClientDirectiveSerialized } from \"./types\";\n\ndeclare let __island_raw_import__: <T>(file: string) => Promise<T>;\n\nconst listeners = new WeakMap<Element, VoidFunction>();\n\nconst observer = new IntersectionObserver(entries => {\n\tfor (const entry of entries) {\n\t\tif (entry.isIntersecting) {\n\t\t\tlisteners.get(entry.target)?.();\n\t\t\tunobserve(entry.target);\n\t\t}\n\t}\n});\n\nconst observe = (target: Element, cb: VoidFunction) => {\n\tobserver.observe(target);\n\tlisteners.set(target, cb);\n};\n\nconst unobserve = (target: Element) => {\n\tobserver.unobserve(target);\n\tlisteners.delete(target);\n};\n\ncustomElements.define(\n\t\"yamf-island\",\n\tclass extends HTMLElement {\n\t\tpublic connectedCallback() {\n\t\t\tconst islandProps = parse(this.getAttribute(\"island-props\") ?? \"{}\");\n\t\t\tconst islandSrc = this.getAttribute(\"island-src\");\n\t\t\tconst islandEntry = this.getAttribute(\"island-entry\");\n\t\t\t// oxlint-disable-next-line typescript/no-unsafe-type-assertion\n\t\t\tconst islandClient = (this.getAttribute(\"island-client\") ??\n\t\t\t\t\"load\") as IslandClientDirectiveSerialized;\n\n\t\t\tif (!islandSrc) {\n\t\t\t\tthrow new Error(\"Missing island-src attribute\");\n\t\t\t}\n\n\t\t\tif (!islandEntry) {\n\t\t\t\tthrow new Error(\"Missing island-entry attribute\");\n\t\t\t}\n\n\t\t\tconst hydrateIsland = (mod: Record<string, FC>) => {\n\t\t\t\tconst Comp = mod[islandEntry];\n\n\t\t\t\tif (!Comp) {\n\t\t\t\t\tthrow new Error(`Missing island entry ${islandEntry} in ${islandSrc}`);\n\t\t\t\t}\n\n\t\t\t\thydrateRoot(this, <Comp {...islandProps} />);\n\t\t\t};\n\n\t\t\tconst initIsland = () => {\n\t\t\t\tconst src = withLeadingSlash(islandSrc);\n\t\t\t\tvoid __island_raw_import__<Record<string, FC>>(src).then(hydrateIsland);\n\t\t\t};\n\n\t\t\tswitch (islandClient) {\n\t\t\t\tcase \"true\":\n\t\t\t\tcase \"load\":\n\t\t\t\t\tinitIsland();\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"idle\":\n\t\t\t\t\trequestIdleCallback(initIsland);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"visible\":\n\t\t\t\t\t// yamf-island has `display: contents` which breaks IntersectionObserver\n\t\t\t\t\t// so we have to observe the first child instead if it exists\n\t\t\t\t\tif (this.firstElementChild) {\n\t\t\t\t\t\tobserve(this.firstElementChild, initIsland);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tinitIsland();\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"false\":\n\t\t\t\tcase \"skip\":\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tislandClient satisfies never;\n\t\t\t\t\t// oxlint-disable-next-line typescript/restrict-template-expressions\n\t\t\t\t\tthrow new Error(`Invalid island-client value: ${islandClient}`);\n\t\t\t}\n\t\t}\n\n\t\tpublic disconnectedCallback() {\n\t\t\tunobserve(this);\n\t\t}\n\t},\n);\n"],"mappings":";;;;;AAQA,MAAM,4BAAY,IAAI,QAA+B;AAErD,MAAM,WAAW,IAAI,sBAAqB,YAAW;CACpD,KAAK,MAAM,SAAS,SACnB,IAAI,MAAM,gBAAgB;EACzB,UAAU,IAAI,MAAM,MAAM,CAAC,GAAG;EAC9B,UAAU,MAAM,MAAM;CACvB;AAEF,CAAC;AAED,MAAM,WAAW,QAAiB,OAAqB;CACtD,SAAS,QAAQ,MAAM;CACvB,UAAU,IAAI,QAAQ,EAAE;AACzB;AAEA,MAAM,aAAa,WAAoB;CACtC,SAAS,UAAU,MAAM;CACzB,UAAU,OAAO,MAAM;AACxB;AAEA,eAAe,OACd,eACA,cAAc,YAAY;CACzB,oBAA2B;EAC1B,MAAM,cAAc,MAAM,KAAK,aAAa,cAAc,KAAK,IAAI;EACnE,MAAM,YAAY,KAAK,aAAa,YAAY;EAChD,MAAM,cAAc,KAAK,aAAa,cAAc;EAEpD,MAAM,eAAgB,KAAK,aAAa,eAAe,KACtD;EAED,IAAI,CAAC,WACJ,MAAM,IAAI,MAAM,8BAA8B;EAG/C,IAAI,CAAC,aACJ,MAAM,IAAI,MAAM,gCAAgC;EAGjD,MAAM,iBAAiB,QAA4B;GAClD,MAAM,OAAO,IAAI;GAEjB,IAAI,CAAC,MACJ,MAAM,IAAI,MAAM,wBAAwB,YAAY,MAAM,WAAW;GAGtE,YAAY,MAAM,oBAAC,MAAD,EAAM,GAAI,YAAc,CAAA,CAAC;EAC5C;EAEA,MAAM,mBAAmB;GACxB,MAAM,MAAM,iBAAiB,SAAS;GACtC,sBAA+C,GAAG,CAAC,CAAC,KAAK,aAAa;EACvE;EAEA,QAAQ,cAAR;GACC,KAAK;GACL,KAAK;IACJ,WAAW;IACX;GACD,KAAK;IACJ,oBAAoB,UAAU;IAC9B;GACD,KAAK;IAGJ,IAAI,KAAK,mBACR,QAAQ,KAAK,mBAAmB,UAAU;SAE1C,WAAW;IAEZ;GACD,KAAK;GACL,KAAK,QACJ;GACD,SAGC,MAAM,IAAI,MAAM,gCAAgC,cAAc;EAChE;CACD;CAEA,uBAA8B;EAC7B,UAAU,IAAI;CACf;AACD,CACD"}
@@ -1,5 +1,5 @@
1
1
  //#region src/island/types.d.ts
2
- type IslandClientDirective = "load" | "idle" | "visible" | "skip";
2
+ type IslandClientDirective = "load" | "idle" | "visible" | "skip" | boolean;
3
3
  interface IslandProps {
4
4
  "yamf-client"?: IslandClientDirective;
5
5
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../../src/island/types.ts"],"mappings":";KAAY,qBAAA;AAAA,UAEK,WAAA;EAChB,aAAA,GAAgB,qBAAA;AAAA"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../src/island/types.ts"],"mappings":";KAAY;UASK;EAChB,gBAAgB"}
package/dist/page.d.ts CHANGED
@@ -4,7 +4,6 @@ import { Child } from "hono/jsx";
4
4
  import { EventHandlerResponse, H3Event, HTTPResponse } from "nitro/h3";
5
5
  import { Unhead } from "unhead/server";
6
6
  import { UseSeoMetaInput } from "unhead/types";
7
-
8
7
  //#region src/page.d.ts
9
8
  type PageHandler = (event: H3Event, params: {
10
9
  assets: ImportAssetsResult;
@@ -13,12 +12,12 @@ type PageHandler = (event: H3Event, params: {
13
12
  type PageRenderer = (event: H3Event, params: {
14
13
  head: Unhead;
15
14
  seoHead: (input: UseSeoMetaInput) => void;
16
- }) => HTTPResponse | Promise<HTTPResponse> | Child | Promise<Child>;
15
+ }) => HTTPResponse | Child | Promise<Child | HTTPResponse>;
17
16
  interface DefinePageOptions {
18
17
  render: PageRenderer;
19
18
  stream?: boolean;
20
19
  }
21
20
  declare const definePage: (options: DefinePageOptions) => PageHandler;
22
21
  //#endregion
23
- export { PageHandler, definePage };
22
+ export { PageHandler, PageRenderer, definePage };
24
23
  //# sourceMappingURL=page.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"page.d.ts","names":[],"sources":["../src/page.tsx"],"mappings":";;;;;;;;KAgBY,WAAA,IACX,KAAA,EAAO,OAAA,EACP,MAAA;EACC,MAAA,EAAQ,kBAAA;EACR,IAAA,GAAO,QAAA;AAAA,MAEJ,oBAAA;AAAA,KAEO,YAAA,IACX,KAAA,EAAO,OAAA,EACP,MAAA;EACC,IAAA,EAAM,MAAA;EACN,OAAA,GAAU,KAAA,EAAO,eAAA;AAAA,MAEd,YAAA,GAAe,OAAA,CAAQ,YAAA,IAAgB,KAAA,GAAQ,OAAA,CAAQ,KAAA;AAAA,UAElD,iBAAA;EACT,MAAA,EAAQ,YAAA;EACR,MAAA;AAAA;AAAA,cAGY,UAAA,GAAc,OAAA,EAAS,iBAAA,KAAoB,WAAA"}
1
+ {"version":3,"file":"page.d.ts","names":[],"sources":["../src/page.tsx"],"mappings":";;;;;;;KAiBY,eACX,OAAO,SACP;EACC,QAAQ;EACR,OAAO;MAEJ;KAEO,gBACX,OAAO,SACP;EACC,MAAM;EACN,UAAU,OAAO;MAEd,eAAe,QAAQ,QAAQ,QAAQ;UAElC;EACT,QAAQ;EACR;;cAGY,aAAc,SAAS,sBAAoB"}
package/dist/page.js CHANGED
@@ -8,6 +8,7 @@ import { transformHtmlTemplate } from "unhead/server";
8
8
  import { createStreamableHead, wrapStream } from "unhead/stream/server";
9
9
  import { Root } from "virtual:yamf:root";
10
10
  import { template } from "virtual:yamf:template";
11
+ import { Router } from "wouter";
11
12
  import { jsx } from "hono/jsx/jsx-runtime";
12
13
  //#region src/page.tsx
13
14
  const definePage = (options) => {
@@ -39,7 +40,11 @@ const definePage = (options) => {
39
40
  head,
40
41
  event
41
42
  },
42
- children: /* @__PURE__ */ jsx(Root$1, { children: content })
43
+ children: /* @__PURE__ */ jsx(Router, {
44
+ ssrPath: event.url.pathname,
45
+ ssrSearch: event.url.search,
46
+ children: /* @__PURE__ */ jsx(Root$1, { children: content })
47
+ })
43
48
  });
44
49
  const responseInit = { headers: { "Content-Type": "text/html; charset=utf-8" } };
45
50
  if (!options.stream) return new Hono().get("/", async (c) => withServerTiming(event, "#render", async () => {
package/dist/page.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"page.js","names":["Root","RootComponent"],"sources":["../src/page.tsx"],"sourcesContent":["import { Fragment, type Child } from \"hono/jsx\";\nimport { renderToReadableStream } from \"hono/jsx/streaming\";\nimport { Hono } from \"hono/tiny\";\nimport type { EventHandlerResponse, H3Event } from \"nitro/h3\";\nimport { HTTPResponse, withServerTiming } from \"nitro/h3\";\nimport { useSeoMeta } from \"unhead\";\nimport type { Unhead } from \"unhead/server\";\nimport { transformHtmlTemplate } from \"unhead/server\";\nimport { createStreamableHead, wrapStream } from \"unhead/stream/server\";\nimport type { ResolvableLink, UseSeoMetaInput } from \"unhead/types\";\nimport { Root as RootComponent } from \"virtual:yamf:root\";\nimport { template } from \"virtual:yamf:template\";\nimport { SSRContext } from \"./context/ssr\";\nimport type { ImportAssetsResult } from \"./shared/assets\";\nimport { YamfHead } from \"./shared/head\";\n\nexport type PageHandler = (\n\tevent: H3Event,\n\tparams: {\n\t\tassets: ImportAssetsResult;\n\t\thead?: YamfHead;\n\t},\n) => EventHandlerResponse;\n\nexport type PageRenderer = (\n\tevent: H3Event,\n\tparams: {\n\t\thead: Unhead;\n\t\tseoHead: (input: UseSeoMetaInput) => void;\n\t},\n) => HTTPResponse | Promise<HTTPResponse> | Child | Promise<Child>;\n\ninterface DefinePageOptions {\n\trender: PageRenderer;\n\tstream?: boolean;\n}\n\nexport const definePage = (options: DefinePageOptions): PageHandler => {\n\treturn async (event, { assets, head: headInit }) => {\n\t\tconst { head } = createStreamableHead({ init: [headInit] });\n\t\tconst seoHead = (input?: UseSeoMetaInput) => useSeoMeta(head, input);\n\t\tseoHead(headInit?.seo);\n\n\t\thead.push({\n\t\t\tlink: [\n\t\t\t\t...assets.js.map((attrs): ResolvableLink => ({ rel: \"modulepreload\", ...attrs })),\n\t\t\t\t...assets.css.map((attrs): ResolvableLink => ({ rel: \"stylesheet\", ...attrs })),\n\t\t\t],\n\t\t\tscript: [{ type: \"module\", src: assets.entry }],\n\t\t});\n\n\t\tconst content = await options.render(event, { head, seoHead });\n\n\t\tif (content instanceof HTTPResponse) {\n\t\t\treturn content;\n\t\t}\n\n\t\tconst Root = RootComponent ?? Fragment;\n\n\t\tconst App = async () => (\n\t\t\t<SSRContext value={{ head, event }}>\n\t\t\t\t<Root>{content}</Root>\n\t\t\t</SSRContext>\n\t\t);\n\n\t\tconst responseInit = {\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"text/html; charset=utf-8\",\n\t\t\t},\n\t\t};\n\n\t\tif (!options.stream) {\n\t\t\t// TODO: figure out how expensive this is\n\t\t\treturn new Hono()\n\t\t\t\t.get(\"/\", async c =>\n\t\t\t\t\twithServerTiming(event, \"#render\", async () => {\n\t\t\t\t\t\tconst response = await c.html(<App />);\n\n\t\t\t\t\t\tlet html = await response.text();\n\n\t\t\t\t\t\thtml = transformHtmlTemplate(\n\t\t\t\t\t\t\thead,\n\t\t\t\t\t\t\ttemplate.replace(\"<!--ssr-outlet-->\", html ?? \"\"),\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\treturn new Response(html, responseInit);\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t\t.request(\"/\");\n\t\t}\n\n\t\tconst stream = wrapStream(head, renderToReadableStream(<App />), template);\n\n\t\treturn new Response(stream, responseInit);\n\t};\n};\n"],"mappings":";;;;;;;;;;;;AAqCA,MAAa,cAAc,YAA4C;CACtE,OAAO,OAAO,OAAO,EAAE,QAAQ,MAAM,eAAe;EACnD,MAAM,EAAE,SAAS,qBAAqB,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;EAC1D,MAAM,WAAW,UAA4B,WAAW,MAAM,KAAK;EACnE,QAAQ,UAAU,GAAG;EAErB,KAAK,KAAK;GACT,MAAM,CACL,GAAG,OAAO,GAAG,KAAK,WAA2B;IAAE,KAAK;IAAiB,GAAG;GAAM,EAAE,GAChF,GAAG,OAAO,IAAI,KAAK,WAA2B;IAAE,KAAK;IAAc,GAAG;GAAM,EAAE,CAC/E;GACA,QAAQ,CAAC;IAAE,MAAM;IAAU,KAAK,OAAO;GAAM,CAAC;EAC/C,CAAC;EAED,MAAM,UAAU,MAAM,QAAQ,OAAO,OAAO;GAAE;GAAM;EAAQ,CAAC;EAE7D,IAAI,mBAAmB,cACtB,OAAO;EAGR,MAAMA,SAAOC,QAAiB;EAE9B,MAAM,MAAM,YACX,oBAAC,YAAD;GAAY,OAAO;IAAE;IAAM;GAAM;aAChC,oBAACD,QAAD,EAAA,UAAO,QAAc,CAAA;EACV,CAAA;EAGb,MAAM,eAAe,EACpB,SAAS,EACR,gBAAgB,2BACjB,EACD;EAEA,IAAI,CAAC,QAAQ,QAEZ,OAAO,IAAI,KAAK,CAAC,CACf,IAAI,KAAK,OAAM,MACf,iBAAiB,OAAO,WAAW,YAAY;GAG9C,IAAI,OAAO,OAAM,MAFM,EAAE,KAAK,oBAAC,KAAD,CAAM,CAAA,CAAC,EAAA,CAEX,KAAK;GAE/B,OAAO,sBACN,MACA,SAAS,QAAQ,qBAAqB,QAAQ,EAAE,CACjD;GAEA,OAAO,IAAI,SAAS,MAAM,YAAY;EACvC,CAAC,CACF,CAAC,CACA,QAAQ,GAAG;EAGd,MAAM,SAAS,WAAW,MAAM,uBAAuB,oBAAC,KAAD,CAAM,CAAA,CAAC,GAAG,QAAQ;EAEzE,OAAO,IAAI,SAAS,QAAQ,YAAY;CACzC;AACD"}
1
+ {"version":3,"file":"page.js","names":["Root","RootComponent"],"sources":["../src/page.tsx"],"sourcesContent":["import { Fragment, type Child } from \"hono/jsx\";\nimport { renderToReadableStream } from \"hono/jsx/streaming\";\nimport { Hono } from \"hono/tiny\";\nimport type { EventHandlerResponse, H3Event } from \"nitro/h3\";\nimport { HTTPResponse, withServerTiming } from \"nitro/h3\";\nimport { useSeoMeta } from \"unhead\";\nimport type { Unhead } from \"unhead/server\";\nimport { transformHtmlTemplate } from \"unhead/server\";\nimport { createStreamableHead, wrapStream } from \"unhead/stream/server\";\nimport type { ResolvableLink, UseSeoMetaInput } from \"unhead/types\";\nimport { Root as RootComponent } from \"virtual:yamf:root\";\nimport { template } from \"virtual:yamf:template\";\nimport { Router } from \"wouter\";\nimport { SSRContext } from \"./context/ssr\";\nimport type { ImportAssetsResult } from \"./shared/assets\";\nimport { YamfHead } from \"./shared/head\";\n\nexport type PageHandler = (\n\tevent: H3Event,\n\tparams: {\n\t\tassets: ImportAssetsResult;\n\t\thead?: YamfHead;\n\t},\n) => EventHandlerResponse;\n\nexport type PageRenderer = (\n\tevent: H3Event,\n\tparams: {\n\t\thead: Unhead;\n\t\tseoHead: (input: UseSeoMetaInput) => void;\n\t},\n) => HTTPResponse | Child | Promise<Child | HTTPResponse>;\n\ninterface DefinePageOptions {\n\trender: PageRenderer;\n\tstream?: boolean;\n}\n\nexport const definePage = (options: DefinePageOptions): PageHandler => {\n\treturn async (event, { assets, head: headInit }) => {\n\t\tconst { head } = createStreamableHead({ init: [headInit] });\n\t\tconst seoHead = (input?: UseSeoMetaInput) => useSeoMeta(head, input);\n\t\tseoHead(headInit?.seo);\n\n\t\thead.push({\n\t\t\tlink: [\n\t\t\t\t...assets.js.map((attrs): ResolvableLink => ({ rel: \"modulepreload\", ...attrs })),\n\t\t\t\t...assets.css.map((attrs): ResolvableLink => ({ rel: \"stylesheet\", ...attrs })),\n\t\t\t],\n\t\t\tscript: [{ type: \"module\", src: assets.entry }],\n\t\t});\n\n\t\tconst content = await options.render(event, { head, seoHead });\n\n\t\tif (content instanceof HTTPResponse) {\n\t\t\treturn content;\n\t\t}\n\n\t\tconst Root = RootComponent ?? Fragment;\n\n\t\tconst App = async () => (\n\t\t\t<SSRContext value={{ head, event }}>\n\t\t\t\t<Router ssrPath={event.url.pathname} ssrSearch={event.url.search}>\n\t\t\t\t\t<Root>{content}</Root>\n\t\t\t\t</Router>\n\t\t\t</SSRContext>\n\t\t);\n\n\t\tconst responseInit = {\n\t\t\theaders: {\n\t\t\t\t\"Content-Type\": \"text/html; charset=utf-8\",\n\t\t\t},\n\t\t};\n\n\t\tif (!options.stream) {\n\t\t\t// TODO: figure out how expensive this is\n\t\t\treturn new Hono()\n\t\t\t\t.get(\"/\", async c =>\n\t\t\t\t\twithServerTiming(event, \"#render\", async () => {\n\t\t\t\t\t\tconst response = await c.html(<App />);\n\n\t\t\t\t\t\tlet html = await response.text();\n\n\t\t\t\t\t\thtml = transformHtmlTemplate(\n\t\t\t\t\t\t\thead,\n\t\t\t\t\t\t\ttemplate.replace(\"<!--ssr-outlet-->\", html ?? \"\"),\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\treturn new Response(html, responseInit);\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t\t.request(\"/\");\n\t\t}\n\n\t\tconst stream = wrapStream(head, renderToReadableStream(<App />), template);\n\n\t\treturn new Response(stream, responseInit);\n\t};\n};\n"],"mappings":";;;;;;;;;;;;;AAsCA,MAAa,cAAc,YAA4C;CACtE,OAAO,OAAO,OAAO,EAAE,QAAQ,MAAM,eAAe;EACnD,MAAM,EAAE,SAAS,qBAAqB,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;EAC1D,MAAM,WAAW,UAA4B,WAAW,MAAM,KAAK;EACnE,QAAQ,UAAU,GAAG;EAErB,KAAK,KAAK;GACT,MAAM,CACL,GAAG,OAAO,GAAG,KAAK,WAA2B;IAAE,KAAK;IAAiB,GAAG;GAAM,EAAE,GAChF,GAAG,OAAO,IAAI,KAAK,WAA2B;IAAE,KAAK;IAAc,GAAG;GAAM,EAAE,CAC/E;GACA,QAAQ,CAAC;IAAE,MAAM;IAAU,KAAK,OAAO;GAAM,CAAC;EAC/C,CAAC;EAED,MAAM,UAAU,MAAM,QAAQ,OAAO,OAAO;GAAE;GAAM;EAAQ,CAAC;EAE7D,IAAI,mBAAmB,cACtB,OAAO;EAGR,MAAMA,SAAOC,QAAiB;EAE9B,MAAM,MAAM,YACX,oBAAC,YAAD;GAAY,OAAO;IAAE;IAAM;GAAM;GAChC,UAAA,oBAAC,QAAD;IAAQ,SAAS,MAAM,IAAI;IAAU,WAAW,MAAM,IAAI;IACzD,UAAA,oBAACD,QAAD,EAAA,UAAO,QAAc,CAAA;GACd,CAAA;EACG,CAAA;EAGb,MAAM,eAAe,EACpB,SAAS,EACR,gBAAgB,2BACjB,EACD;EAEA,IAAI,CAAC,QAAQ,QAEZ,OAAO,IAAI,KAAK,CAAC,CACf,IAAI,KAAK,OAAM,MACf,iBAAiB,OAAO,WAAW,YAAY;GAG9C,IAAI,OAAO,OAAM,MAFM,EAAE,KAAK,oBAAC,KAAD,CAAM,CAAA,CAAC,EAAA,CAEX,KAAK;GAE/B,OAAO,sBACN,MACA,SAAS,QAAQ,qBAAqB,QAAQ,EAAE,CACjD;GAEA,OAAO,IAAI,SAAS,MAAM,YAAY;EACvC,CAAC,CACF,CAAC,CACA,QAAQ,GAAG;EAGd,MAAM,SAAS,WAAW,MAAM,uBAAuB,oBAAC,KAAD,CAAM,CAAA,CAAC,GAAG,QAAQ;EAEzE,OAAO,IAAI,SAAS,QAAQ,YAAY;CACzC;AACD"}
@@ -1,6 +1,5 @@
1
1
  import { YamfHead } from "./shared/head.mjs";
2
2
  import { EventHandlerRequest, EventHandlerWithFetch, H3Event } from "nitro/h3";
3
-
4
3
  //#region src/server/entry.d.ts
5
4
  type ServerEntry = EventHandlerWithFetch<EventHandlerRequest, Promise<unknown>>;
6
5
  interface DefineServerEntryOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"entry.d.mts","names":[],"sources":["../../src/server/entry.tsx"],"mappings":";;;;KA8CY,WAAA,GAAc,qBAAA,CAAsB,mBAAA,EAAqB,OAAA;AAAA,UAEpD,wBAAA;EAChB,IAAA,GAAO,QAAA,KAAa,KAAA,EAAO,OAAA,KAAY,QAAA;EACvC,iBAAA;AAAA;AAAA,cAGY,iBAAA,GAAqB,OAAA,GAAU,wBAAA,KAA2B,WAAA"}
1
+ {"version":3,"file":"entry.d.mts","names":[],"sources":["../../src/server/entry.tsx"],"mappings":";;;KA8CY,cAAc,sBAAsB,qBAAqB;UAEpD;EAChB,OAAO,aAAa,OAAO,YAAY;EACvC;;cAGY,oBAAqB,UAAU,6BAA2B"}
@@ -1,7 +1,6 @@
1
1
  import { ImportAssetsResultRaw } from "../shared/assets.mjs";
2
2
  import { IslandClientDirective } from "./types.mjs";
3
3
  import { Child, FC } from "hono/jsx";
4
-
5
4
  //#region src/island/server.d.ts
6
5
  declare module "hono/jsx" {
7
6
  namespace JSX {
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.mts","names":[],"sources":["../../../src/island/server.tsx"],"mappings":";;;;;;YAMW,GAAA;IAAA,UACC,iBAAA;MACT,aAAA;QACC,cAAA;QACA,YAAA;QACA,cAAA;QACA,eAAA,EAAiB,qBAAA;QACjB,QAAA,EAAU,KAAA;QACV,KAAA,GAAQ,GAAA,CAAI,aAAA;MAAA;IAAA;EAAA;AAAA;AAAA,cAMH,YAAA,GACZ,SAAA,EAAW,EAAA,EACX,UAAA,UACA,MAAA,EAAQ,qBAAA,KACN,EAAA"}
1
+ {"version":3,"file":"server.d.mts","names":[],"sources":["../../../src/island/server.tsx"],"mappings":";;;;;YAMW;cACC;MACT;QACC;QACA;QACA;QACA,iBAAiB;QACjB,UAAU;QACV,QAAQ,IAAI;;;;;cAMH,eACZ,WAAW,IACX,oBACA,QAAQ,0BACN"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.mjs","names":[],"sources":["../../../src/island/server.tsx"],"sourcesContent":["import { stringify } from \"devalue\";\nimport type { Child, FC } from \"hono/jsx\";\nimport type { ImportAssetsResultRaw } from \"#/shared/assets\";\nimport type { IslandClientDirective } from \"./types\";\n\ndeclare module \"hono/jsx\" {\n\tnamespace JSX {\n\t\tinterface IntrinsicElements {\n\t\t\t\"yamf-island\": {\n\t\t\t\t\"island-props\"?: string;\n\t\t\t\t\"island-src\": string;\n\t\t\t\t\"island-entry\": string;\n\t\t\t\t\"island-client\": IslandClientDirective;\n\t\t\t\tchildren: Child;\n\t\t\t\tstyle?: JSX.CSSProperties;\n\t\t\t};\n\t\t}\n\t}\n}\n\nexport const createIsland = (\n\tComponent: FC,\n\texportName: string,\n\tassets: ImportAssetsResultRaw,\n): FC => {\n\tconst ComponentWrapper: FC & { name: string } = props => {\n\t\tif (!assets.entry) {\n\t\t\tthrow new Error(`Missing island entry for island ${Component.name}`);\n\t\t}\n\n\t\treturn (\n\t\t\t<yamf-island\n\t\t\t\tisland-props={stringify(props)}\n\t\t\t\tisland-src={assets.entry}\n\t\t\t\tisland-entry={exportName}\n\t\t\t\t// oxlint-disable-next-line typescript/no-unsafe-type-assertion\n\t\t\t\tisland-client={props[\"yamf-client\"] as IslandClientDirective}\n\t\t\t\t// TODO: export CSS?\n\t\t\t\tstyle={{ display: \"contents\" }}\n\t\t\t>\n\t\t\t\t<Component {...props} />\n\t\t\t</yamf-island>\n\t\t);\n\t};\n\n\tObject.defineProperty(ComponentWrapper, \"name\", {\n\t\tvalue: Component.name,\n\t});\n\n\treturn ComponentWrapper;\n};\n"],"mappings":";;;AAoBA,MAAa,gBACZ,WACA,YACA,WACQ;CACR,MAAM,oBAA0C,UAAS;EACxD,IAAI,CAAC,OAAO,OACX,MAAM,IAAI,MAAM,mCAAmC,UAAU,MAAM;EAGpE,OACC,oBAAC,eAAD;GACC,gBAAc,UAAU,KAAK;GAC7B,cAAY,OAAO;GACnB,gBAAc;GAEd,iBAAe,MAAM;GAErB,OAAO,EAAE,SAAS,WAAW;aAE7B,oBAAC,WAAD,EAAW,GAAI,MAAQ,CAAA;EACX,CAAA;CAEf;CAEA,OAAO,eAAe,kBAAkB,QAAQ,EAC/C,OAAO,UAAU,KAClB,CAAC;CAED,OAAO;AACR"}
1
+ {"version":3,"file":"server.mjs","names":[],"sources":["../../../src/island/server.tsx"],"sourcesContent":["import { stringify } from \"devalue\";\nimport type { Child, FC } from \"hono/jsx\";\nimport type { ImportAssetsResultRaw } from \"#/shared/assets\";\nimport type { IslandClientDirective } from \"./types\";\n\ndeclare module \"hono/jsx\" {\n\tnamespace JSX {\n\t\tinterface IntrinsicElements {\n\t\t\t\"yamf-island\": {\n\t\t\t\t\"island-props\"?: string;\n\t\t\t\t\"island-src\": string;\n\t\t\t\t\"island-entry\": string;\n\t\t\t\t\"island-client\": IslandClientDirective;\n\t\t\t\tchildren: Child;\n\t\t\t\tstyle?: JSX.CSSProperties;\n\t\t\t};\n\t\t}\n\t}\n}\n\nexport const createIsland = (\n\tComponent: FC,\n\texportName: string,\n\tassets: ImportAssetsResultRaw,\n): FC => {\n\tconst ComponentWrapper: FC & { name: string } = props => {\n\t\tif (!assets.entry) {\n\t\t\tthrow new Error(`Missing island entry for island ${Component.name}`);\n\t\t}\n\n\t\treturn (\n\t\t\t<yamf-island\n\t\t\t\tisland-props={stringify(props)}\n\t\t\t\tisland-src={assets.entry}\n\t\t\t\tisland-entry={exportName}\n\t\t\t\t// oxlint-disable-next-line typescript/no-unsafe-type-assertion\n\t\t\t\tisland-client={props[\"yamf-client\"] as IslandClientDirective}\n\t\t\t\t// TODO: export CSS?\n\t\t\t\tstyle={{ display: \"contents\" }}\n\t\t\t>\n\t\t\t\t<Component {...props} />\n\t\t\t</yamf-island>\n\t\t);\n\t};\n\n\tObject.defineProperty(ComponentWrapper, \"name\", {\n\t\tvalue: Component.name,\n\t});\n\n\treturn ComponentWrapper;\n};\n"],"mappings":";;;AAoBA,MAAa,gBACZ,WACA,YACA,WACQ;CACR,MAAM,oBAA0C,UAAS;EACxD,IAAI,CAAC,OAAO,OACX,MAAM,IAAI,MAAM,mCAAmC,UAAU,MAAM;EAGpE,OACC,oBAAC,eAAD;GACC,gBAAc,UAAU,KAAK;GAC7B,cAAY,OAAO;GACnB,gBAAc;GAEd,iBAAe,MAAM;GAErB,OAAO,EAAE,SAAS,WAAW;GAE7B,UAAA,oBAAC,WAAD,EAAW,GAAI,MAAQ,CAAA;EACX,CAAA;CAEf;CAEA,OAAO,eAAe,kBAAkB,QAAQ,EAC/C,OAAO,UAAU,KAClB,CAAC;CAED,OAAO;AACR"}
@@ -1,5 +1,5 @@
1
1
  //#region src/island/types.d.ts
2
- type IslandClientDirective = "load" | "idle" | "visible" | "skip";
2
+ type IslandClientDirective = "load" | "idle" | "visible" | "skip" | boolean;
3
3
  //#endregion
4
4
  export { IslandClientDirective };
5
5
  //# sourceMappingURL=types.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/island/types.ts"],"mappings":";KAAY,qBAAA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/island/types.ts"],"mappings":";KAAY"}
@@ -1 +1 @@
1
- {"version":3,"file":"assets.d.mts","names":[],"sources":["../../../src/shared/assets.ts"],"mappings":";KAKY,qBAAA;EACX,KAAA;EACA,EAAA;IAAM,IAAA;EAAA;EACN,GAAA;IAAO,IAAA;IAAc,kBAAA;EAAA;AAAA"}
1
+ {"version":3,"file":"assets.d.mts","names":[],"sources":["../../../src/shared/assets.ts"],"mappings":";KAKY;EACX;EACA;IAAM;;EACN;IAAO;IAAc"}
@@ -1,5 +1,4 @@
1
1
  import { ResolvableHead, UseSeoMetaInput } from "unhead/types";
2
-
3
2
  //#region src/shared/head.d.ts
4
3
  type YamfHead = ResolvableHead & {
5
4
  seo?: UseSeoMetaInput;
@@ -1 +1 @@
1
- {"version":3,"file":"head.d.mts","names":[],"sources":["../../../src/shared/head.ts"],"mappings":";;;KAEY,QAAA,GAAW,cAAA;EAAmB,GAAA,GAAM,eAAA;AAAA"}
1
+ {"version":3,"file":"head.d.mts","names":[],"sources":["../../../src/shared/head.ts"],"mappings":";;KAEY,WAAW;EAAmB,MAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"assets.d.ts","names":[],"sources":["../../src/shared/assets.ts"],"mappings":";KACY,kBAAA,GAAqB,qBAAA;EAChC,KAAA,IAAS,IAAA,EAAM,qBAAA,KAA0B,kBAAA;AAAA;AAAA,KAG9B,qBAAA;EACX,KAAA;EACA,EAAA;IAAM,IAAA;EAAA;EACN,GAAA;IAAO,IAAA;IAAc,kBAAA;EAAA;AAAA"}
1
+ {"version":3,"file":"assets.d.ts","names":[],"sources":["../../src/shared/assets.ts"],"mappings":";KACY,qBAAqB;EAChC,SAAS,MAAM,0BAA0B;;KAG9B;EACX;EACA;IAAM;;EACN;IAAO;IAAc"}
@@ -1,5 +1,4 @@
1
1
  import { ResolvableHead, UseSeoMetaInput } from "unhead/types";
2
-
3
2
  //#region src/shared/head.d.ts
4
3
  type YamfHead = ResolvableHead & {
5
4
  seo?: UseSeoMetaInput;
@@ -1 +1 @@
1
- {"version":3,"file":"head.d.ts","names":[],"sources":["../../src/shared/head.ts"],"mappings":";;;KAEY,QAAA,GAAW,cAAA;EAAmB,GAAA,GAAM,eAAA;AAAA"}
1
+ {"version":3,"file":"head.d.ts","names":[],"sources":["../../src/shared/head.ts"],"mappings":";;KAEY,WAAW;EAAmB,MAAM"}
@@ -1,6 +1,5 @@
1
1
  import { NitroPluginConfig } from "nitro/vite";
2
2
  import { PluginOption } from "vite";
3
-
4
3
  //#region src/vite/index.d.ts
5
4
  interface YamfOptions {
6
5
  nitro?: NitroPluginConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/vite/index.ts"],"mappings":";;;;UAUiB,WAAA;EAChB,KAAA,GAAQ,iBAAA;AAAA;AAAA,cAGH,IAAA,GAAQ,OAAA,GAAU,WAAA,KAAc,YAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/vite/index.ts"],"mappings":";;;UAUiB;EAChB,QAAQ;;cAGH,OAAQ,UAAU,gBAAc"}
@@ -12,7 +12,7 @@ const yamf = (options) => {
12
12
  name: "yamf:config",
13
13
  config() {
14
14
  return {
15
- ssr: { noExternal: ["@pajecawav/yamf"] },
15
+ ssr: { noExternal: true },
16
16
  optimizeDeps: { include: [
17
17
  "hono",
18
18
  "hono/jsx/dom/client",
@@ -20,6 +20,20 @@ const yamf = (options) => {
20
20
  "devalue",
21
21
  "ufo"
22
22
  ] },
23
+ resolve: { alias: [
24
+ {
25
+ find: "react",
26
+ replacement: "@hono/react-compat"
27
+ },
28
+ {
29
+ find: "react-dom",
30
+ replacement: "@hono/react-compat"
31
+ },
32
+ {
33
+ find: /^use-sync-external-store(?:\/shim(?:\/.*)?)?$/,
34
+ replacement: "@hono/react-compat"
35
+ }
36
+ ] },
23
37
  environments: {
24
38
  ...existsSync("./src/server.tsx") ? { ssr: { build: {
25
39
  cssCodeSplit: false,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/vite/index.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport type { NitroPluginConfig } from \"nitro/vite\";\nimport { nitro } from \"nitro/vite\";\nimport type { EnvironmentOptions, PluginOption } from \"vite\";\nimport { islands } from \"./islands\";\nimport { virtualAssets } from \"./virtual-assets\";\nimport { virtualPages } from \"./virtual-pages\";\nimport { virtualRoot } from \"./virtual-root\";\nimport { virtualTemplate } from \"./virtual-template\";\n\nexport interface YamfOptions {\n\tnitro?: NitroPluginConfig;\n}\n\nconst yamf = (options?: YamfOptions): PluginOption[] => {\n\tconst plugins: PluginOption[] = [];\n\n\t// TODO: extendable config\n\tplugins.push({\n\t\tname: \"yamf:config\",\n\t\tconfig() {\n\t\t\tconst ssrEnv: EnvironmentOptions = {\n\t\t\t\tbuild: {\n\t\t\t\t\t// TODO: figure out if this should be true\n\t\t\t\t\tcssCodeSplit: false,\n\t\t\t\t\trolldownOptions: {\n\t\t\t\t\t\tinput: \"./src/server.tsx\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\n\t\t\treturn {\n\t\t\t\tssr: {\n\t\t\t\t\t// we need to inline because otherwise fullstack plugin fails to build manifest for assets imports\n\t\t\t\t\tnoExternal: [\"@pajecawav/yamf\"],\n\t\t\t\t},\n\t\t\t\toptimizeDeps: {\n\t\t\t\t\tinclude: [\n\t\t\t\t\t\t\"hono\",\n\t\t\t\t\t\t\"hono/jsx/dom/client\",\n\t\t\t\t\t\t\"hono/jsx/jsx-runtime\",\n\t\t\t\t\t\t\"devalue\",\n\t\t\t\t\t\t\"ufo\",\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\tenvironments: {\n\t\t\t\t\t...(existsSync(\"./src/server.tsx\") ? { ssr: ssrEnv } : {}),\n\t\t\t\t\tclient: {\n\t\t\t\t\t\tbuild: {\n\t\t\t\t\t\t\trolldownOptions: {\n\t\t\t\t\t\t\t\tinput: \"./src/client/index.ts\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t});\n\n\tplugins.push(islands());\n\n\tplugins.push(virtualAssets());\n\tplugins.push(virtualPages());\n\tplugins.push(virtualTemplate());\n\tplugins.push(virtualRoot());\n\n\tplugins.push(\n\t\tnitro({\n\t\t\tserverDir: \"./src\",\n\t\t\trenderer: false,\n\t\t\t...options?.nitro,\n\t\t\tcompressPublicAssets: {\n\t\t\t\tgzip: true,\n\t\t\t\tbrotli: true,\n\t\t\t},\n\t\t\tpublicAssets: [\n\t\t\t\t{\n\t\t\t\t\tbaseURL: \"assets\",\n\t\t\t\t\tdir: \"./public/assets\",\n\t\t\t\t\tmaxAge: 365 * 24 * 60 * 60,\n\t\t\t\t},\n\t\t\t],\n\t\t}),\n\t);\n\n\treturn plugins;\n};\n\nexport default yamf;\n"],"mappings":";;;;;;;;AAcA,MAAM,QAAQ,YAA0C;CACvD,MAAM,UAA0B,CAAC;CAGjC,QAAQ,KAAK;EACZ,MAAM;EACN,SAAS;GAWR,OAAO;IACN,KAAK,EAEJ,YAAY,CAAC,iBAAiB,EAC/B;IACA,cAAc,EACb,SAAS;KACR;KACA;KACA;KACA;KACA;IACD,EACD;IACA,cAAc;KACb,GAAI,WAAW,kBAAkB,IAAI,EAAE,KAAK,EAxB7C,OAAO;MAEN,cAAc;MACd,iBAAiB,EAChB,OAAO,mBACR;KACD,EAkBkD,EAAE,IAAI,CAAC;KACxD,QAAQ,EACP,OAAO,EACN,iBAAiB,EAChB,OAAO,wBACR,EACD,EACD;IACD;GACD;EACD;CACD,CAAC;CAED,QAAQ,KAAK,QAAQ,CAAC;CAEtB,QAAQ,KAAK,cAAc,CAAC;CAC5B,QAAQ,KAAK,aAAa,CAAC;CAC3B,QAAQ,KAAK,gBAAgB,CAAC;CAC9B,QAAQ,KAAK,YAAY,CAAC;CAE1B,QAAQ,KACP,MAAM;EACL,WAAW;EACX,UAAU;EACV,GAAG,SAAS;EACZ,sBAAsB;GACrB,MAAM;GACN,QAAQ;EACT;EACA,cAAc,CACb;GACC,SAAS;GACT,KAAK;GACL,QAAQ,MAAM,KAAK,KAAK;EACzB,CACD;CACD,CAAC,CACF;CAEA,OAAO;AACR"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/vite/index.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport type { NitroPluginConfig } from \"nitro/vite\";\nimport { nitro } from \"nitro/vite\";\nimport type { EnvironmentOptions, PluginOption } from \"vite\";\nimport { islands } from \"./islands\";\nimport { virtualAssets } from \"./virtual-assets\";\nimport { virtualPages } from \"./virtual-pages\";\nimport { virtualRoot } from \"./virtual-root\";\nimport { virtualTemplate } from \"./virtual-template\";\n\nexport interface YamfOptions {\n\tnitro?: NitroPluginConfig;\n}\n\nconst yamf = (options?: YamfOptions): PluginOption[] => {\n\tconst plugins: PluginOption[] = [];\n\n\t// TODO: extendable config\n\tplugins.push({\n\t\tname: \"yamf:config\",\n\t\tconfig() {\n\t\t\tconst ssrEnv: EnvironmentOptions = {\n\t\t\t\tbuild: {\n\t\t\t\t\t// TODO: figure out if this should be true\n\t\t\t\t\tcssCodeSplit: false,\n\t\t\t\t\trolldownOptions: {\n\t\t\t\t\t\tinput: \"./src/server.tsx\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\n\t\t\treturn {\n\t\t\t\tssr: {\n\t\t\t\t\t// we need to inline because otherwise fullstack plugin fails to build manifest for assets imports\n\t\t\t\t\t// also, we need to bundle everything so that the `react` -> `@hono/react-compat` alias\n\t\t\t\t\t// is applied to all dependencies (e.g. wouter) in dev mode. Externalized deps are\n\t\t\t\t\t// loaded natively by Node, bypassing Vite's resolve.alias.\n\t\t\t\t\tnoExternal: true,\n\t\t\t\t},\n\t\t\t\toptimizeDeps: {\n\t\t\t\t\tinclude: [\n\t\t\t\t\t\t\"hono\",\n\t\t\t\t\t\t\"hono/jsx/dom/client\",\n\t\t\t\t\t\t\"hono/jsx/jsx-runtime\",\n\t\t\t\t\t\t\"devalue\",\n\t\t\t\t\t\t\"ufo\",\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\tresolve: {\n\t\t\t\t\talias: [\n\t\t\t\t\t\t{ find: \"react\", replacement: \"@hono/react-compat\" },\n\t\t\t\t\t\t{ find: \"react-dom\", replacement: \"@hono/react-compat\" },\n\t\t\t\t\t\t// use-sync-external-store is a CJS package that requires(\"react\").\n\t\t\t\t\t\t// Vite's SSR module runner can't process CJS, so we alias it to\n\t\t\t\t\t\t// @hono/react-compat which exports useSyncExternalStore from hono/jsx.\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tfind: /^use-sync-external-store(?:\\/shim(?:\\/.*)?)?$/,\n\t\t\t\t\t\t\treplacement: \"@hono/react-compat\",\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t\tenvironments: {\n\t\t\t\t\t...(existsSync(\"./src/server.tsx\") ? { ssr: ssrEnv } : {}),\n\t\t\t\t\tclient: {\n\t\t\t\t\t\tbuild: {\n\t\t\t\t\t\t\trolldownOptions: {\n\t\t\t\t\t\t\t\tinput: \"./src/client/index.ts\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t});\n\n\tplugins.push(islands());\n\n\tplugins.push(virtualAssets());\n\tplugins.push(virtualPages());\n\tplugins.push(virtualTemplate());\n\tplugins.push(virtualRoot());\n\n\tplugins.push(\n\t\tnitro({\n\t\t\tserverDir: \"./src\",\n\t\t\trenderer: false,\n\t\t\t...options?.nitro,\n\t\t\tcompressPublicAssets: {\n\t\t\t\tgzip: true,\n\t\t\t\tbrotli: true,\n\t\t\t},\n\t\t\tpublicAssets: [\n\t\t\t\t{\n\t\t\t\t\tbaseURL: \"assets\",\n\t\t\t\t\tdir: \"./public/assets\",\n\t\t\t\t\tmaxAge: 365 * 24 * 60 * 60,\n\t\t\t\t},\n\t\t\t],\n\t\t}),\n\t);\n\n\treturn plugins;\n};\n\nexport default yamf;\n"],"mappings":";;;;;;;;AAcA,MAAM,QAAQ,YAA0C;CACvD,MAAM,UAA0B,CAAC;CAGjC,QAAQ,KAAK;EACZ,MAAM;EACN,SAAS;GAWR,OAAO;IACN,KAAK,EAKJ,YAAY,KACb;IACA,cAAc,EACb,SAAS;KACR;KACA;KACA;KACA;KACA;IACD,EACD;IACA,SAAS,EACR,OAAO;KACN;MAAE,MAAM;MAAS,aAAa;KAAqB;KACnD;MAAE,MAAM;MAAa,aAAa;KAAqB;KAIvD;MACC,MAAM;MACN,aAAa;KACd;IACD,EACD;IACA,cAAc;KACb,GAAI,WAAW,kBAAkB,IAAI,EAAE,KAAK,EAxC7C,OAAO;MAEN,cAAc;MACd,iBAAiB,EAChB,OAAO,mBACR;KACD,EAkCkD,EAAE,IAAI,CAAC;KACxD,QAAQ,EACP,OAAO,EACN,iBAAiB,EAChB,OAAO,wBACR,EACD,EACD;IACD;GACD;EACD;CACD,CAAC;CAED,QAAQ,KAAK,QAAQ,CAAC;CAEtB,QAAQ,KAAK,cAAc,CAAC;CAC5B,QAAQ,KAAK,aAAa,CAAC;CAC3B,QAAQ,KAAK,gBAAgB,CAAC;CAC9B,QAAQ,KAAK,YAAY,CAAC;CAE1B,QAAQ,KACP,MAAM;EACL,WAAW;EACX,UAAU;EACV,GAAG,SAAS;EACZ,sBAAsB;GACrB,MAAM;GACN,QAAQ;EACT;EACA,cAAc,CACb;GACC,SAAS;GACT,KAAK;GACL,QAAQ,MAAM,KAAK,KAAK;EACzB,CACD;CACD,CAAC,CACF;CAEA,OAAO;AACR"}
@@ -11,11 +11,12 @@ const virtualPages = () => {
11
11
  load(id) {
12
12
  if (id !== resolvedVirtualModuleId) return;
13
13
  return js`
14
- const pages = import.meta.glob("/src/pages/**/*.{js,mjs,cjs,ts,mts,cts,tsx,jsx}", {
14
+ const pages = import.meta.glob("/src/pages/**/*.page.{js,mjs,cjs,ts,mts,cts,tsx,jsx}", {
15
15
  import: "default",
16
16
  });
17
+
17
18
  const assets = import.meta.glob(
18
- "/src/pages/**/*.{js,mjs,cjs,ts,mts,cts,tsx,jsx}",
19
+ "/src/pages/**/*.page.{js,mjs,cjs,ts,mts,cts,tsx,jsx}",
19
20
  {
20
21
  import: "default",
21
22
  query: "?assets=ssr",
@@ -1 +1 @@
1
- {"version":3,"file":"virtual-pages.mjs","names":[],"sources":["../../src/vite/virtual-pages.ts"],"sourcesContent":["import type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nexport const virtualPages = (): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:pages\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\treturn {\n\t\tname: \"yamf:virtual-pages\",\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tload(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\treturn js`\nconst pages = import.meta.glob(\"/src/pages/**/*.{js,mjs,cjs,ts,mts,cts,tsx,jsx}\", {\n\timport: \"default\",\n});\nconst assets = import.meta.glob(\n\t\"/src/pages/**/*.{js,mjs,cjs,ts,mts,cts,tsx,jsx}\",\n\t{\n\t\timport: \"default\",\n\t\tquery: \"?assets=ssr\",\n\t\teager: true,\n\t},\n);\n\nexport { pages, assets };\n`.trim();\n\t\t},\n\t};\n};\n"],"mappings":";;AAGA,MAAa,qBAA6B;CACzC,MAAM,kBAAkB;CACxB,MAAM,0BAA0B;CAEhC,OAAO;EACN,MAAM;EACN,UAAU,IAAI;GACb,IAAI,OAAO,iBACV,OAAO;EAIT;EACA,KAAK,IAAI;GACR,IAAI,OAAO,yBACV;GAGD,OAAO,EAAE;;;;;;;;;;;;;;EAcV,KAAK;EACL;CACD;AACD"}
1
+ {"version":3,"file":"virtual-pages.mjs","names":[],"sources":["../../src/vite/virtual-pages.ts"],"sourcesContent":["import type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nexport const virtualPages = (): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:pages\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\treturn {\n\t\tname: \"yamf:virtual-pages\",\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tload(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\treturn js`\nconst pages = import.meta.glob(\"/src/pages/**/*.page.{js,mjs,cjs,ts,mts,cts,tsx,jsx}\", {\n\timport: \"default\",\n});\n\nconst assets = import.meta.glob(\n\t\"/src/pages/**/*.page.{js,mjs,cjs,ts,mts,cts,tsx,jsx}\",\n\t{\n\t\timport: \"default\",\n\t\tquery: \"?assets=ssr\",\n\t\teager: true,\n\t},\n);\n\nexport { pages, assets };\n`.trim();\n\t\t},\n\t};\n};\n"],"mappings":";;AAGA,MAAa,qBAA6B;CACzC,MAAM,kBAAkB;CACxB,MAAM,0BAA0B;CAEhC,OAAO;EACN,MAAM;EACN,UAAU,IAAI;GACb,IAAI,OAAO,iBACV,OAAO;EAIT;EACA,KAAK,IAAI;GACR,IAAI,OAAO,yBACV;GAGD,OAAO,EAAE;;;;;;;;;;;;;;;EAeV,KAAK;EACL;CACD;AACD"}
@@ -34,6 +34,7 @@ const virtualTemplate = () => {
34
34
  if (relative(file, TEMPLATE_PATH) === "") {
35
35
  const mod = server.moduleGraph.getModuleById(resolvedVirtualModuleId);
36
36
  if (mod) server.moduleGraph.invalidateModule(mod);
37
+ server.ws.send({ type: "full-reload" });
37
38
  }
38
39
  }
39
40
  };
@@ -1 +1 @@
1
- {"version":3,"file":"virtual-template.mjs","names":[],"sources":["../../src/vite/virtual-template.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { relative } from \"node:path\";\nimport type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nconst TEMPLATE_PATH = \"./src/template.html\";\n\nconst DEFAULT_TEMPLATE = /* html */ `\n<!DOCTYPE html>\n<html>\n <head></head>\n <body>\n <!--ssr-outlet-->\n </body>\n</html>\n`.trim();\n\nexport const virtualTemplate = (): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:template\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\treturn {\n\t\tname: \"yamf:virtual-template\",\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tasync load(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst template = await readFile(TEMPLATE_PATH, \"utf8\");\n\n\t\t\t\treturn js`export const template = ${JSON.stringify(template)};`;\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n\t\t\t\t\treturn js`export const template = ${JSON.stringify(DEFAULT_TEMPLATE)};`;\n\t\t\t\t}\n\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t\thandleHotUpdate({ file, server }) {\n\t\t\tif (relative(file, TEMPLATE_PATH) === \"\") {\n\t\t\t\tconst mod = server.moduleGraph.getModuleById(resolvedVirtualModuleId);\n\n\t\t\t\tif (mod) {\n\t\t\t\t\tserver.moduleGraph.invalidateModule(mod);\n\t\t\t\t}\n\n\t\t\t\t// TODO: should reload?\n\t\t\t\t// server.ws.send({ type: \"full-reload\" });\n\t\t\t}\n\t\t},\n\t};\n};\n"],"mappings":";;;;AAKA,MAAM,gBAAgB;AAEtB,MAAM,mBAA8B;;;;;;;;EAQlC,KAAK;AAEP,MAAa,wBAAgC;CAC5C,MAAM,kBAAkB;CACxB,MAAM,0BAA0B;CAEhC,OAAO;EACN,MAAM;EACN,UAAU,IAAI;GACb,IAAI,OAAO,iBACV,OAAO;EAIT;EACA,MAAM,KAAK,IAAI;GACd,IAAI,OAAO,yBACV;GAGD,IAAI;IACH,MAAM,WAAW,MAAM,SAAS,eAAe,MAAM;IAErD,OAAO,EAAE,2BAA2B,KAAK,UAAU,QAAQ,EAAE;GAC9D,SAAS,OAAO;IACf,IAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAC/D,OAAO,EAAE,2BAA2B,KAAK,UAAU,gBAAgB,EAAE;IAGtE,MAAM;GACP;EACD;EACA,gBAAgB,EAAE,MAAM,UAAU;GACjC,IAAI,SAAS,MAAM,aAAa,MAAM,IAAI;IACzC,MAAM,MAAM,OAAO,YAAY,cAAc,uBAAuB;IAEpE,IAAI,KACH,OAAO,YAAY,iBAAiB,GAAG;GAKzC;EACD;CACD;AACD"}
1
+ {"version":3,"file":"virtual-template.mjs","names":[],"sources":["../../src/vite/virtual-template.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { relative } from \"node:path\";\nimport type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nconst TEMPLATE_PATH = \"./src/template.html\";\n\nconst DEFAULT_TEMPLATE = /* html */ `\n<!DOCTYPE html>\n<html>\n <head></head>\n <body>\n <!--ssr-outlet-->\n </body>\n</html>\n`.trim();\n\nexport const virtualTemplate = (): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:template\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\treturn {\n\t\tname: \"yamf:virtual-template\",\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tasync load(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst template = await readFile(TEMPLATE_PATH, \"utf8\");\n\n\t\t\t\treturn js`export const template = ${JSON.stringify(template)};`;\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n\t\t\t\t\treturn js`export const template = ${JSON.stringify(DEFAULT_TEMPLATE)};`;\n\t\t\t\t}\n\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t\thandleHotUpdate({ file, server }) {\n\t\t\tif (relative(file, TEMPLATE_PATH) === \"\") {\n\t\t\t\tconst mod = server.moduleGraph.getModuleById(resolvedVirtualModuleId);\n\n\t\t\t\tif (mod) {\n\t\t\t\t\tserver.moduleGraph.invalidateModule(mod);\n\t\t\t\t}\n\n\t\t\t\tserver.ws.send({ type: \"full-reload\" });\n\t\t\t}\n\t\t},\n\t};\n};\n"],"mappings":";;;;AAKA,MAAM,gBAAgB;AAEtB,MAAM,mBAA8B;;;;;;;;EAQlC,KAAK;AAEP,MAAa,wBAAgC;CAC5C,MAAM,kBAAkB;CACxB,MAAM,0BAA0B;CAEhC,OAAO;EACN,MAAM;EACN,UAAU,IAAI;GACb,IAAI,OAAO,iBACV,OAAO;EAIT;EACA,MAAM,KAAK,IAAI;GACd,IAAI,OAAO,yBACV;GAGD,IAAI;IACH,MAAM,WAAW,MAAM,SAAS,eAAe,MAAM;IAErD,OAAO,EAAE,2BAA2B,KAAK,UAAU,QAAQ,EAAE;GAC9D,SAAS,OAAO;IACf,IAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAC/D,OAAO,EAAE,2BAA2B,KAAK,UAAU,gBAAgB,EAAE;IAGtE,MAAM;GACP;EACD;EACA,gBAAgB,EAAE,MAAM,UAAU;GACjC,IAAI,SAAS,MAAM,aAAa,MAAM,IAAI;IACzC,MAAM,MAAM,OAAO,YAAY,cAAc,uBAAuB;IAEpE,IAAI,KACH,OAAO,YAAY,iBAAiB,GAAG;IAGxC,OAAO,GAAG,KAAK,EAAE,MAAM,cAAc,CAAC;GACvC;EACD;CACD;AACD"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pajecawav/yamf",
3
3
  "type": "module",
4
- "version": "0.0.4",
4
+ "version": "0.0.6",
5
5
  "description": "Yet another meta framework",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/pajecawav/yamf#readme",
@@ -30,8 +30,9 @@
30
30
  "access": "public"
31
31
  },
32
32
  "peerDependencies": {
33
- "hono": "^4.12.23",
34
- "vite": "^8.0.16"
33
+ "@hono/react-compat": "^0.0.3",
34
+ "hono": "^4.12.30",
35
+ "vite": "^8.1.5"
35
36
  },
36
37
  "dependencies": {
37
38
  "@babel/generator": "^7.29.7",
@@ -39,31 +40,33 @@
39
40
  "@babel/traverse": "^7.29.7",
40
41
  "@babel/types": "^7.29.7",
41
42
  "devalue": "^5.8.1",
42
- "exsolve": "^1.0.8",
43
+ "exsolve": "^1.1.0",
43
44
  "nitro": "3.0.260610-beta",
44
- "rou3": "^0.8.1",
45
+ "rou3": "^0.9.1",
45
46
  "ufo": "^1.6.4",
46
- "unhead": "^3.1.4"
47
+ "unhead": "^3.1.8",
48
+ "wouter": "^3.10.0"
47
49
  },
48
50
  "devDependencies": {
49
- "@pajecawav/tools": "^0.0.4",
51
+ "@hono/react-compat": "^0.0.3",
52
+ "@pajecawav/tools": "^0.0.7",
50
53
  "@types/babel__generator": "^7.27.0",
51
54
  "@types/babel__traverse": "^7.28.0",
52
- "@types/node": "^24.13.2",
53
- "@typescript/native-preview": "7.0.0-dev.20260612.1",
54
- "@vitest/coverage-v8": "^4.1.8",
55
- "@vitest/ui": "^4.1.8",
55
+ "@types/node": "^24.13.3",
56
+ "@vitest/coverage-v8": "^4.1.10",
57
+ "@vitest/ui": "^4.1.10",
56
58
  "cross-env": "^10.1.0",
57
- "hono": "^4.12.25",
59
+ "hono": "^4.12.30",
58
60
  "husky": "^9.1.7",
59
- "npm-run-all2": "^9.0.1",
60
- "oxfmt": "^0.54.0",
61
- "oxlint": "^1.69.0",
62
- "oxlint-tsgolint": "^0.23.0",
61
+ "npm-run-all2": "^9.0.2",
62
+ "oxfmt": "^0.59.0",
63
+ "oxlint": "^1.74.0",
64
+ "oxlint-tsgolint": "^0.25.0",
63
65
  "publint": "^0.3.21",
64
- "tsdown": "0.22.2",
65
- "vite": "^8.0.16",
66
- "vitest": "^4.1.8"
66
+ "tsdown": "0.22.9",
67
+ "typescript": "~7.0.2",
68
+ "vite": "^8.1.5",
69
+ "vitest": "^4.1.10"
67
70
  },
68
71
  "scripts": {
69
72
  "build": "tsdown",
@@ -77,7 +80,7 @@
77
80
  "test:ui": "vitest --ui",
78
81
  "lint": "cross-env FORCE_COLOR=1 run-p -l lint:*",
79
82
  "lint:oxlint": "oxlint .",
80
- "lint:tsc": "tsgo -b --noEmit",
83
+ "lint:tsc": "tsc -b --noEmit",
81
84
  "lint:format": "pt format --check",
82
85
  "lint:package": "publint",
83
86
  "format": "pt format"
@@ -2,7 +2,7 @@ import { parse } from "devalue";
2
2
  import type { FC } from "hono/jsx";
3
3
  import { hydrateRoot } from "hono/jsx/dom/client";
4
4
  import { withLeadingSlash } from "ufo";
5
- import type { IslandClientDirective } from "./types";
5
+ import type { IslandClientDirectiveSerialized } from "./types";
6
6
 
7
7
  declare let __island_raw_import__: <T>(file: string) => Promise<T>;
8
8
 
@@ -36,7 +36,7 @@ customElements.define(
36
36
  const islandEntry = this.getAttribute("island-entry");
37
37
  // oxlint-disable-next-line typescript/no-unsafe-type-assertion
38
38
  const islandClient = (this.getAttribute("island-client") ??
39
- "load") as IslandClientDirective;
39
+ "load") as IslandClientDirectiveSerialized;
40
40
 
41
41
  if (!islandSrc) {
42
42
  throw new Error("Missing island-src attribute");
@@ -62,6 +62,7 @@ customElements.define(
62
62
  };
63
63
 
64
64
  switch (islandClient) {
65
+ case "true":
65
66
  case "load":
66
67
  initIsland();
67
68
  break;
@@ -77,9 +78,11 @@ customElements.define(
77
78
  initIsland();
78
79
  }
79
80
  break;
81
+ case "false":
80
82
  case "skip":
81
83
  break;
82
84
  default:
85
+ islandClient satisfies never;
83
86
  // oxlint-disable-next-line typescript/restrict-template-expressions
84
87
  throw new Error(`Invalid island-client value: ${islandClient}`);
85
88
  }
@@ -1,4 +1,11 @@
1
- export type IslandClientDirective = "load" | "idle" | "visible" | "skip";
1
+ export type IslandClientDirective = "load" | "idle" | "visible" | "skip" | boolean;
2
+ export type IslandClientDirectiveSerialized =
3
+ | "load"
4
+ | "idle"
5
+ | "visible"
6
+ | "skip"
7
+ | "true"
8
+ | "false";
2
9
 
3
10
  export interface IslandProps {
4
11
  "yamf-client"?: IslandClientDirective;
package/src/page.tsx CHANGED
@@ -10,6 +10,7 @@ import { createStreamableHead, wrapStream } from "unhead/stream/server";
10
10
  import type { ResolvableLink, UseSeoMetaInput } from "unhead/types";
11
11
  import { Root as RootComponent } from "virtual:yamf:root";
12
12
  import { template } from "virtual:yamf:template";
13
+ import { Router } from "wouter";
13
14
  import { SSRContext } from "./context/ssr";
14
15
  import type { ImportAssetsResult } from "./shared/assets";
15
16
  import { YamfHead } from "./shared/head";
@@ -28,7 +29,7 @@ export type PageRenderer = (
28
29
  head: Unhead;
29
30
  seoHead: (input: UseSeoMetaInput) => void;
30
31
  },
31
- ) => HTTPResponse | Promise<HTTPResponse> | Child | Promise<Child>;
32
+ ) => HTTPResponse | Child | Promise<Child | HTTPResponse>;
32
33
 
33
34
  interface DefinePageOptions {
34
35
  render: PageRenderer;
@@ -59,7 +60,9 @@ export const definePage = (options: DefinePageOptions): PageHandler => {
59
60
 
60
61
  const App = async () => (
61
62
  <SSRContext value={{ head, event }}>
62
- <Root>{content}</Root>
63
+ <Router ssrPath={event.url.pathname} ssrSearch={event.url.search}>
64
+ <Root>{content}</Root>
65
+ </Router>
63
66
  </SSRContext>
64
67
  );
65
68
 
package/src/vite/index.ts CHANGED
@@ -32,7 +32,10 @@ const yamf = (options?: YamfOptions): PluginOption[] => {
32
32
  return {
33
33
  ssr: {
34
34
  // we need to inline because otherwise fullstack plugin fails to build manifest for assets imports
35
- noExternal: ["@pajecawav/yamf"],
35
+ // also, we need to bundle everything so that the `react` -> `@hono/react-compat` alias
36
+ // is applied to all dependencies (e.g. wouter) in dev mode. Externalized deps are
37
+ // loaded natively by Node, bypassing Vite's resolve.alias.
38
+ noExternal: true,
36
39
  },
37
40
  optimizeDeps: {
38
41
  include: [
@@ -43,6 +46,19 @@ const yamf = (options?: YamfOptions): PluginOption[] => {
43
46
  "ufo",
44
47
  ],
45
48
  },
49
+ resolve: {
50
+ alias: [
51
+ { find: "react", replacement: "@hono/react-compat" },
52
+ { find: "react-dom", replacement: "@hono/react-compat" },
53
+ // use-sync-external-store is a CJS package that requires("react").
54
+ // Vite's SSR module runner can't process CJS, so we alias it to
55
+ // @hono/react-compat which exports useSyncExternalStore from hono/jsx.
56
+ {
57
+ find: /^use-sync-external-store(?:\/shim(?:\/.*)?)?$/,
58
+ replacement: "@hono/react-compat",
59
+ },
60
+ ],
61
+ },
46
62
  environments: {
47
63
  ...(existsSync("./src/server.tsx") ? { ssr: ssrEnv } : {}),
48
64
  client: {
@@ -20,11 +20,12 @@ export const virtualPages = (): Plugin => {
20
20
  }
21
21
 
22
22
  return js`
23
- const pages = import.meta.glob("/src/pages/**/*.{js,mjs,cjs,ts,mts,cts,tsx,jsx}", {
23
+ const pages = import.meta.glob("/src/pages/**/*.page.{js,mjs,cjs,ts,mts,cts,tsx,jsx}", {
24
24
  import: "default",
25
25
  });
26
+
26
27
  const assets = import.meta.glob(
27
- "/src/pages/**/*.{js,mjs,cjs,ts,mts,cts,tsx,jsx}",
28
+ "/src/pages/**/*.page.{js,mjs,cjs,ts,mts,cts,tsx,jsx}",
28
29
  {
29
30
  import: "default",
30
31
  query: "?assets=ssr",
@@ -53,8 +53,7 @@ export const virtualTemplate = (): Plugin => {
53
53
  server.moduleGraph.invalidateModule(mod);
54
54
  }
55
55
 
56
- // TODO: should reload?
57
- // server.ws.send({ type: "full-reload" });
56
+ server.ws.send({ type: "full-reload" });
58
57
  }
59
58
  },
60
59
  };