@pajecawav/yamf 0.0.8 → 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 +2 -2
- package/dist/components/Head.js.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/useHead.d.ts +9 -3
- 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/page.d.ts +31 -4
- 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 +9 -0
- package/dist/server/entry.d.mts.map +1 -1
- package/dist/server/entry.mjs +7 -2
- package/dist/server/entry.mjs.map +1 -1
- package/dist/server/island/server.d.mts.map +1 -1
- package/dist/server/island/server.mjs +11 -1
- package/dist/server/island/server.mjs.map +1 -1
- package/dist/vite/index.d.mts.map +1 -1
- package/dist/vite/index.mjs +12 -4
- 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-root.mjs +11 -4
- package/dist/vite/virtual-root.mjs.map +1 -1
- package/dist/vite/virtual-template.mjs +8 -3
- package/dist/vite/virtual-template.mjs.map +1 -1
- package/package.json +18 -11
- 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 +24 -1
- 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 +59 -5
- 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.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
|
@@ -5,6 +5,15 @@ export type ServerEntry = EventHandlerWithFetch<EventHandlerRequest, Promise<unk
|
|
|
5
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
18
|
export declare const defineServerEntry: (options?: DefineServerEntryOptions) => ServerEntry;
|
|
10
19
|
//#endregion
|
|
@@ -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) => {
|
|
@@ -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"}
|
|
@@ -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,11 +1,21 @@
|
|
|
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
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
21
|
"island-client": clientDirective,
|
|
@@ -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, IslandProps } from \"./types\";\n\ndeclare module \"hono/jsx\" {\n\tnamespace JSX {\n\t\tinterface IntrinsicElements {\n\t\t\t\"yamf-island\": {\n\t\t\t\t\"island-props\"?: string;\n\t\t\t\t\"island-src\": string;\n\t\t\t\t\"island-entry\": string;\n\t\t\t\t\"island-client\"?: IslandClientDirective;\n\t\t\t\tchildren: Child;\n\t\t\t\tstyle?: JSX.CSSProperties;\n\t\t\t};\n\t\t}\n\t}\n}\n\nexport const createIsland = (\n\tComponent: FC,\n\texportName: string,\n\tassets: ImportAssetsResultRaw,\n): FC => {\n\tconst ComponentWrapper: FC<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\treturn (\n\t\t\t<yamf-island\n\t\t\t\tisland-props={
|
|
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 +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,15 +54,20 @@ 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
73
|
maxAge: 31536e3
|
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"}
|
package/dist/vite/islands.mjs
CHANGED
|
@@ -2,6 +2,20 @@ import { withMagicString } from "rolldown-string";
|
|
|
2
2
|
import { Visitor } from "vite";
|
|
3
3
|
//#region src/vite/islands.ts
|
|
4
4
|
const ISLAND_REGEX = /\.island\.(j|t)sx?$/;
|
|
5
|
+
const WRAPPABLE_INIT_TYPES = /* @__PURE__ */ new Set([
|
|
6
|
+
"ArrowFunctionExpression",
|
|
7
|
+
"FunctionExpression",
|
|
8
|
+
"CallExpression",
|
|
9
|
+
"OptionalCallExpression",
|
|
10
|
+
"Identifier",
|
|
11
|
+
"ConditionalExpression",
|
|
12
|
+
"SequenceExpression",
|
|
13
|
+
"TSAsExpression",
|
|
14
|
+
"NonNullExpression"
|
|
15
|
+
]);
|
|
16
|
+
const isWrappableInit = (init) => {
|
|
17
|
+
return WRAPPABLE_INIT_TYPES.has(init.type);
|
|
18
|
+
};
|
|
5
19
|
function langFromId(id) {
|
|
6
20
|
if (id.endsWith(".tsx")) return "tsx";
|
|
7
21
|
if (id.endsWith(".ts")) return "ts";
|
|
@@ -19,23 +33,37 @@ const islands = () => {
|
|
|
19
33
|
s.prepend(`import * as __runtime from "@pajecawav/yamf/server";\n`);
|
|
20
34
|
s.prepend(`import __assets from "${id}?assets=client";\n`);
|
|
21
35
|
const seenExports = /* @__PURE__ */ new Set();
|
|
36
|
+
const warnings = [];
|
|
22
37
|
new Visitor({
|
|
23
38
|
ExportDefaultDeclaration(node) {
|
|
24
39
|
const declaration = node.declaration;
|
|
25
|
-
if (declaration.type !== "FunctionDeclaration" && declaration.type !== "FunctionExpression" && declaration.type !== "ArrowFunctionExpression")
|
|
40
|
+
if (declaration.type !== "FunctionDeclaration" && declaration.type !== "FunctionExpression" && declaration.type !== "ArrowFunctionExpression") {
|
|
41
|
+
warnings.push(`${id}: export default <${declaration.type === "Identifier" ? "identifier" : declaration.type}> is not wrapped as an island — only function components are supported`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
26
44
|
s.overwrite(node.start, declaration.start, "const __ISLAND__ = ");
|
|
27
45
|
s.appendRight(declaration.end, `; export default __runtime.createIsland(__ISLAND__, "default", __assets)`);
|
|
28
46
|
},
|
|
29
47
|
ExportNamedDeclaration(node) {
|
|
30
48
|
const declaration = node.declaration;
|
|
31
|
-
if (declaration
|
|
32
|
-
const
|
|
49
|
+
if (!declaration) {
|
|
50
|
+
if (node.exportKind !== "type") warnings.push(`${id}: export { … } statements are not wrapped as islands — export the component with \`export const\` or \`export function\``);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (declaration.type === "VariableDeclaration") {
|
|
54
|
+
const declarators = declaration.declarations;
|
|
55
|
+
const declarator = declarators[0];
|
|
33
56
|
if (!declarator || declarator.id.type !== "Identifier" || !declarator.init || seenExports.has(declarator.id.name)) return;
|
|
34
57
|
const exportName = declarator.id.name;
|
|
58
|
+
if (!isWrappableInit(declarator.init)) {
|
|
59
|
+
warnings.push(`${id}: export "${exportName}" does not look like a component — left as a plain export, not an island`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (declarators.length > 1) warnings.push(`${id}: only the first declarator of a multi-declarator export is wrapped as an island ("${exportName}") — split the declaration`);
|
|
35
63
|
seenExports.add(exportName);
|
|
36
64
|
s.overwrite(node.start, declarator.init.start, `const __wrap_${exportName} = `);
|
|
37
65
|
s.appendRight(declarator.init.end, `; export const ${exportName} = __runtime.createIsland(__wrap_${exportName}, "${exportName}", __assets)`);
|
|
38
|
-
} else if (declaration
|
|
66
|
+
} else if (declaration.type === "FunctionDeclaration") {
|
|
39
67
|
const fnId = declaration.id;
|
|
40
68
|
if (!fnId || fnId.type !== "Identifier" || seenExports.has(fnId.name)) return;
|
|
41
69
|
const exportName = fnId.name;
|
|
@@ -46,6 +74,7 @@ const islands = () => {
|
|
|
46
74
|
}
|
|
47
75
|
}
|
|
48
76
|
}).visit(program);
|
|
77
|
+
for (const warning of warnings) this.warn(warning);
|
|
49
78
|
})
|
|
50
79
|
}
|
|
51
80
|
}, {
|
|
@@ -53,7 +82,7 @@ const islands = () => {
|
|
|
53
82
|
transform: {
|
|
54
83
|
order: "post",
|
|
55
84
|
handler: withMagicString(function(s) {
|
|
56
|
-
if (s.original.includes("
|
|
85
|
+
if (s.original.includes("__yamf_raw_import__")) s.replaceAll("__yamf_raw_import__", "import");
|
|
57
86
|
})
|
|
58
87
|
}
|
|
59
88
|
}];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"islands.mjs","names":[],"sources":["../../src/vite/islands.ts"],"sourcesContent":["// reference https://github.com/hi-ogawa/vite-plugin-fullstack/blob/28e9540a68529c58842e9a3bf17d2193a065d524/examples/island/src/framework/island/plugin.ts\nimport { withMagicString } from \"rolldown-string\";\nimport type { ParserOptions, Plugin } from \"vite\";\nimport { Visitor } from \"vite\";\n\nconst ISLAND_REGEX = /\\.island\\.(j|t)sx?$/;\n\ntype Lang = NonNullable<ParserOptions[\"lang\"]>;\n\nfunction langFromId(id: string): Lang {\n\tif (id.endsWith(\".tsx\")) {\n\t\treturn \"tsx\";\n\t}\n\tif (id.endsWith(\".ts\")) {\n\t\treturn \"ts\";\n\t}\n\tif (id.endsWith(\".jsx\")) {\n\t\treturn \"jsx\";\n\t}\n\treturn \"js\";\n}\n\nexport const islands = (): Plugin[] => {\n\treturn [\n\t\t{\n\t\t\tname: \"yamf:islands\",\n\t\t\ttransform: {\n\t\t\t\tfilter: { id: ISLAND_REGEX },\n\t\t\t\thandler: withMagicString(function (this, s, id) {\n\t\t\t\t\tif (this.environment.name !== \"ssr\") {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst program = this.parse(s.original, {\n\t\t\t\t\t\tlang: langFromId(id),\n\t\t\t\t\t});\n\n\t\t\t\t\t// prepend server island runtime + asset imports:\n\t\t\t\t\t// import * as __runtime from \"@pajecawav/yamf/server\";\n\t\t\t\t\t// import __assets from \"<id>?assets=client\";\n\t\t\t\t\ts.prepend(`import * as __runtime from \"@pajecawav/yamf/server\";\\n`);\n\t\t\t\t\ts.prepend(`import __assets from \"${id}?assets=client\";\\n`);\n\n\t\t\t\t\tconst seenExports = new Set<string>();\n\n\t\t\t\t\tnew Visitor({\n\t\t\t\t\t\tExportDefaultDeclaration(node) {\n\t\t\t\t\t\t\tconst declaration = node.declaration;\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tdeclaration.type !== \"FunctionDeclaration\" &&\n\t\t\t\t\t\t\t\tdeclaration.type !== \"FunctionExpression\" &&\n\t\t\t\t\t\t\t\tdeclaration.type !== \"ArrowFunctionExpression\"\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// export default <fn> ->\n\t\t\t\t\t\t\t// const __ISLAND__ = <fn>; export default __runtime.createIsland(__ISLAND__, \"default\", __assets)\n\t\t\t\t\t\t\ts.overwrite(node.start, declaration.start, \"const __ISLAND__ = \");\n\t\t\t\t\t\t\ts.appendRight(\n\t\t\t\t\t\t\t\tdeclaration.end,\n\t\t\t\t\t\t\t\t`; export default __runtime.createIsland(__ISLAND__, \"default\", __assets)`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExportNamedDeclaration(node) {\n\t\t\t\t\t\t\tconst declaration = node.declaration;\n\t\t\t\t\t\t\tif (declaration
|
|
1
|
+
{"version":3,"file":"islands.mjs","names":[],"sources":["../../src/vite/islands.ts"],"sourcesContent":["// reference https://github.com/hi-ogawa/vite-plugin-fullstack/blob/28e9540a68529c58842e9a3bf17d2193a065d524/examples/island/src/framework/island/plugin.ts\nimport { withMagicString } from \"rolldown-string\";\nimport type { ParserOptions, Plugin } from \"vite\";\nimport { Visitor } from \"vite\";\n\nconst ISLAND_REGEX = /\\.island\\.(j|t)sx?$/;\n\n// inits that can plausibly evaluate to a component; anything else (object,\n// array, literal, class…) is left as a plain export with a warning\nconst WRAPPABLE_INIT_TYPES = new Set([\n\t\"ArrowFunctionExpression\",\n\t\"FunctionExpression\",\n\t\"CallExpression\",\n\t\"OptionalCallExpression\",\n\t\"Identifier\",\n\t\"ConditionalExpression\",\n\t\"SequenceExpression\",\n\t\"TSAsExpression\",\n\t\"NonNullExpression\",\n]);\n\nconst isWrappableInit = (init: { type: string }): boolean => {\n\treturn WRAPPABLE_INIT_TYPES.has(init.type);\n};\n\ntype Lang = NonNullable<ParserOptions[\"lang\"]>;\n\nfunction langFromId(id: string): Lang {\n\tif (id.endsWith(\".tsx\")) {\n\t\treturn \"tsx\";\n\t}\n\tif (id.endsWith(\".ts\")) {\n\t\treturn \"ts\";\n\t}\n\tif (id.endsWith(\".jsx\")) {\n\t\treturn \"jsx\";\n\t}\n\treturn \"js\";\n}\n\nexport const islands = (): Plugin[] => {\n\treturn [\n\t\t{\n\t\t\tname: \"yamf:islands\",\n\t\t\ttransform: {\n\t\t\t\tfilter: { id: ISLAND_REGEX },\n\t\t\t\thandler: withMagicString(function (this, s, id) {\n\t\t\t\t\tif (this.environment.name !== \"ssr\") {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst program = this.parse(s.original, {\n\t\t\t\t\t\tlang: langFromId(id),\n\t\t\t\t\t});\n\n\t\t\t\t\t// prepend server island runtime + asset imports:\n\t\t\t\t\t// import * as __runtime from \"@pajecawav/yamf/server\";\n\t\t\t\t\t// import __assets from \"<id>?assets=client\";\n\t\t\t\t\ts.prepend(`import * as __runtime from \"@pajecawav/yamf/server\";\\n`);\n\t\t\t\t\ts.prepend(`import __assets from \"${id}?assets=client\";\\n`);\n\n\t\t\t\t\tconst seenExports = new Set<string>();\n\t\t\t\t\tconst warnings: string[] = [];\n\n\t\t\t\t\tnew Visitor({\n\t\t\t\t\t\tExportDefaultDeclaration(node) {\n\t\t\t\t\t\t\tconst declaration = node.declaration;\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\tdeclaration.type !== \"FunctionDeclaration\" &&\n\t\t\t\t\t\t\t\tdeclaration.type !== \"FunctionExpression\" &&\n\t\t\t\t\t\t\t\tdeclaration.type !== \"ArrowFunctionExpression\"\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\twarnings.push(\n\t\t\t\t\t\t\t\t\t`${id}: export default <${declaration.type === \"Identifier\" ? \"identifier\" : declaration.type}> is not wrapped as an island — only function components are supported`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// export default <fn> ->\n\t\t\t\t\t\t\t// const __ISLAND__ = <fn>; export default __runtime.createIsland(__ISLAND__, \"default\", __assets)\n\t\t\t\t\t\t\ts.overwrite(node.start, declaration.start, \"const __ISLAND__ = \");\n\t\t\t\t\t\t\ts.appendRight(\n\t\t\t\t\t\t\t\tdeclaration.end,\n\t\t\t\t\t\t\t\t`; export default __runtime.createIsland(__ISLAND__, \"default\", __assets)`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t\tExportNamedDeclaration(node) {\n\t\t\t\t\t\t\tconst declaration = node.declaration;\n\n\t\t\t\t\t\t\tif (!declaration) {\n\t\t\t\t\t\t\t\tif (node.exportKind !== \"type\") {\n\t\t\t\t\t\t\t\t\twarnings.push(\n\t\t\t\t\t\t\t\t\t\t`${id}: export { … } statements are not wrapped as islands — export the component with \\`export const\\` or \\`export function\\``,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (declaration.type === \"VariableDeclaration\") {\n\t\t\t\t\t\t\t\tconst declarators = declaration.declarations;\n\t\t\t\t\t\t\t\tconst declarator = declarators[0];\n\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t!declarator ||\n\t\t\t\t\t\t\t\t\tdeclarator.id.type !== \"Identifier\" ||\n\t\t\t\t\t\t\t\t\t!declarator.init ||\n\t\t\t\t\t\t\t\t\tseenExports.has(declarator.id.name)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tconst exportName = declarator.id.name;\n\n\t\t\t\t\t\t\t\tif (!isWrappableInit(declarator.init)) {\n\t\t\t\t\t\t\t\t\twarnings.push(\n\t\t\t\t\t\t\t\t\t\t`${id}: export \"${exportName}\" does not look like a component — left as a plain export, not an island`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (declarators.length > 1) {\n\t\t\t\t\t\t\t\t\twarnings.push(\n\t\t\t\t\t\t\t\t\t\t`${id}: only the first declarator of a multi-declarator export is wrapped as an island (\"${exportName}\") — split the declaration`,\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tseenExports.add(exportName);\n\n\t\t\t\t\t\t\t\t// export const <name> = <init> ->\n\t\t\t\t\t\t\t\t// const __wrap_<name> = <init>; export const <name> = __runtime.createIsland(__wrap_<name>, \"<name>\", __assets)\n\t\t\t\t\t\t\t\ts.overwrite(\n\t\t\t\t\t\t\t\t\tnode.start,\n\t\t\t\t\t\t\t\t\tdeclarator.init.start,\n\t\t\t\t\t\t\t\t\t`const __wrap_${exportName} = `,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\ts.appendRight(\n\t\t\t\t\t\t\t\t\tdeclarator.init.end,\n\t\t\t\t\t\t\t\t\t`; export const ${exportName} = __runtime.createIsland(__wrap_${exportName}, \"${exportName}\", __assets)`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} else if (declaration.type === \"FunctionDeclaration\") {\n\t\t\t\t\t\t\t\tconst fnId = declaration.id;\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\t!fnId ||\n\t\t\t\t\t\t\t\t\tfnId.type !== \"Identifier\" ||\n\t\t\t\t\t\t\t\t\tseenExports.has(fnId.name)\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst exportName = fnId.name;\n\t\t\t\t\t\t\t\tseenExports.add(exportName);\n\n\t\t\t\t\t\t\t\t// export function <name>() {} ->\n\t\t\t\t\t\t\t\t// function __wrap_<name>() {}; export const <name> = __runtime.createIsland(__wrap_<name>, \"<name>\", __assets)\n\t\t\t\t\t\t\t\ts.remove(node.start, declaration.start);\n\t\t\t\t\t\t\t\ts.overwrite(fnId.start, fnId.end, `__wrap_${exportName}`);\n\t\t\t\t\t\t\t\ts.appendRight(\n\t\t\t\t\t\t\t\t\tdeclaration.end,\n\t\t\t\t\t\t\t\t\t`; export const ${exportName} = __runtime.createIsland(__wrap_${exportName}, \"${exportName}\", __assets)`,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t}).visit(program);\n\n\t\t\t\t\tfor (const warning of warnings) {\n\t\t\t\t\t\tthis.warn(warning);\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"yamf:islands:raw-import\",\n\t\t\ttransform: {\n\t\t\t\torder: \"post\",\n\t\t\t\thandler: withMagicString(function (s) {\n\t\t\t\t\t// the token is namespaced to avoid colliding with user code\n\t\t\t\t\tif (s.original.includes(\"__yamf_raw_import__\")) {\n\t\t\t\t\t\ts.replaceAll(\"__yamf_raw_import__\", \"import\");\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t},\n\t\t},\n\t];\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/vite/prerender.ts
|
|
2
|
+
const prerenderModule = {
|
|
3
|
+
name: "yamf:prerender",
|
|
4
|
+
setup(nitro) {
|
|
5
|
+
nitro.hooks.hook("prerender:config", (config) => {
|
|
6
|
+
config.serverEntry = false;
|
|
7
|
+
});
|
|
8
|
+
nitro.hooks.hook("prerender:generate", (route) => {
|
|
9
|
+
if (route.route === "/404" && !route.error && !route.skip) route.fileName = "404.html";
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
//#endregion
|
|
14
|
+
export { prerenderModule };
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=prerender.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prerender.mjs","names":[],"sources":["../../src/vite/prerender.ts"],"sourcesContent":["import type { NitroModule } from \"nitro/types\";\n\n// Nitro's prerenderer is a standalone rolldown build (`nitro-prerender` preset)\n// that cannot run yamf's vite plugins, so `virtual:yamf:*` modules, the islands\n// transform, and the vite asset manifests do not exist there. Its auto-detected\n// serverEntry (src/server.tsx) would be bundled by rolldown and crash the\n// worker at init (`Received protocol 'virtual:'`). Disabling it lets prerender\n// requests fall through to the inherited renderer — nitro's vite ssr-renderer —\n// which dispatches them into the vite-built ssr service bundle: the exact code\n// path the production server uses.\nexport const prerenderModule: NitroModule = {\n\tname: \"yamf:prerender\",\n\tsetup(nitro) {\n\t\tnitro.hooks.hook(\"prerender:config\", config => {\n\t\t\tconfig.serverEntry = false;\n\t\t});\n\n\t\tnitro.hooks.hook(\"prerender:generate\", route => {\n\t\t\t// static hosts (github-pages & friends) expect the not-found document\n\t\t\t// at exactly /404.html; nitro would write /404 as 404/index.html\n\t\t\t// (autoSubfolderIndex)\n\t\t\tif (route.route === \"/404\" && !route.error && !route.skip) {\n\t\t\t\troute.fileName = \"404.html\";\n\t\t\t}\n\t\t});\n\t},\n};\n"],"mappings":";AAUA,MAAa,kBAA+B;CAC3C,MAAM;CACN,MAAM,OAAO;EACZ,MAAM,MAAM,KAAK,qBAAoB,WAAU;GAC9C,OAAO,cAAc;EACtB,CAAC;EAED,MAAM,MAAM,KAAK,uBAAsB,UAAS;GAI/C,IAAI,MAAM,UAAU,UAAU,CAAC,MAAM,SAAS,CAAC,MAAM,MACpD,MAAM,WAAW;EAEnB,CAAC;CACF;AACD"}
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import { js } from "./shared/utils.mjs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
2
3
|
import { resolveModulePath } from "exsolve";
|
|
3
4
|
//#region src/vite/virtual-root.ts
|
|
4
5
|
const ROOT_PATH = "./src/root";
|
|
5
6
|
const virtualRoot = () => {
|
|
6
7
|
const virtualModuleId = "virtual:yamf:root";
|
|
7
8
|
const resolvedVirtualModuleId = "\0" + virtualModuleId;
|
|
9
|
+
let root = process.cwd();
|
|
8
10
|
return {
|
|
9
11
|
name: "yamf:virtual-root",
|
|
12
|
+
configResolved(config) {
|
|
13
|
+
root = config.root;
|
|
14
|
+
},
|
|
10
15
|
resolveId(id) {
|
|
11
16
|
if (id === virtualModuleId) return resolvedVirtualModuleId;
|
|
12
17
|
},
|
|
13
18
|
load(id) {
|
|
14
19
|
if (id !== resolvedVirtualModuleId) return;
|
|
15
|
-
|
|
20
|
+
const rootPath = resolve(root, ROOT_PATH);
|
|
21
|
+
const rootModule = resolveModulePath(rootPath, {
|
|
16
22
|
try: true,
|
|
17
23
|
suffixes: ["", "/index"],
|
|
18
24
|
extensions: [
|
|
@@ -25,15 +31,16 @@ const virtualRoot = () => {
|
|
|
25
31
|
".mts",
|
|
26
32
|
".cts"
|
|
27
33
|
]
|
|
28
|
-
})
|
|
34
|
+
});
|
|
35
|
+
if (!rootModule) return js`
|
|
29
36
|
const Root = null;
|
|
30
37
|
const rootAssets = null;
|
|
31
38
|
|
|
32
39
|
export { Root, rootAssets };
|
|
33
40
|
`;
|
|
34
41
|
return js`
|
|
35
|
-
import Root from "${
|
|
36
|
-
import rootAssets from "${
|
|
42
|
+
import Root from "${rootModule}";
|
|
43
|
+
import rootAssets from "${rootModule}?assets=ssr";
|
|
37
44
|
|
|
38
45
|
export { Root, rootAssets };
|
|
39
46
|
`.trim();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtual-root.mjs","names":[],"sources":["../../src/vite/virtual-root.ts"],"sourcesContent":["import { resolveModulePath } from \"exsolve\";\nimport type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nconst ROOT_PATH = \"./src/root\";\n\nexport const virtualRoot = (): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:root\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\treturn {\n\t\tname: \"yamf:virtual-root\",\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tload(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst root = resolveModulePath(
|
|
1
|
+
{"version":3,"file":"virtual-root.mjs","names":[],"sources":["../../src/vite/virtual-root.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { resolveModulePath } from \"exsolve\";\nimport type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nconst ROOT_PATH = \"./src/root\";\n\nexport const virtualRoot = (): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:root\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\t// resolved against the vite root (not cwd) so running vite from another\n\t// working directory does not silently skip the root layout\n\tlet root = process.cwd();\n\n\treturn {\n\t\tname: \"yamf:virtual-root\",\n\t\tconfigResolved(config) {\n\t\t\troot = config.root;\n\t\t},\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tload(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst rootPath = resolve(root, ROOT_PATH);\n\n\t\t\tconst rootModule = resolveModulePath(rootPath, {\n\t\t\t\ttry: true,\n\t\t\t\tsuffixes: [\"\", \"/index\"],\n\t\t\t\textensions: [\".tsx\", \".jsx\", \".ts\", \".js\", \".mjs\", \".cjs\", \".mts\", \".cts\"],\n\t\t\t});\n\n\t\t\tif (!rootModule) {\n\t\t\t\treturn js`\nconst Root = null;\nconst rootAssets = null;\n\nexport { Root, rootAssets };\n `;\n\t\t\t}\n\n\t\t\treturn js`\nimport Root from \"${rootModule}\";\nimport rootAssets from \"${rootModule}?assets=ssr\";\n\nexport { Root, rootAssets };\n`.trim();\n\t\t},\n\t};\n};\n"],"mappings":";;;;AAKA,MAAM,YAAY;AAElB,MAAa,oBAA4B;CACxC,MAAM,kBAAkB;CACxB,MAAM,0BAA0B,OAAO;CAIvC,IAAI,OAAO,QAAQ,IAAI;CAEvB,OAAO;EACN,MAAM;EACN,eAAe,QAAQ;GACtB,OAAO,OAAO;EACf;EACA,UAAU,IAAI;GACb,IAAI,OAAO,iBACV,OAAO;EAIT;EACA,KAAK,IAAI;GACR,IAAI,OAAO,yBACV;GAGD,MAAM,WAAW,QAAQ,MAAM,SAAS;GAExC,MAAM,aAAa,kBAAkB,UAAU;IAC9C,KAAK;IACL,UAAU,CAAC,IAAI,QAAQ;IACvB,YAAY;KAAC;KAAQ;KAAQ;KAAO;KAAO;KAAQ;KAAQ;KAAQ;IAAM;GAC1E,CAAC;GAED,IAAI,CAAC,YACJ,OAAO,EAAE;;;;;;GAQV,OAAO,EAAE;oBACQ,WAAW;0BACL,WAAW;;;EAGnC,KAAK;EACL;CACD;AACD"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { js } from "./shared/utils.mjs";
|
|
2
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
2
3
|
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { relative } from "node:path";
|
|
4
4
|
//#region src/vite/virtual-template.ts
|
|
5
5
|
const TEMPLATE_PATH = "./src/template.html";
|
|
6
6
|
const DEFAULT_TEMPLATE = `
|
|
@@ -15,15 +15,19 @@ const DEFAULT_TEMPLATE = `
|
|
|
15
15
|
const virtualTemplate = () => {
|
|
16
16
|
const virtualModuleId = "virtual:yamf:template";
|
|
17
17
|
const resolvedVirtualModuleId = "\0" + virtualModuleId;
|
|
18
|
+
let root = process.cwd();
|
|
18
19
|
return {
|
|
19
20
|
name: "yamf:virtual-template",
|
|
21
|
+
configResolved(config) {
|
|
22
|
+
root = config.root;
|
|
23
|
+
},
|
|
20
24
|
resolveId(id) {
|
|
21
25
|
if (id === virtualModuleId) return resolvedVirtualModuleId;
|
|
22
26
|
},
|
|
23
27
|
async load(id) {
|
|
24
28
|
if (id !== resolvedVirtualModuleId) return;
|
|
25
29
|
try {
|
|
26
|
-
const template = await readFile(TEMPLATE_PATH, "utf8");
|
|
30
|
+
const template = await readFile(resolve(root, TEMPLATE_PATH), "utf8");
|
|
27
31
|
return js`export const template = ${JSON.stringify(template)};`;
|
|
28
32
|
} catch (error) {
|
|
29
33
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") return js`export const template = ${JSON.stringify(DEFAULT_TEMPLATE)};`;
|
|
@@ -31,7 +35,8 @@ const virtualTemplate = () => {
|
|
|
31
35
|
}
|
|
32
36
|
},
|
|
33
37
|
handleHotUpdate({ file, server }) {
|
|
34
|
-
if (
|
|
38
|
+
if (!isAbsolute(file)) return;
|
|
39
|
+
if (relative(resolve(root, TEMPLATE_PATH), file) === "") {
|
|
35
40
|
const mod = server.moduleGraph.getModuleById(resolvedVirtualModuleId);
|
|
36
41
|
if (mod) server.moduleGraph.invalidateModule(mod);
|
|
37
42
|
server.ws.send({ type: "full-reload" });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtual-template.mjs","names":[],"sources":["../../src/vite/virtual-template.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { relative } from \"node:path\";\nimport type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nconst TEMPLATE_PATH = \"./src/template.html\";\n\nconst DEFAULT_TEMPLATE = /* html */ `\n<!DOCTYPE html>\n<html>\n <head></head>\n <body>\n <!--ssr-outlet-->\n </body>\n</html>\n`.trim();\n\nexport const virtualTemplate = (): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:template\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\treturn {\n\t\tname: \"yamf:virtual-template\",\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tasync load(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst template = await readFile(TEMPLATE_PATH, \"utf8\");\n\n\t\t\t\treturn js`export const template = ${JSON.stringify(template)};`;\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n\t\t\t\t\treturn js`export const template = ${JSON.stringify(DEFAULT_TEMPLATE)};`;\n\t\t\t\t}\n\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t\thandleHotUpdate({ file, server }) {\n\t\t\tif (relative(
|
|
1
|
+
{"version":3,"file":"virtual-template.mjs","names":[],"sources":["../../src/vite/virtual-template.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { isAbsolute, relative, resolve } from \"node:path\";\nimport type { Plugin } from \"vite\";\nimport { js } from \"../shared/utils\";\n\nconst TEMPLATE_PATH = \"./src/template.html\";\n\nconst DEFAULT_TEMPLATE = /* html */ `\n<!DOCTYPE html>\n<html>\n <head></head>\n <body>\n <!--ssr-outlet-->\n </body>\n</html>\n`.trim();\n\nexport const virtualTemplate = (): Plugin => {\n\tconst virtualModuleId = \"virtual:yamf:template\";\n\tconst resolvedVirtualModuleId = \"\\0\" + virtualModuleId;\n\n\t// resolved against the vite root (not cwd) so running vite from another\n\t// working directory — monorepo tooling, turbo, etc — does not silently\n\t// fall back to the default template\n\tlet root = process.cwd();\n\n\treturn {\n\t\tname: \"yamf:virtual-template\",\n\t\tconfigResolved(config) {\n\t\t\troot = config.root;\n\t\t},\n\t\tresolveId(id) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\n\t\t\treturn undefined;\n\t\t},\n\t\tasync load(id) {\n\t\t\tif (id !== resolvedVirtualModuleId) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst template = await readFile(resolve(root, TEMPLATE_PATH), \"utf8\");\n\n\t\t\t\treturn js`export const template = ${JSON.stringify(template)};`;\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n\t\t\t\t\treturn js`export const template = ${JSON.stringify(DEFAULT_TEMPLATE)};`;\n\t\t\t\t}\n\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\t\thandleHotUpdate({ file, server }) {\n\t\t\tif (!isAbsolute(file)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (relative(resolve(root, TEMPLATE_PATH), file) === \"\") {\n\t\t\t\tconst mod = server.moduleGraph.getModuleById(resolvedVirtualModuleId);\n\n\t\t\t\tif (mod) {\n\t\t\t\t\tserver.moduleGraph.invalidateModule(mod);\n\t\t\t\t}\n\n\t\t\t\tserver.ws.send({ type: \"full-reload\" });\n\t\t\t}\n\t\t},\n\t};\n};\n"],"mappings":";;;;AAKA,MAAM,gBAAgB;AAEtB,MAAM,mBAA8B;;;;;;;;EAQlC,KAAK;AAEP,MAAa,wBAAgC;CAC5C,MAAM,kBAAkB;CACxB,MAAM,0BAA0B,OAAO;CAKvC,IAAI,OAAO,QAAQ,IAAI;CAEvB,OAAO;EACN,MAAM;EACN,eAAe,QAAQ;GACtB,OAAO,OAAO;EACf;EACA,UAAU,IAAI;GACb,IAAI,OAAO,iBACV,OAAO;EAIT;EACA,MAAM,KAAK,IAAI;GACd,IAAI,OAAO,yBACV;GAGD,IAAI;IACH,MAAM,WAAW,MAAM,SAAS,QAAQ,MAAM,aAAa,GAAG,MAAM;IAEpE,OAAO,EAAE,2BAA2B,KAAK,UAAU,QAAQ,EAAE;GAC9D,SAAS,OAAO;IACf,IAAI,iBAAiB,SAAS,UAAU,SAAS,MAAM,SAAS,UAC/D,OAAO,EAAE,2BAA2B,KAAK,UAAU,gBAAgB,EAAE;IAGtE,MAAM;GACP;EACD;EACA,gBAAgB,EAAE,MAAM,UAAU;GACjC,IAAI,CAAC,WAAW,IAAI,GACnB;GAGD,IAAI,SAAS,QAAQ,MAAM,aAAa,GAAG,IAAI,MAAM,IAAI;IACxD,MAAM,MAAM,OAAO,YAAY,cAAc,uBAAuB;IAEpE,IAAI,KACH,OAAO,YAAY,iBAAiB,GAAG;IAGxC,OAAO,GAAG,KAAK,EAAE,MAAM,cAAc,CAAC;GACvC;EACD;CACD;AACD"}
|