@pajecawav/yamf 0.0.7 → 0.0.9
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 +63 -7
- package/dist/client/index.js +39 -1
- package/dist/client/index.js.map +1 -0
- package/dist/components/Head.d.ts +3 -4
- package/dist/components/Head.d.ts.map +1 -1
- package/dist/components/Head.js.map +1 -1
- package/dist/context/ssr.d.ts +1 -2
- package/dist/context/ssr.d.ts.map +1 -1
- package/dist/hooks/isPrerendering.d.ts +12 -0
- package/dist/hooks/isPrerendering.d.ts.map +1 -0
- package/dist/hooks/isPrerendering.js +17 -0
- package/dist/hooks/isPrerendering.js.map +1 -0
- package/dist/hooks/useEvent.d.ts +1 -2
- package/dist/hooks/useEvent.d.ts.map +1 -1
- package/dist/hooks/useHead.d.ts +10 -5
- package/dist/hooks/useHead.d.ts.map +1 -1
- package/dist/hooks/useHead.js +46 -4
- package/dist/hooks/useHead.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +3 -1
- package/dist/island/client.js +44 -36
- package/dist/island/client.js.map +1 -1
- package/dist/island/types.d.ts +2 -3
- package/dist/island/types.d.ts.map +1 -1
- package/dist/page.d.ts +32 -6
- package/dist/page.d.ts.map +1 -1
- package/dist/page.js +60 -6
- package/dist/page.js.map +1 -1
- package/dist/safeAsync.d.ts +19 -0
- package/dist/safeAsync.d.ts.map +1 -0
- package/dist/safeAsync.js +34 -0
- package/dist/safeAsync.js.map +1 -0
- package/dist/server/entry.d.mts +12 -4
- package/dist/server/entry.d.mts.map +1 -1
- package/dist/server/entry.mjs +9 -3
- package/dist/server/entry.mjs.map +1 -1
- package/dist/server/island/server.d.mts +2 -3
- package/dist/server/island/server.d.mts.map +1 -1
- package/dist/server/island/server.mjs +13 -3
- package/dist/server/island/server.mjs.map +1 -1
- package/dist/server/island/types.d.mts +1 -2
- package/dist/server/island/types.d.mts.map +1 -1
- package/dist/server/shared/assets.d.mts +1 -2
- package/dist/server/shared/assets.d.mts.map +1 -1
- package/dist/server/shared/head.d.mts +1 -2
- package/dist/server/shared/head.d.mts.map +1 -1
- package/dist/shared/assets.d.ts +2 -3
- package/dist/shared/assets.d.ts.map +1 -1
- package/dist/shared/head.d.ts +1 -2
- package/dist/shared/head.d.ts.map +1 -1
- package/dist/vite/index.d.mts +2 -2
- package/dist/vite/index.d.mts.map +1 -1
- package/dist/vite/index.mjs +13 -5
- package/dist/vite/index.mjs.map +1 -1
- package/dist/vite/islands.mjs +34 -5
- package/dist/vite/islands.mjs.map +1 -1
- package/dist/vite/prerender.mjs +16 -0
- package/dist/vite/prerender.mjs.map +1 -0
- package/dist/vite/virtual-assets.mjs +1 -1
- package/dist/vite/virtual-assets.mjs.map +1 -1
- package/dist/vite/virtual-error-handler.mjs +1 -1
- package/dist/vite/virtual-error-handler.mjs.map +1 -1
- package/dist/vite/virtual-pages.mjs +1 -1
- package/dist/vite/virtual-pages.mjs.map +1 -1
- package/dist/vite/virtual-root.mjs +12 -5
- package/dist/vite/virtual-root.mjs.map +1 -1
- package/dist/vite/virtual-template.mjs +9 -4
- package/dist/vite/virtual-template.mjs.map +1 -1
- package/package.json +27 -20
- package/src/client/index.ts +51 -1
- package/src/components/Head.tsx +2 -2
- package/src/hooks/isPrerendering.ts +13 -0
- package/src/hooks/useHead.tsx +98 -9
- package/src/index.ts +10 -1
- package/src/island/client.tsx +73 -65
- package/src/island/server.tsx +31 -6
- package/src/page.tsx +176 -11
- package/src/safeAsync.tsx +46 -0
- package/src/server/entry.tsx +25 -1
- package/src/vite/index.ts +30 -3
- package/src/vite/islands.ts +68 -8
- package/src/vite/prerender.ts +27 -0
- package/src/vite/virtual-root.ts +14 -4
- package/src/vite/virtual-template.ts +15 -3
package/dist/page.js
CHANGED
|
@@ -2,17 +2,58 @@ import { SSRContext } from "./context/ssr.js";
|
|
|
2
2
|
import { Fragment } from "hono/jsx";
|
|
3
3
|
import { renderToReadableStream } from "hono/jsx/streaming";
|
|
4
4
|
import { Hono } from "hono/tiny";
|
|
5
|
-
import { HTTPResponse, withServerTiming } from "nitro/h3";
|
|
5
|
+
import { HTTPError, HTTPResponse, withServerTiming } from "nitro/h3";
|
|
6
6
|
import { useSeoMeta } from "unhead";
|
|
7
7
|
import { transformHtmlTemplate } from "unhead/server";
|
|
8
|
-
import { createStreamableHead, wrapStream } from "unhead/stream/server";
|
|
8
|
+
import { createStreamableHead, renderSSRHeadSuspenseChunk, wrapStream } from "unhead/stream/server";
|
|
9
9
|
import { Root } from "virtual:yamf:root";
|
|
10
10
|
import { template } from "virtual:yamf:template";
|
|
11
11
|
import { Router } from "wouter";
|
|
12
12
|
import { jsx } from "hono/jsx/jsx-runtime";
|
|
13
13
|
//#region src/page.tsx
|
|
14
|
+
const cacheControlValue = (cache) => {
|
|
15
|
+
const options = typeof cache === "number" ? { maxAge: cache } : cache;
|
|
16
|
+
const parts = [options.private ? "private" : "public", `max-age=${options.maxAge}`];
|
|
17
|
+
if (options.swr !== void 0) parts.push(`stale-while-revalidate=${options.swr}`);
|
|
18
|
+
return parts.join(", ");
|
|
19
|
+
};
|
|
20
|
+
const unwrapEntryInput = (input) => {
|
|
21
|
+
return typeof input === "function" ? input() : input;
|
|
22
|
+
};
|
|
23
|
+
const collectHeadInputs = (head) => {
|
|
24
|
+
const inputs = [];
|
|
25
|
+
for (const entry of head.entries.values()) inputs.push(unwrapEntryInput(entry.input));
|
|
26
|
+
return inputs;
|
|
27
|
+
};
|
|
28
|
+
const serializeHeadPayload = (inputs) => {
|
|
29
|
+
return JSON.stringify(inputs).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
30
|
+
};
|
|
31
|
+
const scriptNonceAttr = (nonce) => {
|
|
32
|
+
return nonce ? ` nonce="${nonce.replace(/"/g, """)}"` : "";
|
|
33
|
+
};
|
|
34
|
+
const headPayloadScript = (inputs, nonce) => {
|
|
35
|
+
if (!inputs.length) return "";
|
|
36
|
+
return `<script${scriptNonceAttr(nonce)}>window.__unhead__&&window.__unhead__.push(${serializeHeadPayload(inputs)})<\/script>`;
|
|
37
|
+
};
|
|
38
|
+
const injectHeadPayload = (template, script) => {
|
|
39
|
+
if (!script) return template;
|
|
40
|
+
if (/<body[^>]*>/.test(template)) return template.replace(/<body[^>]*>/, (match) => `${match}${script}`);
|
|
41
|
+
return template.replace("</head>", `</head>${script}`);
|
|
42
|
+
};
|
|
43
|
+
const validateWithSchema = async (schema, value, status, what) => {
|
|
44
|
+
if (!schema) return value;
|
|
45
|
+
const result = await schema["~standard"].validate(value);
|
|
46
|
+
if (result.issues) throw new HTTPError(`Invalid ${what}`, {
|
|
47
|
+
status,
|
|
48
|
+
cause: new Error(result.issues.map((issue) => issue.message).join("; "))
|
|
49
|
+
});
|
|
50
|
+
return result.value;
|
|
51
|
+
};
|
|
14
52
|
const definePage = (options) => {
|
|
15
|
-
return async (event, { assets, head: headInit }) => {
|
|
53
|
+
return async (event, { assets, head: headInit, nonce }) => {
|
|
54
|
+
if (options.cache !== void 0) event.res.headers.set("cache-control", cacheControlValue(options.cache));
|
|
55
|
+
const params = await validateWithSchema(options.params, event.context.params, 404, "path params");
|
|
56
|
+
const query = await validateWithSchema(options.query, Object.fromEntries(event.url.searchParams), 400, "query params");
|
|
16
57
|
const { head } = createStreamableHead({ init: [headInit] });
|
|
17
58
|
const seoHead = (input) => useSeoMeta(head, input);
|
|
18
59
|
seoHead(headInit?.seo);
|
|
@@ -31,9 +72,13 @@ const definePage = (options) => {
|
|
|
31
72
|
});
|
|
32
73
|
const content = await options.render(event, {
|
|
33
74
|
head,
|
|
34
|
-
seoHead
|
|
75
|
+
seoHead,
|
|
76
|
+
params,
|
|
77
|
+
query
|
|
35
78
|
});
|
|
36
79
|
if (content instanceof HTTPResponse) return content;
|
|
80
|
+
const payloadScript = headPayloadScript(collectHeadInputs(head), nonce);
|
|
81
|
+
const templateWithPayload = injectHeadPayload(template, payloadScript);
|
|
37
82
|
const Root$1 = Root ?? Fragment;
|
|
38
83
|
const App = async () => /* @__PURE__ */ jsx(SSRContext, {
|
|
39
84
|
value: {
|
|
@@ -49,10 +94,19 @@ const definePage = (options) => {
|
|
|
49
94
|
const responseInit = { headers: { "Content-Type": "text/html; charset=utf-8" } };
|
|
50
95
|
if (!options.stream) return new Hono().get("/", async (c) => withServerTiming(event, "#render", async () => {
|
|
51
96
|
let html = await (await c.html(/* @__PURE__ */ jsx(App, {}))).text();
|
|
52
|
-
html = transformHtmlTemplate(head,
|
|
97
|
+
html = transformHtmlTemplate(head, templateWithPayload.replace("<!--ssr-outlet-->", html ?? ""));
|
|
53
98
|
return new Response(html, responseInit);
|
|
54
99
|
})).request("/");
|
|
55
|
-
const stream = wrapStream(head, renderToReadableStream(/* @__PURE__ */ jsx(App, {})),
|
|
100
|
+
const stream = wrapStream(head, renderToReadableStream(/* @__PURE__ */ jsx(App, {})), templateWithPayload, void 0, { flushChunk: () => {
|
|
101
|
+
let chunk;
|
|
102
|
+
try {
|
|
103
|
+
chunk = renderSSRHeadSuspenseChunk(head);
|
|
104
|
+
} catch {
|
|
105
|
+
return "";
|
|
106
|
+
}
|
|
107
|
+
if (!chunk) return "";
|
|
108
|
+
return `<script${scriptNonceAttr(nonce)}>window.__unhead__&&(${chunk});document.currentScript.remove()<\/script>`;
|
|
109
|
+
} });
|
|
56
110
|
return new Response(stream, responseInit);
|
|
57
111
|
};
|
|
58
112
|
};
|
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 { 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
|
+
{"version":3,"file":"page.js","names":["Root","RootComponent"],"sources":["../src/page.tsx"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { 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 { HTTPError, HTTPResponse, withServerTiming } from \"nitro/h3\";\nimport { useSeoMeta } from \"unhead\";\nimport type { Unhead } from \"unhead/server\";\nimport { transformHtmlTemplate } from \"unhead/server\";\nimport { createStreamableHead, renderSSRHeadSuspenseChunk, wrapStream } from \"unhead/stream/server\";\nimport type { ResolvableHead, 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\tnonce?: string;\n\t},\n) => EventHandlerResponse;\n\nexport type PageRenderer<P = unknown, Q = unknown> = (\n\tevent: H3Event,\n\tparams: {\n\t\thead: Unhead;\n\t\tseoHead: (input: UseSeoMetaInput) => void;\n\t\tparams: P;\n\t\tquery: Q;\n\t},\n) => HTTPResponse | Child | Promise<Child | HTTPResponse>;\n\nexport interface PageCacheOptions {\n\tmaxAge: number;\n\tswr?: number;\n\tprivate?: boolean;\n}\n\ninterface DefinePageOptions<P = unknown, Q = unknown> {\n\trender: PageRenderer<P, Q>;\n\tstream?: boolean;\n\t/**\n\t * Cache-Control for the page response. `60` is shorthand for\n\t * `{ maxAge: 60 }` → `Cache-Control: public, max-age=60`; the object form\n\t * adds `stale-while-revalidate` and `private`.\n\t */\n\tcache?: number | PageCacheOptions;\n\t/**\n\t * Standard-schema validator (zod, valibot, arktype, …) for the route\n\t * params. On failure the page throws HTTPError 404 before render; the\n\t * validated, typed output is passed to render as `params`.\n\t */\n\tparams?: StandardSchemaV1<unknown, P>;\n\t/**\n\t * Standard-schema validator for the search params. On failure the page\n\t * throws HTTPError 400 before render; the validated, typed output is\n\t * passed to render as `query`. Repeated keys collapse to the last value.\n\t */\n\tquery?: StandardSchemaV1<unknown, Q>;\n}\n\nconst cacheControlValue = (cache: number | PageCacheOptions): string => {\n\tconst options = typeof cache === \"number\" ? { maxAge: cache } : cache;\n\n\tconst parts = [options.private ? \"private\" : \"public\", `max-age=${options.maxAge}`];\n\n\tif (options.swr !== undefined) {\n\t\tparts.push(`stale-while-revalidate=${options.swr}`);\n\t}\n\n\treturn parts.join(\", \");\n};\n\n// resolvable head inputs may be functions — resolve them for serialization\nconst unwrapEntryInput = (input: unknown): ResolvableHead => {\n\treturn (\n\t\ttypeof input === \"function\" ? (input as () => ResolvableHead)() : input\n\t) as ResolvableHead;\n};\n\nconst collectHeadInputs = (head: Unhead): ResolvableHead[] => {\n\tconst inputs: ResolvableHead[] = [];\n\n\tfor (const entry of head.entries.values()) {\n\t\tinputs.push(unwrapEntryInput(entry.input));\n\t}\n\n\treturn inputs;\n};\n\nconst serializeHeadPayload = (inputs: ResolvableHead[]): string => {\n\treturn JSON.stringify(inputs)\n\t\t.replace(/</g, \"\\\\u003c\")\n\t\t.replace(/>/g, \"\\\\u003e\")\n\t\t.replace(/&/g, \"\\\\u0026\");\n};\n\nconst scriptNonceAttr = (nonce?: string): string => {\n\treturn nonce ? ` nonce=\"${nonce.replace(/\"/g, \""\")}\"` : \"\";\n};\n\n// client handshake: re-register the server head entries (titleTemplate,\n// htmlAttrs, page-level head) into the client head via the unhead stream\n// queue, so post-hydration updates keep templates and adopt SSR tags\n// instead of duplicating them\nconst headPayloadScript = (inputs: ResolvableHead[], nonce?: string): string => {\n\tif (!inputs.length) {\n\t\treturn \"\";\n\t}\n\n\treturn `<script${scriptNonceAttr(nonce)}>window.__unhead__&&window.__unhead__.push(${serializeHeadPayload(inputs)})</script>`;\n};\n\n// injected at the start of <body>: unhead rebuilds the html/head tags and\n// appends its head tags (including the stream bootstrap shim) inside <head>,\n// so the payload must come after them for window.__unhead__ to exist\nconst injectHeadPayload = (template: string, script: string): string => {\n\tif (!script) {\n\t\treturn template;\n\t}\n\n\tif (/<body[^>]*>/.test(template)) {\n\t\treturn template.replace(/<body[^>]*>/, match => `${match}${script}`);\n\t}\n\n\treturn template.replace(\"</head>\", `</head>${script}`);\n};\n\nconst validateWithSchema = async <T,>(\n\tschema: StandardSchemaV1<unknown, T> | undefined,\n\tvalue: unknown,\n\tstatus: 400 | 404,\n\twhat: string,\n): Promise<T> => {\n\tif (!schema) {\n\t\treturn value as T;\n\t}\n\n\tconst result = await schema[\"~standard\"].validate(value);\n\n\tif (result.issues) {\n\t\tthrow new HTTPError(`Invalid ${what}`, {\n\t\t\tstatus,\n\t\t\tcause: new Error(result.issues.map(issue => issue.message).join(\"; \")),\n\t\t});\n\t}\n\n\treturn result.value;\n};\n\nexport const definePage = <P = unknown, Q = unknown>(\n\toptions: DefinePageOptions<P, Q>,\n): PageHandler => {\n\treturn async (event, { assets, head: headInit, nonce }) => {\n\t\tif (options.cache !== undefined) {\n\t\t\tevent.res.headers.set(\"cache-control\", cacheControlValue(options.cache));\n\t\t}\n\n\t\tconst params = await validateWithSchema(\n\t\t\toptions.params,\n\t\t\tevent.context.params,\n\t\t\t404,\n\t\t\t\"path params\",\n\t\t);\n\n\t\tconst query = await validateWithSchema(\n\t\t\toptions.query,\n\t\t\tObject.fromEntries(event.url.searchParams),\n\t\t\t400,\n\t\t\t\"query params\",\n\t\t);\n\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, params, query });\n\n\t\tif (content instanceof HTTPResponse) {\n\t\t\treturn content;\n\t\t}\n\n\t\t// must be collected before wrapStream — it clears entries after the shell\n\t\tconst payloadScript = headPayloadScript(collectHeadInputs(head), nonce);\n\n\t\tconst templateWithPayload = injectHeadPayload(template, payloadScript);\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\ttemplateWithPayload.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(\n\t\t\thead,\n\t\t\trenderToReadableStream(<App />),\n\t\t\ttemplateWithPayload,\n\t\t\tundefined,\n\t\t\t{\n\t\t\t\t// same as unhead's default flushChunk, but with CSP nonce support\n\t\t\t\tflushChunk: () => {\n\t\t\t\t\tlet chunk: string;\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tchunk = renderSSRHeadSuspenseChunk(head);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn \"\";\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!chunk) {\n\t\t\t\t\t\treturn \"\";\n\t\t\t\t\t}\n\n\t\t\t\t\treturn `<script${scriptNonceAttr(nonce)}>window.__unhead__&&(${chunk});document.currentScript.remove()</script>`;\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\n\t\treturn new Response(stream, responseInit);\n\t};\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Child, FC } from "hono/jsx";
|
|
2
|
+
//#region src/safeAsync.d.ts
|
|
3
|
+
export interface SafeAsyncFallbackProps {
|
|
4
|
+
error: unknown;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Wraps an async server component with error handling for streaming SSR.
|
|
8
|
+
*
|
|
9
|
+
* When an async component inside a Suspense boundary rejects after the shell
|
|
10
|
+
* has been flushed, the streaming renderer swallows the error: the browser
|
|
11
|
+
* keeps the loading fallback forever with a 200 status, and nothing reaches
|
|
12
|
+
* the error handler. `safeAsync` awaits the component directly, so a
|
|
13
|
+
* rejection is caught here and rendered as `fallback` instead.
|
|
14
|
+
*
|
|
15
|
+
* Catches errors of the wrapped component itself, not of its subtree.
|
|
16
|
+
*/
|
|
17
|
+
export declare const safeAsync: <P extends object>(Component: (props: P) => Child | Promise<Child>, fallback?: FC<SafeAsyncFallbackProps>) => FC<P>;
|
|
18
|
+
//#endregion
|
|
19
|
+
//# sourceMappingURL=safeAsync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safeAsync.d.ts","names":[],"sources":["../src/safeAsync.tsx"],"mappings":";;iBAEiB;EAChB;;;;;;;;;;;;;qBAwBY,YAAa,kBACzB,YAAY,OAAO,MAAM,QAAQ,QAAQ,QACzC,WAAU,GAAG,4BACX,GAAG"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx } from "hono/jsx/jsx-runtime";
|
|
2
|
+
//#region src/safeAsync.tsx
|
|
3
|
+
const DefaultFallback = () => /* @__PURE__ */ jsx("div", {
|
|
4
|
+
role: "alert",
|
|
5
|
+
"data-testid": "yamf-error",
|
|
6
|
+
style: "padding: 1rem; border: 1px solid currentColor",
|
|
7
|
+
children: "Something went wrong."
|
|
8
|
+
});
|
|
9
|
+
/**
|
|
10
|
+
* Wraps an async server component with error handling for streaming SSR.
|
|
11
|
+
*
|
|
12
|
+
* When an async component inside a Suspense boundary rejects after the shell
|
|
13
|
+
* has been flushed, the streaming renderer swallows the error: the browser
|
|
14
|
+
* keeps the loading fallback forever with a 200 status, and nothing reaches
|
|
15
|
+
* the error handler. `safeAsync` awaits the component directly, so a
|
|
16
|
+
* rejection is caught here and rendered as `fallback` instead.
|
|
17
|
+
*
|
|
18
|
+
* Catches errors of the wrapped component itself, not of its subtree.
|
|
19
|
+
*/
|
|
20
|
+
const safeAsync = (Component, fallback = DefaultFallback) => {
|
|
21
|
+
const Safe = async (props) => {
|
|
22
|
+
try {
|
|
23
|
+
return await Component(props);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error("[yamf] safeAsync: async component rejected", error);
|
|
26
|
+
return fallback({ error });
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
return Safe;
|
|
30
|
+
};
|
|
31
|
+
//#endregion
|
|
32
|
+
export { safeAsync };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=safeAsync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safeAsync.js","names":[],"sources":["../src/safeAsync.tsx"],"sourcesContent":["import type { Child, FC } from \"hono/jsx\";\n\nexport interface SafeAsyncFallbackProps {\n\terror: unknown;\n}\n\nconst DefaultFallback: FC<SafeAsyncFallbackProps> = () => (\n\t<div\n\t\trole=\"alert\"\n\t\tdata-testid=\"yamf-error\"\n\t\tstyle=\"padding: 1rem; border: 1px solid currentColor\"\n\t>\n\t\tSomething went wrong.\n\t</div>\n);\n\n/**\n * Wraps an async server component with error handling for streaming SSR.\n *\n * When an async component inside a Suspense boundary rejects after the shell\n * has been flushed, the streaming renderer swallows the error: the browser\n * keeps the loading fallback forever with a 200 status, and nothing reaches\n * the error handler. `safeAsync` awaits the component directly, so a\n * rejection is caught here and rendered as `fallback` instead.\n *\n * Catches errors of the wrapped component itself, not of its subtree.\n */\nexport const safeAsync = <P extends object>(\n\tComponent: (props: P) => Child | Promise<Child>,\n\tfallback: FC<SafeAsyncFallbackProps> = DefaultFallback,\n): FC<P> => {\n\tconst Safe = async (props: P): Promise<Child> => {\n\t\ttry {\n\t\t\treturn await Component(props);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[yamf] safeAsync: async component rejected\", error);\n\n\t\t\treturn fallback({ error });\n\t\t}\n\t};\n\n\t// hono's FC type does not include Promise<Child> in its return type,\n\t// but the JSX renderer awaits component results\n\t// oxlint-disable-next-line typescript/no-unsafe-type-assertion\n\treturn Safe as FC<P>;\n};\n"],"mappings":";;AAMA,MAAM,wBACL,oBAAC,OAAD;CACC,MAAK;CACL,eAAY;CACZ,OAAM;CACN,UAAA;AAEI,CAAA;;;;;;;;;;;;AAcN,MAAa,aACZ,WACA,WAAuC,oBAC5B;CACX,MAAM,OAAO,OAAO,UAA6B;EAChD,IAAI;GACH,OAAO,MAAM,UAAU,KAAK;EAC7B,SAAS,OAAO;GACf,QAAQ,MAAM,8CAA8C,KAAK;GAEjE,OAAO,SAAS,EAAE,MAAM,CAAC;EAC1B;CACD;CAKA,OAAO;AACR"}
|
package/dist/server/entry.d.mts
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { YamfHead } from "./shared/head.mjs";
|
|
2
2
|
import { EventHandlerRequest, EventHandlerWithFetch, H3Event } from "nitro/h3";
|
|
3
3
|
//#region src/server/entry.d.ts
|
|
4
|
-
type ServerEntry = EventHandlerWithFetch<EventHandlerRequest, Promise<unknown>>;
|
|
5
|
-
interface DefineServerEntryOptions {
|
|
4
|
+
export type ServerEntry = EventHandlerWithFetch<EventHandlerRequest, Promise<unknown>>;
|
|
5
|
+
export interface DefineServerEntryOptions {
|
|
6
6
|
head?: YamfHead | ((event: H3Event) => YamfHead);
|
|
7
7
|
disableEarlyHints?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* CSP nonce for the inline scripts yamf injects (head handshake payload
|
|
10
|
+
* and streamed head patches).
|
|
11
|
+
*
|
|
12
|
+
* Note: unhead's stream bootstrap script (`window.__unhead__||(…)`) does
|
|
13
|
+
* not support a nonce in unhead 3.x — with a strict `script-src` CSP it
|
|
14
|
+
* must be allowed separately (e.g. via a hash).
|
|
15
|
+
*/
|
|
16
|
+
nonce?: string | ((event: H3Event) => string | undefined);
|
|
8
17
|
}
|
|
9
|
-
declare const defineServerEntry: (options?: DefineServerEntryOptions) => ServerEntry;
|
|
18
|
+
export declare const defineServerEntry: (options?: DefineServerEntryOptions) => ServerEntry;
|
|
10
19
|
//#endregion
|
|
11
|
-
export { DefineServerEntryOptions, ServerEntry, defineServerEntry };
|
|
12
20
|
//# sourceMappingURL=entry.d.mts.map
|
|
@@ -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":";;;YAkEY,cAAc,sBAAsB,qBAAqB;iBAEpD;EAChB,OAAO,aAAa,OAAO,YAAY;EACvC;;;;;;;;;EASA,mBAAmB,OAAO;;qBAGd,oBAAqB,UAAU,6BAA2B"}
|
package/dist/server/entry.mjs
CHANGED
|
@@ -8,12 +8,16 @@ import { assets, pages } from "virtual:yamf:pages";
|
|
|
8
8
|
import { rootAssets } from "virtual:yamf:root";
|
|
9
9
|
//#region src/server/entry.tsx
|
|
10
10
|
const router = createRouter();
|
|
11
|
+
const registeredRoutes = /* @__PURE__ */ new Map();
|
|
11
12
|
for (const [relativePath, handler] of Object.entries(pages).toSorted((a, b) => a[0].localeCompare(b[0]))) {
|
|
12
13
|
const ext = path.extname(relativePath);
|
|
13
14
|
let route = path.relative("/src/pages", relativePath);
|
|
14
15
|
if (ext.length) route = route.slice(0, -ext.length);
|
|
15
16
|
route = route.replace(/\.[A-Za-z]+$/, "").replace(/\(([^(/\\]+)\)[/\\]/g, "").replace(/\[\.{3}]/g, "**").replace(/\[\.{3}([^\]]+)]/g, (_, p) => "**:" + p.replace(/[^\w-]/g, "_")).replace(/\[([^/\]]+)]/g, (_, p) => ":" + p.replace(/[^\w-]/g, "_")).replace(/(\/|^)index$/, "") || "/";
|
|
16
17
|
route = withLeadingSlash(withoutTrailingSlash(route));
|
|
18
|
+
const conflictingFile = registeredRoutes.get(route);
|
|
19
|
+
if (conflictingFile && conflictingFile !== relativePath) console.warn(`[yamf] duplicate page route "${route}": ${conflictingFile} and ${relativePath} both map to it — the routes conflict`);
|
|
20
|
+
registeredRoutes.set(route, relativePath);
|
|
17
21
|
addRoute(router, "GET", route, {
|
|
18
22
|
handler,
|
|
19
23
|
serverAssets: assets[relativePath]
|
|
@@ -26,13 +30,14 @@ const defineServerEntry = (options) => {
|
|
|
26
30
|
const { handler, serverAssets } = route.data;
|
|
27
31
|
event.context.params = route.params;
|
|
28
32
|
const assets = clientAssets.merge(...[rootAssets, serverAssets].filter((x) => !!x));
|
|
29
|
-
if (!options?.disableEarlyHints) {
|
|
33
|
+
if (!options?.disableEarlyHints && !event.req.headers.has("x-nitro-prerender")) {
|
|
30
34
|
const link = [...assets.js.map((script) => `<${script.href}>; rel=modulepreload`), ...assets.css.map((style) => `<${style.href}>; rel=preload; as=style`)];
|
|
31
35
|
if (link.length) await writeEarlyHints(event, { link });
|
|
32
36
|
}
|
|
33
37
|
return (await handler())(event, {
|
|
34
38
|
assets,
|
|
35
|
-
head: typeof options?.head === "function" ? options.head(event) : options?.head
|
|
39
|
+
head: typeof options?.head === "function" ? options.head(event) : options?.head,
|
|
40
|
+
nonce: typeof options?.nonce === "function" ? options.nonce(event) : options?.nonce
|
|
36
41
|
});
|
|
37
42
|
};
|
|
38
43
|
return defineHandler(async (event) => {
|
|
@@ -40,7 +45,8 @@ const defineServerEntry = (options) => {
|
|
|
40
45
|
try {
|
|
41
46
|
return await rootHandler(event);
|
|
42
47
|
} catch (error) {
|
|
43
|
-
|
|
48
|
+
const httpError = error instanceof HTTPError ? error : new HTTPError({ cause: error });
|
|
49
|
+
return errorHandler(httpError, event);
|
|
44
50
|
}
|
|
45
51
|
});
|
|
46
52
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.mjs","names":["pagesServerAssets"],"sources":["../../src/server/entry.tsx"],"sourcesContent":["import path from \"node:path\";\nimport {\n\tdefineHandler,\n\tEventHandler,\n\tH3Event,\n\tHTTPError,\n\twriteEarlyHints,\n\ttype EventHandlerRequest,\n\ttype EventHandlerWithFetch,\n} from \"nitro/h3\";\nimport { addRoute, createRouter, findRoute } from \"rou3\";\nimport { withLeadingSlash, withoutTrailingSlash } from \"ufo\";\nimport { clientAssets } from \"virtual:yamf:assets\";\nimport { errorHandler } from \"virtual:yamf:error-handler\";\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\tconst rootHandler: EventHandler<EventHandlerRequest, Promise<unknown>> = 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\treturn defineHandler(async event => {\n\t\tif (!errorHandler) {\n\t\t\treturn rootHandler(event);\n\t\t}\n\n\t\ttry {\n\t\t\treturn await rootHandler(event);\n\t\t} catch (error) {\n\t\t\tconst httpError = error instanceof HTTPError ? error : new HTTPError({ cause: error });\n\n\t\t\treturn errorHandler(httpError, event);\n\t\t}\n\t});\n};\n"],"mappings":";;;;;;;;;AAwBA,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;
|
|
1
|
+
{"version":3,"file":"entry.mjs","names":["pagesServerAssets"],"sources":["../../src/server/entry.tsx"],"sourcesContent":["import path from \"node:path\";\nimport {\n\tdefineHandler,\n\tEventHandler,\n\tH3Event,\n\tHTTPError,\n\twriteEarlyHints,\n\ttype EventHandlerRequest,\n\ttype EventHandlerWithFetch,\n} from \"nitro/h3\";\nimport { addRoute, createRouter, findRoute } from \"rou3\";\nimport { withLeadingSlash, withoutTrailingSlash } from \"ufo\";\nimport { clientAssets } from \"virtual:yamf:assets\";\nimport { errorHandler } from \"virtual:yamf:error-handler\";\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\nconst registeredRoutes = new Map<string, string>();\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\tconst conflictingFile = registeredRoutes.get(route);\n\n\tif (conflictingFile && conflictingFile !== relativePath) {\n\t\tconsole.warn(\n\t\t\t`[yamf] duplicate page route \"${route}\": ${conflictingFile} and ${relativePath} both map to it — the routes conflict`,\n\t\t);\n\t}\n\n\tregisteredRoutes.set(route, relativePath);\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\t/**\n\t * CSP nonce for the inline scripts yamf injects (head handshake payload\n\t * and streamed head patches).\n\t *\n\t * Note: unhead's stream bootstrap script (`window.__unhead__||(…)`) does\n\t * not support a nonce in unhead 3.x — with a strict `script-src` CSP it\n\t * must be allowed separately (e.g. via a hash).\n\t */\n\tnonce?: string | ((event: H3Event) => string | undefined);\n}\n\nexport const defineServerEntry = (options?: DefineServerEntryOptions): ServerEntry => {\n\tconst rootHandler: EventHandler<EventHandlerRequest, Promise<unknown>> = 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\t// early hints are pointless inside the prerender worker — nitro's\n\t\t// prerenderer buffers the response to a file and discards them\n\t\tif (!options?.disableEarlyHints && !event.req.headers.has(\"x-nitro-prerender\")) {\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\tnonce: typeof options?.nonce === \"function\" ? options.nonce(event) : options?.nonce,\n\t\t});\n\t};\n\n\treturn defineHandler(async event => {\n\t\tif (!errorHandler) {\n\t\t\treturn rootHandler(event);\n\t\t}\n\n\t\ttry {\n\t\t\treturn await rootHandler(event);\n\t\t} catch (error) {\n\t\t\tconst httpError = error instanceof HTTPError ? error : new HTTPError({ cause: error });\n\n\t\t\treturn errorHandler(httpError, event);\n\t\t}\n\t});\n};\n"],"mappings":";;;;;;;;;AAwBA,MAAM,SAAS,aAAoB;AAEnC,MAAM,mCAAmB,IAAI,IAAoB;AAEjD,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,MAAM,kBAAkB,iBAAiB,IAAI,KAAK;CAElD,IAAI,mBAAmB,oBAAoB,cAC1C,QAAQ,KACP,gCAAgC,MAAM,KAAK,gBAAgB,OAAO,aAAa,sCAChF;CAGD,iBAAiB,IAAI,OAAO,YAAY;CAExC,SAAS,QAAQ,OAAO,OAAO;EAC9B;EACA,cAAcA,OAAkB;CACjC,CAAC;AACF;AAkBA,MAAa,qBAAqB,YAAoD;CACrF,MAAM,cAAmE,OAAM,UAAS;EACvF,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;EAIhF,IAAI,CAAC,SAAS,qBAAqB,CAAC,MAAM,IAAI,QAAQ,IAAI,mBAAmB,GAAG;GAC/E,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;GAC3E,OAAO,OAAO,SAAS,UAAU,aAAa,QAAQ,MAAM,KAAK,IAAI,SAAS;EAC/E,CAAC;CACF;CAEA,OAAO,cAAc,OAAM,UAAS;EACnC,IAAI,CAAC,cACJ,OAAO,YAAY,KAAK;EAGzB,IAAI;GACH,OAAO,MAAM,YAAY,KAAK;EAC/B,SAAS,OAAO;GACf,MAAM,YAAY,iBAAiB,YAAY,QAAQ,IAAI,UAAU,EAAE,OAAO,MAAM,CAAC;GAErF,OAAO,aAAa,WAAW,KAAK;EACrC;CACD,CAAC;AACF"}
|
|
@@ -9,14 +9,13 @@ declare module "hono/jsx" {
|
|
|
9
9
|
"island-props"?: string;
|
|
10
10
|
"island-src": string;
|
|
11
11
|
"island-entry": string;
|
|
12
|
-
"island-client"
|
|
12
|
+
"island-client"?: IslandClientDirective;
|
|
13
13
|
children: Child;
|
|
14
14
|
style?: JSX.CSSProperties;
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
-
declare const createIsland: (Component: FC, exportName: string, assets: ImportAssetsResultRaw) => FC;
|
|
19
|
+
export declare const createIsland: (Component: FC, exportName: string, assets: ImportAssetsResultRaw) => FC;
|
|
20
20
|
//#endregion
|
|
21
|
-
export { createIsland };
|
|
22
21
|
//# sourceMappingURL=server.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.mts","names":[],"sources":["../../../src/island/server.tsx"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"server.d.mts","names":[],"sources":["../../../src/island/server.tsx"],"mappings":";;;;;YASW;cACC;MACT;QACC;QACA;QACA;QACA,kBAAkB;QAClB,UAAU;QACV,QAAQ,IAAI;;;;;qBAUH,eACZ,WAAW,IACX,oBACA,QAAQ,0BACN"}
|
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import { stringify } from "devalue";
|
|
2
2
|
import { jsx } from "hono/jsx/jsx-runtime";
|
|
3
3
|
//#region src/island/server.tsx
|
|
4
|
+
const PROPS_WARNING_LIMIT = Number(import.meta.env["YAMF_ISLAND_PROPS_LIMIT"]) || 16384;
|
|
5
|
+
const isSkipDirective = (directive) => {
|
|
6
|
+
return directive === false || directive === "skip";
|
|
7
|
+
};
|
|
4
8
|
const createIsland = (Component, exportName, assets) => {
|
|
5
|
-
const ComponentWrapper = (props) => {
|
|
9
|
+
const ComponentWrapper = ({ "yamf-client": clientDirective, ...props }) => {
|
|
6
10
|
if (!assets.entry) throw new Error(`Missing island entry for island ${Component.name}`);
|
|
11
|
+
const skip = isSkipDirective(clientDirective);
|
|
12
|
+
let islandProps;
|
|
13
|
+
if (!skip) {
|
|
14
|
+
islandProps = stringify(props);
|
|
15
|
+
if (import.meta.env.DEV && islandProps.length > PROPS_WARNING_LIMIT) console.warn(`[yamf] island "${exportName}" receives ${(islandProps.length / 1024).toFixed(1)} KB of serialized props — consider slimming them down (limit: ${PROPS_WARNING_LIMIT} bytes, configurable via YAMF_ISLAND_PROPS_LIMIT)`);
|
|
16
|
+
}
|
|
7
17
|
return /* @__PURE__ */ jsx("yamf-island", {
|
|
8
|
-
"island-props":
|
|
18
|
+
"island-props": islandProps,
|
|
9
19
|
"island-src": assets.entry,
|
|
10
20
|
"island-entry": exportName,
|
|
11
|
-
"island-client":
|
|
21
|
+
"island-client": clientDirective,
|
|
12
22
|
style: { display: "contents" },
|
|
13
23
|
children: /* @__PURE__ */ jsx(Component, { ...props })
|
|
14
24
|
});
|
|
@@ -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\"
|
|
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, IslandProps } from \"./types\";\n\n// configurable via the YAMF_ISLAND_PROPS_LIMIT env / define\nconst PROPS_WARNING_LIMIT = Number(import.meta.env[\"YAMF_ISLAND_PROPS_LIMIT\"]) || 16 * 1024;\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\nconst isSkipDirective = (directive: IslandClientDirective | undefined): boolean => {\n\treturn directive === false || directive === \"skip\";\n};\n\nexport const createIsland = (\n\tComponent: FC,\n\texportName: string,\n\tassets: ImportAssetsResultRaw,\n): FC => {\n\tconst ComponentWrapper: FC<IslandProps> & { name: string } = ({\n\t\t\"yamf-client\": clientDirective,\n\t\t...props\n\t}) => {\n\t\tif (!assets.entry) {\n\t\t\tthrow new Error(`Missing island entry for island ${Component.name}`);\n\t\t}\n\n\t\t// skip islands are never hydrated — serializing their props into the\n\t\t// attribute would be pure dead weight in the HTML\n\t\tconst skip = isSkipDirective(clientDirective);\n\n\t\tlet islandProps: string | undefined;\n\n\t\tif (!skip) {\n\t\t\tislandProps = stringify(props);\n\n\t\t\tif (import.meta.env.DEV && islandProps.length > PROPS_WARNING_LIMIT) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`[yamf] island \"${exportName}\" receives ${(islandProps.length / 1024).toFixed(1)} KB of serialized props — consider slimming them down (limit: ${PROPS_WARNING_LIMIT} bytes, configurable via YAMF_ISLAND_PROPS_LIMIT)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn (\n\t\t\t<yamf-island\n\t\t\t\tisland-props={islandProps}\n\t\t\t\tisland-src={assets.entry}\n\t\t\t\tisland-entry={exportName}\n\t\t\t\tisland-client={clientDirective}\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":";;;AAMA,MAAM,sBAAsB,OAAO,YAAY,IAAI,0BAA0B,KAAK;AAiBlF,MAAM,mBAAmB,cAA0D;CAClF,OAAO,cAAc,SAAS,cAAc;AAC7C;AAEA,MAAa,gBACZ,WACA,YACA,WACQ;CACR,MAAM,oBAAwD,EAC7D,eAAe,iBACf,GAAG,YACE;EACL,IAAI,CAAC,OAAO,OACX,MAAM,IAAI,MAAM,mCAAmC,UAAU,MAAM;EAKpE,MAAM,OAAO,gBAAgB,eAAe;EAE5C,IAAI;EAEJ,IAAI,CAAC,MAAM;GACV,cAAc,UAAU,KAAK;GAE7B,IAAI,YAAY,IAAI,OAAO,YAAY,SAAS,qBAC/C,QAAQ,KACP,kBAAkB,WAAW,cAAc,YAAY,SAAS,KAAA,CAAM,QAAQ,CAAC,EAAE,gEAAgE,oBAAoB,kDACtK;EAEF;EAEA,OACC,oBAAC,eAAD;GACC,gBAAc;GACd,cAAY,OAAO;GACnB,gBAAc;GACd,iBAAe;GAEf,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,4 @@
|
|
|
1
1
|
//#region src/island/types.d.ts
|
|
2
|
-
type IslandClientDirective = "load" | "idle" | "visible" | "skip" | boolean;
|
|
2
|
+
export type IslandClientDirective = "load" | "idle" | "visible" | "skip" | boolean;
|
|
3
3
|
//#endregion
|
|
4
|
-
export { IslandClientDirective };
|
|
5
4
|
//# sourceMappingURL=types.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/island/types.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/island/types.ts"],"mappings":";YAAY"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/shared/assets.d.ts
|
|
2
|
-
type ImportAssetsResultRaw = {
|
|
2
|
+
export type ImportAssetsResultRaw = {
|
|
3
3
|
entry?: string;
|
|
4
4
|
js: {
|
|
5
5
|
href: string;
|
|
@@ -10,5 +10,4 @@ type ImportAssetsResultRaw = {
|
|
|
10
10
|
}[];
|
|
11
11
|
};
|
|
12
12
|
//#endregion
|
|
13
|
-
export { ImportAssetsResultRaw };
|
|
14
13
|
//# sourceMappingURL=assets.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assets.d.mts","names":[],"sources":["../../../src/shared/assets.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"assets.d.mts","names":[],"sources":["../../../src/shared/assets.ts"],"mappings":";YAKY;EACX;EACA;IAAM;;EACN;IAAO;IAAc"}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { ResolvableHead, UseSeoMetaInput } from "unhead/types";
|
|
2
2
|
//#region src/shared/head.d.ts
|
|
3
|
-
type YamfHead = ResolvableHead & {
|
|
3
|
+
export type YamfHead = ResolvableHead & {
|
|
4
4
|
seo?: UseSeoMetaInput;
|
|
5
5
|
};
|
|
6
6
|
//#endregion
|
|
7
|
-
export { YamfHead };
|
|
8
7
|
//# sourceMappingURL=head.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"head.d.mts","names":[],"sources":["../../../src/shared/head.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"head.d.mts","names":[],"sources":["../../../src/shared/head.ts"],"mappings":";;YAEY,WAAW;EAAmB,MAAM"}
|
package/dist/shared/assets.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
//#region src/shared/assets.d.ts
|
|
2
|
-
type ImportAssetsResult = ImportAssetsResultRaw & {
|
|
2
|
+
export type ImportAssetsResult = ImportAssetsResultRaw & {
|
|
3
3
|
merge(...args: ImportAssetsResultRaw[]): ImportAssetsResult;
|
|
4
4
|
};
|
|
5
|
-
type ImportAssetsResultRaw = {
|
|
5
|
+
export type ImportAssetsResultRaw = {
|
|
6
6
|
entry?: string;
|
|
7
7
|
js: {
|
|
8
8
|
href: string;
|
|
@@ -13,5 +13,4 @@ type ImportAssetsResultRaw = {
|
|
|
13
13
|
}[];
|
|
14
14
|
};
|
|
15
15
|
//#endregion
|
|
16
|
-
export { ImportAssetsResult, ImportAssetsResultRaw };
|
|
17
16
|
//# sourceMappingURL=assets.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assets.d.ts","names":[],"sources":["../../src/shared/assets.ts"],"mappings":";
|
|
1
|
+
{"version":3,"file":"assets.d.ts","names":[],"sources":["../../src/shared/assets.ts"],"mappings":";YACY,qBAAqB;EAChC,SAAS,MAAM,0BAA0B;;YAG9B;EACX;EACA;IAAM;;EACN;IAAO;IAAc"}
|
package/dist/shared/head.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { ResolvableHead, UseSeoMetaInput } from "unhead/types";
|
|
2
2
|
//#region src/shared/head.d.ts
|
|
3
|
-
type YamfHead = ResolvableHead & {
|
|
3
|
+
export type YamfHead = ResolvableHead & {
|
|
4
4
|
seo?: UseSeoMetaInput;
|
|
5
5
|
};
|
|
6
6
|
//#endregion
|
|
7
|
-
export { YamfHead };
|
|
8
7
|
//# sourceMappingURL=head.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"head.d.ts","names":[],"sources":["../../src/shared/head.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"head.d.ts","names":[],"sources":["../../src/shared/head.ts"],"mappings":";;YAEY,WAAW;EAAmB,MAAM"}
|
package/dist/vite/index.d.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { NitroPluginConfig } from "nitro/vite";
|
|
2
2
|
import { PluginOption } from "vite";
|
|
3
3
|
//#region src/vite/index.d.ts
|
|
4
|
-
interface YamfOptions {
|
|
4
|
+
export interface YamfOptions {
|
|
5
5
|
nitro?: NitroPluginConfig;
|
|
6
6
|
}
|
|
7
7
|
declare const yamf: (options?: YamfOptions) => PluginOption[];
|
|
8
8
|
//#endregion
|
|
9
|
-
export {
|
|
9
|
+
export { yamf as default };
|
|
10
10
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/vite/index.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/vite/index.ts"],"mappings":";;;iBAaiB;EAChB,QAAQ;;cAGH,OAAQ,UAAU,gBAAc"}
|
package/dist/vite/index.mjs
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { islands } from "./islands.mjs";
|
|
2
|
+
import { prerenderModule } from "./prerender.mjs";
|
|
2
3
|
import { virtualAssets } from "./virtual-assets.mjs";
|
|
3
4
|
import { virtualErrorHandler } from "./virtual-error-handler.mjs";
|
|
4
5
|
import { virtualPages } from "./virtual-pages.mjs";
|
|
5
6
|
import { virtualRoot } from "./virtual-root.mjs";
|
|
6
7
|
import { virtualTemplate } from "./virtual-template.mjs";
|
|
7
8
|
import { existsSync } from "node:fs";
|
|
9
|
+
import { resolve } from "node:path";
|
|
8
10
|
import { nitro } from "nitro/vite";
|
|
9
11
|
//#region src/vite/index.ts
|
|
10
12
|
const yamf = (options) => {
|
|
11
13
|
const plugins = [];
|
|
12
14
|
plugins.push({
|
|
13
15
|
name: "yamf:config",
|
|
14
|
-
config() {
|
|
16
|
+
config(config) {
|
|
17
|
+
const root = config.root ?? process.cwd();
|
|
15
18
|
return {
|
|
16
19
|
ssr: { noExternal: true },
|
|
17
20
|
optimizeDeps: { include: [
|
|
@@ -36,7 +39,7 @@ const yamf = (options) => {
|
|
|
36
39
|
}
|
|
37
40
|
] },
|
|
38
41
|
environments: {
|
|
39
|
-
...existsSync("./src/server.tsx") ? { ssr: { build: {
|
|
42
|
+
...existsSync(resolve(root, "./src/server.tsx")) ? { ssr: { build: {
|
|
40
43
|
cssCodeSplit: false,
|
|
41
44
|
rolldownOptions: { input: "./src/server.tsx" }
|
|
42
45
|
} } } : {},
|
|
@@ -51,18 +54,23 @@ const yamf = (options) => {
|
|
|
51
54
|
plugins.push(virtualPages());
|
|
52
55
|
plugins.push(virtualTemplate());
|
|
53
56
|
plugins.push(virtualRoot());
|
|
57
|
+
plugins.push({
|
|
58
|
+
name: "yamf:prerender",
|
|
59
|
+
nitro: prerenderModule
|
|
60
|
+
});
|
|
61
|
+
const { serverDir: _serverDir, renderer: _renderer, compressPublicAssets: _compressPublicAssets, publicAssets: userPublicAssets, ...userNitro } = options?.nitro ?? {};
|
|
54
62
|
plugins.push(nitro({
|
|
63
|
+
...userNitro,
|
|
55
64
|
serverDir: "./src",
|
|
56
65
|
renderer: false,
|
|
57
|
-
...options?.nitro,
|
|
58
66
|
compressPublicAssets: {
|
|
59
67
|
gzip: true,
|
|
60
68
|
brotli: true
|
|
61
69
|
},
|
|
62
|
-
publicAssets: [{
|
|
70
|
+
publicAssets: [...userPublicAssets ?? [], {
|
|
63
71
|
baseURL: "assets",
|
|
64
72
|
dir: "./public/assets",
|
|
65
|
-
maxAge:
|
|
73
|
+
maxAge: 31536e3
|
|
66
74
|
}]
|
|
67
75
|
}));
|
|
68
76
|
return plugins;
|
package/dist/vite/index.mjs.map
CHANGED
|
@@ -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 { virtualErrorHandler } from \"./virtual-error-handler\";\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(virtualErrorHandler(options));\n\tplugins.push(virtualAssets());\n\tplugins.push(virtualPages());\n\tplugins.push(virtualTemplate());\n\tplugins.push(virtualRoot());\n\n\tplugins.push(\n\t\tnitro
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/vite/index.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { NitroPluginConfig } from \"nitro/vite\";\nimport { nitro } from \"nitro/vite\";\nimport type { EnvironmentOptions, PluginOption } from \"vite\";\nimport { islands } from \"./islands\";\nimport { prerenderModule } from \"./prerender\";\nimport { virtualAssets } from \"./virtual-assets\";\nimport { virtualErrorHandler } from \"./virtual-error-handler\";\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(config) {\n\t\t\t// resolved against the vite root (not cwd) so running vite from\n\t\t\t// another working directory does not silently drop the ssr entry\n\t\t\tconst root = config.root ?? process.cwd();\n\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(resolve(root, \"./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(virtualErrorHandler(options));\n\tplugins.push(virtualAssets());\n\tplugins.push(virtualPages());\n\tplugins.push(virtualTemplate());\n\tplugins.push(virtualRoot());\n\n\t// registers the nitro module that makes prerender work (see prerenderModule)\n\tplugins.push({\n\t\tname: \"yamf:prerender\",\n\t\tnitro: prerenderModule,\n\t});\n\n\t// reserved nitro keys are yamf-controlled and intentionally win over user\n\t// values; everything else the user passes through wins. user publicAssets\n\t// are merged with yamf's instead of being dropped\n\tconst {\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-vars\n\t\tserverDir: _serverDir,\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-vars\n\t\trenderer: _renderer,\n\t\t// eslint-disable-next-line @typescript-eslint/no-unused-vars\n\t\tcompressPublicAssets: _compressPublicAssets,\n\t\tpublicAssets: userPublicAssets,\n\t\t...userNitro\n\t} = options?.nitro ?? {};\n\n\tplugins.push(\n\t\tnitro({\n\t\t\t...userNitro,\n\t\t\tserverDir: \"./src\",\n\t\t\trenderer: false,\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...(userPublicAssets ?? []),\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":";;;;;;;;;;;AAiBA,MAAM,QAAQ,YAA0C;CACvD,MAAM,UAA0B,CAAC;CAGjC,QAAQ,KAAK;EACZ,MAAM;EACN,OAAO,QAAQ;GAGd,MAAM,OAAO,OAAO,QAAQ,QAAQ,IAAI;GAYxC,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,QAAQ,MAAM,kBAAkB,CAAC,IAAI,EAAE,KAAK,EAxC5D,OAAO;MAEN,cAAc;MACd,iBAAiB,EAChB,OAAO,mBACR;KACD,EAkCiE,EAAE,IAAI,CAAC;KACvE,QAAQ,EACP,OAAO,EACN,iBAAiB,EAChB,OAAO,wBACR,EACD,EACD;IACD;GACD;EACD;CACD,CAAC;CAED,QAAQ,KAAK,QAAQ,CAAC;CAEtB,QAAQ,KAAK,oBAAoB,OAAO,CAAC;CACzC,QAAQ,KAAK,cAAc,CAAC;CAC5B,QAAQ,KAAK,aAAa,CAAC;CAC3B,QAAQ,KAAK,gBAAgB,CAAC;CAC9B,QAAQ,KAAK,YAAY,CAAC;CAG1B,QAAQ,KAAK;EACZ,MAAM;EACN,OAAO;CACR,CAAC;CAKD,MAAM,EAEL,WAAW,YAEX,UAAU,WAEV,sBAAsB,uBACtB,cAAc,kBACd,GAAG,cACA,SAAS,SAAS,CAAC;CAEvB,QAAQ,KACP,MAAM;EACL,GAAG;EACH,WAAW;EACX,UAAU;EACV,sBAAsB;GACrB,MAAM;GACN,QAAQ;EACT;EACA,cAAc,CACb,GAAI,oBAAoB,CAAC,GACzB;GACC,SAAS;GACT,KAAK;GACL,QAAQ;EACT,CACD;CACD,CAAC,CACF;CAEA,OAAO;AACR"}
|