@pajecawav/yamf 0.0.3 → 0.0.5
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 +262 -0
- package/dist/components/Head.d.ts +0 -1
- package/dist/components/Head.d.ts.map +1 -1
- package/dist/components/Head.js.map +1 -1
- package/dist/context/ssr.d.ts +1 -1
- package/dist/context/ssr.d.ts.map +1 -1
- package/dist/context/ssr.js.map +1 -1
- package/dist/hooks/useEvent.d.ts +0 -1
- package/dist/hooks/useEvent.d.ts.map +1 -1
- package/dist/hooks/useEvent.js.map +1 -1
- package/dist/hooks/useHead.d.ts +3 -3
- package/dist/hooks/useHead.d.ts.map +1 -1
- package/dist/hooks/useHead.js +8 -2
- package/dist/hooks/useHead.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -2
- package/dist/island/client.js +2 -0
- package/dist/island/client.js.map +1 -1
- package/dist/island/types.d.ts +1 -1
- package/dist/island/types.d.ts.map +1 -1
- package/dist/page.d.ts +8 -9
- package/dist/page.d.ts.map +1 -1
- package/dist/page.js +11 -5
- package/dist/page.js.map +1 -1
- package/dist/server/entry.d.mts +2 -5
- package/dist/server/entry.d.mts.map +1 -1
- package/dist/server/entry.mjs +4 -4
- package/dist/server/entry.mjs.map +1 -1
- package/dist/server/island/server.d.mts +0 -1
- package/dist/server/island/server.d.mts.map +1 -1
- package/dist/server/island/server.mjs.map +1 -1
- package/dist/server/island/types.d.mts +1 -1
- package/dist/server/island/types.d.mts.map +1 -1
- package/dist/server/shared/assets.d.mts.map +1 -1
- package/dist/server/shared/head.d.mts +8 -0
- package/dist/server/shared/head.d.mts.map +1 -0
- package/dist/shared/assets.d.ts.map +1 -1
- package/dist/shared/head.d.ts +8 -0
- package/dist/shared/head.d.ts.map +1 -0
- package/dist/vite/index.d.mts +0 -1
- package/dist/vite/index.d.mts.map +1 -1
- package/dist/vite/index.mjs +2 -0
- package/dist/vite/index.mjs.map +1 -1
- package/dist/vite/islands.mjs.map +1 -1
- package/dist/vite/shared/utils.mjs.map +1 -1
- package/dist/vite/virtual-assets.mjs +2 -2
- package/dist/vite/virtual-assets.mjs.map +1 -1
- package/dist/vite/virtual-pages.mjs +4 -3
- package/dist/vite/virtual-pages.mjs.map +1 -1
- package/dist/vite/virtual-root.mjs +46 -0
- package/dist/vite/virtual-root.mjs.map +1 -0
- package/dist/vite/virtual-template.mjs +1 -1
- package/dist/vite/virtual-template.mjs.map +1 -1
- package/package.json +30 -26
- package/src/hooks/useHead.tsx +14 -2
- package/src/index.ts +2 -1
- package/src/island/client.tsx +3 -0
- package/src/island/types.ts +1 -1
- package/src/page.tsx +19 -15
- package/src/server/entry.tsx +5 -7
- package/src/shared/head.ts +3 -0
- package/src/virtual.d.ts +7 -0
- package/src/vite/index.ts +2 -0
- package/src/vite/virtual-assets.ts +1 -1
- package/src/vite/virtual-pages.ts +3 -2
- package/src/vite/virtual-root.ts +48 -0
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
|
-
{"version":3,"file":"Head.d.ts","names":[],"sources":["../../src/components/Head.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"Head.d.ts","names":[],"sources":["../../src/components/Head.tsx"],"mappings":";;KAGY,YAAY;cAEX,OAAQ,OAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Head.js","names":[],"sources":["../../src/components/Head.tsx"],"sourcesContent":["import type { ResolvableHead } from \"unhead/types\";\nimport { useHead } from \"#/hooks/useHead\";\n\nexport type HeadProps = ResolvableHead;\n\nexport const Head = (props: HeadProps): void => {\n\tuseHead(props);\n};\n"],"mappings":";;AAKA,MAAa,QAAQ,UAA2B;CAC/C,QAAQ,
|
|
1
|
+
{"version":3,"file":"Head.js","names":[],"sources":["../../src/components/Head.tsx"],"sourcesContent":["import type { ResolvableHead } from \"unhead/types\";\nimport { useHead } from \"#/hooks/useHead\";\n\nexport type HeadProps = ResolvableHead;\n\nexport const Head = (props: HeadProps): void => {\n\tuseHead(props);\n};\n"],"mappings":";;AAKA,MAAa,QAAQ,UAA2B;CAC/C,QAAQ,KAAK;AACd"}
|
package/dist/context/ssr.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr.d.ts","names":[],"sources":["../../src/context/ssr.ts"],"mappings":";;;;UAIU
|
|
1
|
+
{"version":3,"file":"ssr.d.ts","names":[],"sources":["../../src/context/ssr.ts"],"mappings":";;;;UAIU;EACT,MAAM;EACN,OAAO;;cAOK,qBAAoB"}
|
package/dist/context/ssr.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr.js","names":[],"sources":["../../src/context/ssr.ts"],"sourcesContent":["import { type Context, createContext, useContext } from \"hono/jsx\";\nimport type { H3Event } from \"nitro\";\nimport type { ServerUnhead } from \"unhead/server\";\n\ninterface SSRContextValue {\n\thead: ServerUnhead;\n\tevent: H3Event;\n}\n\nexport const SSRContext: Context<SSRContextValue | null> = createContext<SSRContextValue | null>(\n\tnull,\n);\n\nexport const useSSRContext = (): SSRContextValue | null => {\n\treturn useContext(SSRContext);\n};\n"],"mappings":";;AASA,MAAa,aAA8C,cAC1D,
|
|
1
|
+
{"version":3,"file":"ssr.js","names":[],"sources":["../../src/context/ssr.ts"],"sourcesContent":["import { type Context, createContext, useContext } from \"hono/jsx\";\nimport type { H3Event } from \"nitro\";\nimport type { ServerUnhead } from \"unhead/server\";\n\ninterface SSRContextValue {\n\thead: ServerUnhead;\n\tevent: H3Event;\n}\n\nexport const SSRContext: Context<SSRContextValue | null> = createContext<SSRContextValue | null>(\n\tnull,\n);\n\nexport const useSSRContext = (): SSRContextValue | null => {\n\treturn useContext(SSRContext);\n};\n"],"mappings":";;AASA,MAAa,aAA8C,cAC1D,IACD;AAEA,MAAa,sBAA8C;CAC1D,OAAO,WAAW,UAAU;AAC7B"}
|
package/dist/hooks/useEvent.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useEvent.d.ts","names":[],"sources":["../../src/hooks/useEvent.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"useEvent.d.ts","names":[],"sources":["../../src/hooks/useEvent.tsx"],"mappings":";;cAGa,gBAAe"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useEvent.js","names":[],"sources":["../../src/hooks/useEvent.tsx"],"sourcesContent":["import type { H3Event } from \"nitro\";\nimport { useSSRContext } from \"#/context/ssr\";\n\nexport const useEvent = (): H3Event => {\n\tconst ctx = useSSRContext();\n\n\tif (!ctx) {\n\t\tthrow new Error(\"SSR context not found\");\n\t}\n\n\treturn ctx.event;\n};\n"],"mappings":";;AAGA,MAAa,iBAA0B;CACtC,MAAM,MAAM,
|
|
1
|
+
{"version":3,"file":"useEvent.js","names":[],"sources":["../../src/hooks/useEvent.tsx"],"sourcesContent":["import type { H3Event } from \"nitro\";\nimport { useSSRContext } from \"#/context/ssr\";\n\nexport const useEvent = (): H3Event => {\n\tconst ctx = useSSRContext();\n\n\tif (!ctx) {\n\t\tthrow new Error(\"SSR context not found\");\n\t}\n\n\treturn ctx.event;\n};\n"],"mappings":";;AAGA,MAAa,iBAA0B;CACtC,MAAM,MAAM,cAAc;CAE1B,IAAI,CAAC,KACJ,MAAM,IAAI,MAAM,uBAAuB;CAGxC,OAAO,IAAI;AACZ"}
|
package/dist/hooks/useHead.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ClientUnhead } from "unhead/client";
|
|
2
|
-
import { ResolvableHead } from "unhead/types";
|
|
3
|
-
|
|
2
|
+
import { ResolvableHead, UseSeoMetaInput } from "unhead/types";
|
|
4
3
|
//#region src/hooks/useHead.d.ts
|
|
5
4
|
declare global {
|
|
6
5
|
interface Window {
|
|
@@ -8,6 +7,7 @@ declare global {
|
|
|
8
7
|
}
|
|
9
8
|
}
|
|
10
9
|
declare const useHead: (input?: ResolvableHead) => void;
|
|
10
|
+
declare const useSeoMeta: (input?: UseSeoMetaInput) => void;
|
|
11
11
|
//#endregion
|
|
12
|
-
export { useHead };
|
|
12
|
+
export { useHead, useSeoMeta };
|
|
13
13
|
//# sourceMappingURL=useHead.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useHead.d.ts","names":[],"sources":["../../src/hooks/useHead.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"useHead.d.ts","names":[],"sources":["../../src/hooks/useHead.tsx"],"mappings":";;;;YAOW;IACT,aAAa;;;cAQF,UAAW,QAAQ;cAYnB,aAAc,QAAQ"}
|
package/dist/hooks/useHead.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useSSRContext } from "../context/ssr.js";
|
|
2
|
-
import { useHead } from "unhead";
|
|
2
|
+
import { useHead, useSeoMeta } from "unhead";
|
|
3
3
|
import { createHead } from "unhead/client";
|
|
4
4
|
//#region src/hooks/useHead.tsx
|
|
5
5
|
if (!import.meta.env.SSR) window.__UNHEAD__ = createHead();
|
|
@@ -9,7 +9,13 @@ const useHead$1 = (input) => {
|
|
|
9
9
|
if (ctx?.head) useHead(ctx.head, input);
|
|
10
10
|
} else if (window.__UNHEAD__) useHead(window.__UNHEAD__, input);
|
|
11
11
|
};
|
|
12
|
+
const useSeoMeta$1 = (input) => {
|
|
13
|
+
if (import.meta.env.SSR) {
|
|
14
|
+
const ctx = useSSRContext();
|
|
15
|
+
if (ctx?.head) useSeoMeta(ctx.head, input);
|
|
16
|
+
} else if (window.__UNHEAD__) useSeoMeta(window.__UNHEAD__, input);
|
|
17
|
+
};
|
|
12
18
|
//#endregion
|
|
13
|
-
export { useHead$1 as useHead };
|
|
19
|
+
export { useHead$1 as useHead, useSeoMeta$1 as useSeoMeta };
|
|
14
20
|
|
|
15
21
|
//# sourceMappingURL=useHead.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useHead.js","names":["useHead"],"sources":["../../src/hooks/useHead.tsx"],"sourcesContent":["import { useHead as _useHead } from \"unhead\";\nimport type { ClientUnhead } from \"unhead/client\";\nimport { createHead } from \"unhead/client\";\nimport type { ResolvableHead } from \"unhead/types\";\nimport { useSSRContext } from \"#/context/ssr\";\n\ndeclare global {\n\tinterface Window {\n\t\t__UNHEAD__?: ClientUnhead;\n\t}\n}\n\nif (!import.meta.env.SSR) {\n\twindow.__UNHEAD__ = createHead();\n}\n\nexport const useHead = (input?: ResolvableHead): void => {\n\tif (import.meta.env.SSR) {\n\t\tconst ctx = useSSRContext();\n\n\t\tif (ctx?.head) {\n\t\t\t_useHead(ctx.head, input);\n\t\t}\n\t} else if (window.__UNHEAD__) {\n\t\t_useHead(window.__UNHEAD__, input);\n\t}\n};\n"],"mappings":";;;;AAYA,IAAI,CAAC,OAAO,KAAK,IAAI,KACpB,OAAO,aAAa,
|
|
1
|
+
{"version":3,"file":"useHead.js","names":["useHead","useSeoMeta"],"sources":["../../src/hooks/useHead.tsx"],"sourcesContent":["import { useHead as _useHead, useSeoMeta as _useSeoMeta } from \"unhead\";\nimport type { ClientUnhead } from \"unhead/client\";\nimport { createHead } from \"unhead/client\";\nimport type { ResolvableHead, UseSeoMetaInput } from \"unhead/types\";\nimport { useSSRContext } from \"#/context/ssr\";\n\ndeclare global {\n\tinterface Window {\n\t\t__UNHEAD__?: ClientUnhead;\n\t}\n}\n\nif (!import.meta.env.SSR) {\n\twindow.__UNHEAD__ = createHead();\n}\n\nexport const useHead = (input?: ResolvableHead): void => {\n\tif (import.meta.env.SSR) {\n\t\tconst ctx = useSSRContext();\n\n\t\tif (ctx?.head) {\n\t\t\t_useHead(ctx.head, input);\n\t\t}\n\t} else if (window.__UNHEAD__) {\n\t\t_useHead(window.__UNHEAD__, input);\n\t}\n};\n\nexport const useSeoMeta = (input?: UseSeoMetaInput): void => {\n\tif (import.meta.env.SSR) {\n\t\tconst ctx = useSSRContext();\n\n\t\tif (ctx?.head) {\n\t\t\t_useSeoMeta(ctx.head, input);\n\t\t}\n\t} else if (window.__UNHEAD__) {\n\t\t_useSeoMeta(window.__UNHEAD__, input);\n\t}\n};\n"],"mappings":";;;;AAYA,IAAI,CAAC,OAAO,KAAK,IAAI,KACpB,OAAO,aAAa,WAAW;AAGhC,MAAaA,aAAW,UAAiC;CACxD,IAAI,OAAO,KAAK,IAAI,KAAK;EACxB,MAAM,MAAM,cAAc;EAE1B,IAAI,KAAK,MACR,QAAS,IAAI,MAAM,KAAK;CAE1B,OAAO,IAAI,OAAO,YACjB,QAAS,OAAO,YAAY,KAAK;AAEnC;AAEA,MAAaC,gBAAc,UAAkC;CAC5D,IAAI,OAAO,KAAK,IAAI,KAAK;EACxB,MAAM,MAAM,cAAc;EAE1B,IAAI,KAAK,MACR,WAAY,IAAI,MAAM,KAAK;CAE7B,OAAO,IAAI,OAAO,YACjB,WAAY,OAAO,YAAY,KAAK;AAEtC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { ImportAssetsResult, ImportAssetsResultRaw } from "./shared/assets.js";
|
|
2
|
+
import { YamfHead } from "./shared/head.js";
|
|
2
3
|
import { PageHandler, definePage } from "./page.js";
|
|
3
4
|
import { useSSRContext } from "./context/ssr.js";
|
|
4
5
|
import { useEvent } from "./hooks/useEvent.js";
|
|
5
|
-
import { useHead } from "./hooks/useHead.js";
|
|
6
|
+
import { useHead, useSeoMeta } from "./hooks/useHead.js";
|
|
6
7
|
import { IslandClientDirective, IslandProps } from "./island/types.js";
|
|
7
8
|
import { Head, HeadProps } from "./components/Head.js";
|
|
8
|
-
export { Head, type HeadProps, type ImportAssetsResult, type ImportAssetsResultRaw, type IslandClientDirective, type IslandProps, type PageHandler, definePage, useEvent, useHead, useSSRContext };
|
|
9
|
+
export { Head, type HeadProps, type ImportAssetsResult, type ImportAssetsResultRaw, type IslandClientDirective, type IslandProps, type PageHandler, type YamfHead, definePage, useEvent, useHead, useSSRContext, useSeoMeta };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useSSRContext } from "./context/ssr.js";
|
|
2
2
|
import { definePage } from "./page.js";
|
|
3
3
|
import { useEvent } from "./hooks/useEvent.js";
|
|
4
|
-
import { useHead } from "./hooks/useHead.js";
|
|
4
|
+
import { useHead, useSeoMeta } from "./hooks/useHead.js";
|
|
5
5
|
import { Head } from "./components/Head.js";
|
|
6
|
-
export { Head, definePage, useEvent, useHead, useSSRContext };
|
|
6
|
+
export { Head, definePage, useEvent, useHead, useSSRContext, useSeoMeta };
|
package/dist/island/client.js
CHANGED
|
@@ -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,
|
|
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 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"}
|
package/dist/island/types.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/island/types.ts"],"mappings":";KAAY
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/island/types.ts"],"mappings":";KAAY;UAEK;EAChB,gBAAgB"}
|
package/dist/page.d.ts
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
import { ImportAssetsResult } from "./shared/assets.js";
|
|
2
|
-
import {
|
|
2
|
+
import { YamfHead } from "./shared/head.js";
|
|
3
|
+
import { Child } from "hono/jsx";
|
|
3
4
|
import { EventHandlerResponse, H3Event, HTTPResponse } from "nitro/h3";
|
|
4
5
|
import { Unhead } from "unhead/server";
|
|
5
|
-
import {
|
|
6
|
-
|
|
6
|
+
import { UseSeoMetaInput } from "unhead/types";
|
|
7
7
|
//#region src/page.d.ts
|
|
8
8
|
type PageHandler = (event: H3Event, params: {
|
|
9
|
-
|
|
10
|
-
head?:
|
|
11
|
-
Layout?: FC<PropsWithChildren>;
|
|
9
|
+
assets: ImportAssetsResult;
|
|
10
|
+
head?: YamfHead;
|
|
12
11
|
}) => EventHandlerResponse;
|
|
13
12
|
type PageRenderer = (event: H3Event, params: {
|
|
14
13
|
head: Unhead;
|
|
15
|
-
|
|
14
|
+
seoHead: (input: UseSeoMetaInput) => void;
|
|
15
|
+
}) => HTTPResponse | Child | Promise<Child | HTTPResponse>;
|
|
16
16
|
interface DefinePageOptions {
|
|
17
17
|
render: PageRenderer;
|
|
18
18
|
stream?: boolean;
|
|
19
|
-
Layout?: FC<PropsWithChildren>;
|
|
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
|
package/dist/page.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.d.ts","names":[],"sources":["../src/page.tsx"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"page.d.ts","names":[],"sources":["../src/page.tsx"],"mappings":";;;;;;;KAgBY,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
|
@@ -3,16 +3,18 @@ import { Fragment } from "hono/jsx";
|
|
|
3
3
|
import { renderToReadableStream } from "hono/jsx/streaming";
|
|
4
4
|
import { Hono } from "hono/tiny";
|
|
5
5
|
import { HTTPResponse, withServerTiming } from "nitro/h3";
|
|
6
|
+
import { useSeoMeta } from "unhead";
|
|
6
7
|
import { transformHtmlTemplate } from "unhead/server";
|
|
7
8
|
import { createStreamableHead, wrapStream } from "unhead/stream/server";
|
|
8
|
-
import {
|
|
9
|
+
import { Root } from "virtual:yamf:root";
|
|
9
10
|
import { template } from "virtual:yamf:template";
|
|
10
11
|
import { jsx } from "hono/jsx/jsx-runtime";
|
|
11
12
|
//#region src/page.tsx
|
|
12
13
|
const definePage = (options) => {
|
|
13
|
-
return async (event, {
|
|
14
|
-
const assets = serverAssets ? clientAssets.merge(serverAssets) : clientAssets;
|
|
14
|
+
return async (event, { assets, head: headInit }) => {
|
|
15
15
|
const { head } = createStreamableHead({ init: [headInit] });
|
|
16
|
+
const seoHead = (input) => useSeoMeta(head, input);
|
|
17
|
+
seoHead(headInit?.seo);
|
|
16
18
|
head.push({
|
|
17
19
|
link: [...assets.js.map((attrs) => ({
|
|
18
20
|
rel: "modulepreload",
|
|
@@ -26,14 +28,18 @@ const definePage = (options) => {
|
|
|
26
28
|
src: assets.entry
|
|
27
29
|
}]
|
|
28
30
|
});
|
|
29
|
-
const content = await options.render(event, {
|
|
31
|
+
const content = await options.render(event, {
|
|
32
|
+
head,
|
|
33
|
+
seoHead
|
|
34
|
+
});
|
|
30
35
|
if (content instanceof HTTPResponse) return content;
|
|
36
|
+
const Root$1 = Root ?? Fragment;
|
|
31
37
|
const App = async () => /* @__PURE__ */ jsx(SSRContext, {
|
|
32
38
|
value: {
|
|
33
39
|
head,
|
|
34
40
|
event
|
|
35
41
|
},
|
|
36
|
-
children: /* @__PURE__ */ jsx(
|
|
42
|
+
children: /* @__PURE__ */ jsx(Root$1, { children: content })
|
|
37
43
|
});
|
|
38
44
|
const responseInit = { headers: { "Content-Type": "text/html; charset=utf-8" } };
|
|
39
45
|
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":[],"sources":["../src/page.tsx"],"sourcesContent":["import
|
|
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 | 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<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"}
|
package/dist/server/entry.d.mts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
+
import { YamfHead } from "./shared/head.mjs";
|
|
1
2
|
import { EventHandlerRequest, EventHandlerWithFetch, H3Event } from "nitro/h3";
|
|
2
|
-
import { FC, PropsWithChildren } from "hono/jsx";
|
|
3
|
-
import { ResolvableHead } from "unhead/types";
|
|
4
|
-
|
|
5
3
|
//#region src/server/entry.d.ts
|
|
6
4
|
type ServerEntry = EventHandlerWithFetch<EventHandlerRequest, Promise<unknown>>;
|
|
7
5
|
interface DefineServerEntryOptions {
|
|
8
|
-
head?:
|
|
9
|
-
Layout?: FC<PropsWithChildren>;
|
|
6
|
+
head?: YamfHead | ((event: H3Event) => YamfHead);
|
|
10
7
|
disableEarlyHints?: boolean;
|
|
11
8
|
}
|
|
12
9
|
declare const defineServerEntry: (options?: DefineServerEntryOptions) => ServerEntry;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.d.mts","names":[],"sources":["../../src/server/entry.tsx"],"mappings":"
|
|
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"}
|
package/dist/server/entry.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { addRoute, createRouter, findRoute } from "rou3";
|
|
|
4
4
|
import { withLeadingSlash, withoutTrailingSlash } from "ufo";
|
|
5
5
|
import { clientAssets } from "virtual:yamf:assets";
|
|
6
6
|
import { assets, pages } from "virtual:yamf:pages";
|
|
7
|
+
import { rootAssets } from "virtual:yamf:root";
|
|
7
8
|
//#region src/server/entry.tsx
|
|
8
9
|
const router = createRouter();
|
|
9
10
|
for (const [relativePath, handler] of Object.entries(pages).toSorted((a, b) => a[0].localeCompare(b[0]))) {
|
|
@@ -23,15 +24,14 @@ const defineServerEntry = (options) => {
|
|
|
23
24
|
if (!route) throw new HTTPError("Not found", { status: 404 });
|
|
24
25
|
const { handler, serverAssets } = route.data;
|
|
25
26
|
event.context.params = route.params;
|
|
26
|
-
const assets =
|
|
27
|
+
const assets = clientAssets.merge(...[rootAssets, serverAssets].filter((x) => !!x));
|
|
27
28
|
if (!options?.disableEarlyHints) {
|
|
28
29
|
const link = [...assets.js.map((script) => `<${script.href}>; rel=modulepreload`), ...assets.css.map((style) => `<${style.href}>; rel=preload; as=style`)];
|
|
29
30
|
if (link.length) await writeEarlyHints(event, { link });
|
|
30
31
|
}
|
|
31
32
|
return (await handler())(event, {
|
|
32
|
-
|
|
33
|
-
head: typeof options?.head === "function" ? options.head(event) : options?.head
|
|
34
|
-
Layout: options?.Layout
|
|
33
|
+
assets,
|
|
34
|
+
head: typeof options?.head === "function" ? options.head(event) : options?.head
|
|
35
35
|
});
|
|
36
36
|
});
|
|
37
37
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.mjs","names":["pagesServerAssets"],"sources":["../../src/server/entry.tsx"],"sourcesContent":["import path from \"node:path\";\nimport type {
|
|
1
|
+
{"version":3,"file":"entry.mjs","names":["pagesServerAssets"],"sources":["../../src/server/entry.tsx"],"sourcesContent":["import path from \"node:path\";\nimport type { EventHandlerRequest, EventHandlerWithFetch, H3Event } from \"nitro/h3\";\nimport { defineHandler, HTTPError, writeEarlyHints } from \"nitro/h3\";\nimport { addRoute, createRouter, findRoute } from \"rou3\";\nimport { withLeadingSlash, withoutTrailingSlash } from \"ufo\";\nimport { clientAssets } from \"virtual:yamf:assets\";\nimport { pages, assets as pagesServerAssets } from \"virtual:yamf:pages\";\nimport { rootAssets } from \"virtual:yamf:root\";\nimport type { PageHandler } from \"#/page\";\nimport { YamfHead } from \"#/shared/head\";\n\ninterface Route {\n\thandler: () => Promise<PageHandler>;\n\tserverAssets?: ImportAssetsResult;\n}\n\nconst router = createRouter<Route>();\n\nfor (const [relativePath, handler] of Object.entries(pages).toSorted((a, b) =>\n\ta[0].localeCompare(b[0]),\n)) {\n\tconst ext = path.extname(relativePath);\n\n\tlet route = path.relative(\"/src/pages\", relativePath);\n\n\tif (ext.length) {\n\t\troute = route.slice(0, -ext.length);\n\t}\n\n\troute =\n\t\troute\n\t\t\t.replace(/\\.[A-Za-z]+$/, \"\")\n\t\t\t.replace(/\\(([^(/\\\\]+)\\)[/\\\\]/g, \"\")\n\t\t\t.replace(/\\[\\.{3}]/g, \"**\")\n\t\t\t.replace(/\\[\\.{3}([^\\]]+)]/g, (_, p: string) => \"**:\" + p.replace(/[^\\w-]/g, \"_\"))\n\t\t\t.replace(/\\[([^/\\]]+)]/g, (_, p: string) => \":\" + p.replace(/[^\\w-]/g, \"_\"))\n\t\t\t.replace(/(\\/|^)index$/, \"\") || \"/\";\n\n\troute = withLeadingSlash(withoutTrailingSlash(route));\n\n\taddRoute(router, \"GET\", route, {\n\t\thandler,\n\t\tserverAssets: pagesServerAssets[relativePath],\n\t});\n}\n\nexport type ServerEntry = EventHandlerWithFetch<EventHandlerRequest, Promise<unknown>>;\n\nexport interface DefineServerEntryOptions {\n\thead?: YamfHead | ((event: H3Event) => YamfHead);\n\tdisableEarlyHints?: boolean;\n}\n\nexport const defineServerEntry = (options?: DefineServerEntryOptions): ServerEntry => {\n\treturn defineHandler(async event => {\n\t\tconst route = findRoute(router, \"GET\", event.url.pathname);\n\n\t\tif (!route) {\n\t\t\tthrow new HTTPError(\"Not found\", { status: 404 });\n\t\t}\n\n\t\tconst { handler, serverAssets } = route.data;\n\t\tevent.context.params = route.params;\n\n\t\tconst assets = clientAssets.merge(...[rootAssets, serverAssets].filter(x => !!x));\n\n\t\tif (!options?.disableEarlyHints) {\n\t\t\tconst link = [\n\t\t\t\t...assets.js.map(script => `<${script.href}>; rel=modulepreload`),\n\t\t\t\t...assets.css.map(style => `<${style.href}>; rel=preload; as=style`),\n\t\t\t];\n\n\t\t\tif (link.length) {\n\t\t\t\tawait writeEarlyHints(event, { link });\n\t\t\t}\n\t\t}\n\n\t\treturn (await handler())(event, {\n\t\t\tassets,\n\t\t\thead: typeof options?.head === \"function\" ? options.head(event) : options?.head,\n\t\t});\n\t});\n};\n"],"mappings":";;;;;;;;AAgBA,MAAM,SAAS,aAAoB;AAEnC,KAAK,MAAM,CAAC,cAAc,YAAY,OAAO,QAAQ,KAAK,CAAC,CAAC,UAAU,GAAG,MACxE,EAAE,EAAE,CAAC,cAAc,EAAE,EAAE,CACxB,GAAG;CACF,MAAM,MAAM,KAAK,QAAQ,YAAY;CAErC,IAAI,QAAQ,KAAK,SAAS,cAAc,YAAY;CAEpD,IAAI,IAAI,QACP,QAAQ,MAAM,MAAM,GAAG,CAAC,IAAI,MAAM;CAGnC,QACC,MACE,QAAQ,gBAAgB,EAAE,CAAC,CAC3B,QAAQ,wBAAwB,EAAE,CAAC,CACnC,QAAQ,aAAa,IAAI,CAAC,CAC1B,QAAQ,sBAAsB,GAAG,MAAc,QAAQ,EAAE,QAAQ,WAAW,GAAG,CAAC,CAAC,CACjF,QAAQ,kBAAkB,GAAG,MAAc,MAAM,EAAE,QAAQ,WAAW,GAAG,CAAC,CAAC,CAC3E,QAAQ,gBAAgB,EAAE,KAAK;CAElC,QAAQ,iBAAiB,qBAAqB,KAAK,CAAC;CAEpD,SAAS,QAAQ,OAAO,OAAO;EAC9B;EACA,cAAcA,OAAkB;CACjC,CAAC;AACF;AASA,MAAa,qBAAqB,YAAoD;CACrF,OAAO,cAAc,OAAM,UAAS;EACnC,MAAM,QAAQ,UAAU,QAAQ,OAAO,MAAM,IAAI,QAAQ;EAEzD,IAAI,CAAC,OACJ,MAAM,IAAI,UAAU,aAAa,EAAE,QAAQ,IAAI,CAAC;EAGjD,MAAM,EAAE,SAAS,iBAAiB,MAAM;EACxC,MAAM,QAAQ,SAAS,MAAM;EAE7B,MAAM,SAAS,aAAa,MAAM,GAAG,CAAC,YAAY,YAAY,CAAC,CAAC,QAAO,MAAK,CAAC,CAAC,CAAC,CAAC;EAEhF,IAAI,CAAC,SAAS,mBAAmB;GAChC,MAAM,OAAO,CACZ,GAAG,OAAO,GAAG,KAAI,WAAU,IAAI,OAAO,KAAK,qBAAqB,GAChE,GAAG,OAAO,IAAI,KAAI,UAAS,IAAI,MAAM,KAAK,yBAAyB,CACpE;GAEA,IAAI,KAAK,QACR,MAAM,gBAAgB,OAAO,EAAE,KAAK,CAAC;EAEvC;EAEA,QAAQ,MAAM,QAAQ,EAAA,CAAG,OAAO;GAC/B;GACA,MAAM,OAAO,SAAS,SAAS,aAAa,QAAQ,KAAK,KAAK,IAAI,SAAS;EAC5E,CAAC;CACF,CAAC;AACF"}
|