@funstack/static 0.0.10 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +4 -4
  2. package/dist/bin/skill-installer.mjs +5 -4
  3. package/dist/bin/skill-installer.mjs.map +1 -1
  4. package/dist/build/buildApp.mjs +1 -2
  5. package/dist/build/buildApp.mjs.map +1 -1
  6. package/dist/build/contentHash.mjs +1 -1
  7. package/dist/build/dependencyGraph.mjs +1 -1
  8. package/dist/build/dependencyGraph.mjs.map +1 -1
  9. package/dist/build/rscPath.mjs +1 -2
  10. package/dist/build/rscPath.mjs.map +1 -1
  11. package/dist/build/rscProcessor.mjs +1 -2
  12. package/dist/build/rscProcessor.mjs.map +1 -1
  13. package/dist/build/validateEntryPath.mjs +1 -1
  14. package/dist/client/entry.d.mts +1 -1
  15. package/dist/client/entry.mjs +2 -3
  16. package/dist/client/entry.mjs.map +1 -1
  17. package/dist/client/error-boundary.mjs +2 -4
  18. package/dist/client/error-boundary.mjs.map +1 -1
  19. package/dist/client/globals.mjs +2 -4
  20. package/dist/client/globals.mjs.map +1 -1
  21. package/dist/docs/GettingStarted.md +9 -5
  22. package/dist/docs/MigratingFromViteSPA.md +4 -4
  23. package/dist/docs/{learn → advanced}/MultipleEntrypoints.md +14 -42
  24. package/dist/docs/{learn → advanced}/SSR.md +3 -3
  25. package/dist/docs/api/EntryDefinition.md +2 -2
  26. package/dist/docs/api/FunstackStatic.md +6 -6
  27. package/dist/docs/index.md +5 -2
  28. package/dist/docs/learn/DeferAndActivity.md +3 -3
  29. package/dist/docs/learn/HowItWorks.md +2 -2
  30. package/dist/docs/learn/LazyServerComponents.md +3 -3
  31. package/dist/docs/learn/OptimizingPayloads.md +4 -4
  32. package/dist/docs/learn/RSC.md +3 -3
  33. package/dist/entries/client.d.mts +1 -1
  34. package/dist/entries/client.mjs +1 -2
  35. package/dist/entries/rsc-client.mjs +1 -3
  36. package/dist/entries/rsc.mjs +1 -2
  37. package/dist/entries/server.mjs +1 -2
  38. package/dist/entries/ssr.mjs +1 -2
  39. package/dist/entryDefinition.mjs +1 -1
  40. package/dist/index.mjs +1 -2
  41. package/dist/plugin/getRSCEntryPoint.mjs +1 -1
  42. package/dist/plugin/getRSCEntryPoint.mjs.map +1 -1
  43. package/dist/plugin/index.mjs +1 -2
  44. package/dist/plugin/index.mjs.map +1 -1
  45. package/dist/plugin/server.mjs +1 -2
  46. package/dist/plugin/server.mjs.map +1 -1
  47. package/dist/rsc/defer.mjs +1 -2
  48. package/dist/rsc/defer.mjs.map +1 -1
  49. package/dist/rsc/entry.mjs +4 -5
  50. package/dist/rsc/entry.mjs.map +1 -1
  51. package/dist/rsc/marker.mjs +1 -1
  52. package/dist/rsc/request.mjs +1 -1
  53. package/dist/rsc/resolveEntry.mjs +1 -2
  54. package/dist/rsc/resolveEntry.mjs.map +1 -1
  55. package/dist/rsc/rscModule.mjs +1 -1
  56. package/dist/rsc-client/clientWrapper.mjs +2 -3
  57. package/dist/rsc-client/clientWrapper.mjs.map +1 -1
  58. package/dist/rsc-client/entry.mjs +2 -4
  59. package/dist/ssr/entry.mjs +2 -2
  60. package/dist/ssr/entry.mjs.map +1 -1
  61. package/dist/util/basePath.mjs +1 -1
  62. package/dist/util/drainStream.mjs +1 -1
  63. package/dist/util/urlPath.mjs +1 -1
  64. package/package.json +5 -5
@@ -98,10 +98,10 @@ Each payload file has a **content-based hash** in its filename. This enables agg
98
98
 
99
99
  **Consider below-the-fold content** - Content hidden in collapsed sections, tabs, or modals is a good candidate for `defer()` since users may never need it.
100
100
 
101
- > **Tip:** You can combine `defer()` with React 19's `<Activity>` component to prefetch content in the background. When a `defer()`ed node is rendered under `<Activity mode="hidden">`, it won't be shown in the UI, but the fetch of the RSC payload will start immediately. This is useful when hidden content (like inactive tabs or collapsed sections) should be fetched ahead of time so it's ready when the user needs it. See [Prefetching with defer() and Activity](/funstack-static/learn/defer-and-activity) for a detailed guide.
101
+ > **Tip:** You can combine `defer()` with React 19's `<Activity>` component to prefetch content in the background. When a `defer()`ed node is rendered under `<Activity mode="hidden">`, it won't be shown in the UI, but the fetch of the RSC payload will start immediately. This is useful when hidden content (like inactive tabs or collapsed sections) should be fetched ahead of time so it's ready when the user needs it. See [Prefetching with defer() and Activity](/learn/defer-and-activity) for a detailed guide.
102
102
 
103
103
  ## See Also
104
104
 
105
- - [defer()](/funstack-static/api/defer) - API reference with full signature and technical details
106
- - [How It Works](/funstack-static/learn/how-it-works) - Overall FUNSTACK Static architecture
107
- - [React Server Components](/funstack-static/learn/rsc) - Understanding RSC fundamentals
105
+ - [defer()](/api/defer) - API reference with full signature and technical details
106
+ - [How It Works](/learn/how-it-works) - Overall FUNSTACK Static architecture
107
+ - [React Server Components](/learn/rsc) - Understanding RSC fundamentals
@@ -174,6 +174,6 @@ This makes FUNSTACK Static ideal for developers looking to leverage RSC benefits
174
174
 
175
175
  ## See Also
176
176
 
177
- - [Getting Started](/funstack-static/getting-started) - Set up your first project
178
- - [defer()](/funstack-static/api/defer) - Stream content progressively
179
- - [funstackStatic()](/funstack-static/api/funstack-static) - Plugin configuration
177
+ - [Getting Started](/getting-started) - Set up your first project
178
+ - [defer()](/api/defer) - Stream content progressively
179
+ - [funstackStatic()](/api/funstack-static) - Plugin configuration
@@ -1 +1 @@
1
- import "../client/entry.mjs";
1
+ export { };
@@ -1,3 +1,2 @@
1
1
  import "../client/entry.mjs";
2
-
3
- export { };
2
+ export {};
@@ -1,6 +1,4 @@
1
1
  "use client";
2
-
3
2
  import { DeferredComponent, RegistryContext } from "../rsc-client/clientWrapper.mjs";
4
3
  import "../rsc-client/entry.mjs";
5
-
6
- export { DeferredComponent, RegistryContext };
4
+ export { DeferredComponent, RegistryContext };
@@ -1,4 +1,3 @@
1
1
  import { defer } from "../rsc/defer.mjs";
2
2
  import { build, isServeRSCError, serveHTML, serveRSC } from "../rsc/entry.mjs";
3
-
4
- export { build, defer, isServeRSCError, serveHTML, serveRSC };
3
+ export { build, defer, isServeRSCError, serveHTML, serveRSC };
@@ -1,3 +1,2 @@
1
1
  import { defer } from "../rsc/defer.mjs";
2
-
3
- export { defer };
2
+ export { defer };
@@ -1,3 +1,2 @@
1
1
  import { renderHTML } from "../ssr/entry.mjs";
2
-
3
- export { renderHTML };
2
+ export { renderHTML };
@@ -1 +1 @@
1
- export { };
1
+ export {};
package/dist/index.mjs CHANGED
@@ -1,3 +1,2 @@
1
1
  import funstackStatic from "./plugin/index.mjs";
2
-
3
- export { funstackStatic as default };
2
+ export { funstackStatic as default };
@@ -10,7 +10,7 @@ async function getRSCEntryPoint(environment) {
10
10
  if (!resolved) throw new Error(`Cannot resolve RSC entry: ${source}`);
11
11
  return await environment.runner.import(resolved.id);
12
12
  }
13
-
14
13
  //#endregion
15
14
  export { getRSCEntryPoint };
15
+
16
16
  //# sourceMappingURL=getRSCEntryPoint.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"getRSCEntryPoint.mjs","names":[],"sources":["../../src/plugin/getRSCEntryPoint.ts"],"sourcesContent":["import type { RunnableDevEnvironment } from \"vite\";\n\n/**\n * Get the entry point module of the RSC environment.\n */\nexport async function getRSCEntryPoint(environment: RunnableDevEnvironment) {\n const rscInput = environment.config.build.rollupOptions?.input;\n const source =\n rscInput !== undefined &&\n typeof rscInput !== \"string\" &&\n !Array.isArray(rscInput)\n ? rscInput.index\n : undefined;\n if (source === undefined) {\n throw new Error(\"Cannot determine RSC entry point\");\n }\n const resolved = await environment.pluginContainer.resolveId(source);\n if (!resolved) {\n throw new Error(`Cannot resolve RSC entry: ${source}`);\n }\n const rscEntry = await environment.runner.import<\n typeof import(\"../rsc/entry\")\n >(resolved.id);\n return rscEntry;\n}\n"],"mappings":";;;;AAKA,eAAsB,iBAAiB,aAAqC;CAC1E,MAAM,WAAW,YAAY,OAAO,MAAM,eAAe;CACzD,MAAM,SACJ,aAAa,UACb,OAAO,aAAa,YACpB,CAAC,MAAM,QAAQ,SAAS,GACpB,SAAS,QACT;AACN,KAAI,WAAW,OACb,OAAM,IAAI,MAAM,mCAAmC;CAErD,MAAM,WAAW,MAAM,YAAY,gBAAgB,UAAU,OAAO;AACpE,KAAI,CAAC,SACH,OAAM,IAAI,MAAM,6BAA6B,SAAS;AAKxD,QAHiB,MAAM,YAAY,OAAO,OAExC,SAAS,GAAG"}
1
+ {"version":3,"file":"getRSCEntryPoint.mjs","names":[],"sources":["../../src/plugin/getRSCEntryPoint.ts"],"sourcesContent":["import type { RunnableDevEnvironment } from \"vite\";\n\n/**\n * Get the entry point module of the RSC environment.\n */\nexport async function getRSCEntryPoint(environment: RunnableDevEnvironment) {\n const rscInput = environment.config.build.rollupOptions?.input;\n const source =\n rscInput !== undefined &&\n typeof rscInput !== \"string\" &&\n !Array.isArray(rscInput)\n ? rscInput.index\n : undefined;\n if (source === undefined) {\n throw new Error(\"Cannot determine RSC entry point\");\n }\n const resolved = await environment.pluginContainer.resolveId(source);\n if (!resolved) {\n throw new Error(`Cannot resolve RSC entry: ${source}`);\n }\n const rscEntry = await environment.runner.import<\n typeof import(\"../rsc/entry\")\n >(resolved.id);\n return rscEntry;\n}\n"],"mappings":";;;;AAKA,eAAsB,iBAAiB,aAAqC;CAC1E,MAAM,WAAW,YAAY,OAAO,MAAM,eAAe;CACzD,MAAM,SACJ,aAAa,KAAA,KACb,OAAO,aAAa,YACpB,CAAC,MAAM,QAAQ,SAAS,GACpB,SAAS,QACT,KAAA;AACN,KAAI,WAAW,KAAA,EACb,OAAM,IAAI,MAAM,mCAAmC;CAErD,MAAM,WAAW,MAAM,YAAY,gBAAgB,UAAU,OAAO;AACpE,KAAI,CAAC,SACH,OAAM,IAAI,MAAM,6BAA6B,SAAS;AAKxD,QAHiB,MAAM,YAAY,OAAO,OAExC,SAAS,GAAG"}
@@ -2,7 +2,6 @@ import { buildApp } from "../build/buildApp.mjs";
2
2
  import { serverPlugin } from "./server.mjs";
3
3
  import path from "node:path";
4
4
  import rsc from "@vitejs/plugin-rsc";
5
-
6
5
  //#region src/plugin/index.ts
7
6
  function funstackStatic(options) {
8
7
  const { publicOutDir = "dist/public", ssr = false, clientInit } = options;
@@ -83,7 +82,7 @@ function funstackStatic(options) {
83
82
  }
84
83
  ];
85
84
  }
86
-
87
85
  //#endregion
88
86
  export { funstackStatic as default };
87
+
89
88
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/plugin/index.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { Plugin } from \"vite\";\nimport rsc from \"@vitejs/plugin-rsc\";\nimport { buildApp } from \"../build/buildApp\";\nimport { serverPlugin } from \"./server\";\n\ninterface FunstackStaticBaseOptions {\n /**\n * Output directory for build.\n *\n * @default dist/public\n */\n publicOutDir?: string;\n /**\n * Enable server-side rendering of the App component.\n * When false, only the Root shell is SSR'd and the App renders client-side.\n * When true, both Root and App are SSR'd and the client hydrates.\n *\n * @default false\n */\n ssr?: boolean;\n /**\n * Path to a module that runs on the client side before React hydration.\n * Use this for client-side instrumentation like Sentry, analytics, or feature flags.\n * The module is imported for its side effects only (no exports needed).\n */\n clientInit?: string;\n}\n\ninterface SingleEntryOptions {\n /**\n * Root component of the page.\n * The file should `export default` a React component that renders the whole page.\n * (`<html>...</html>`).\n */\n root: string;\n /**\n * Entry point of your application.\n * The file should `export default` a React component that renders the application content.\n */\n app: string;\n entries?: never;\n}\n\ninterface MultipleEntriesOptions {\n root?: never;\n app?: never;\n /**\n * Path to a module that exports a function returning entry definitions.\n * Mutually exclusive with `root`+`app`.\n */\n entries: string;\n}\n\nexport type FunstackStaticOptions = FunstackStaticBaseOptions &\n (SingleEntryOptions | MultipleEntriesOptions);\n\nexport default function funstackStatic(\n options: FunstackStaticOptions,\n): (Plugin | Plugin[])[] {\n const { publicOutDir = \"dist/public\", ssr = false, clientInit } = options;\n\n let resolvedEntriesModule: string = \"__uninitialized__\";\n let resolvedClientInitEntry: string | undefined;\n\n // Determine whether user specified entries or root+app\n const isMultiEntry = \"entries\" in options && options.entries !== undefined;\n\n return [\n {\n name: \"@funstack/static:config-pre\",\n // Placed early because the rsc plugin sets the outDir to the default value\n config(config) {\n return {\n environments: {\n client: {\n build: {\n outDir:\n config.environments?.client?.build?.outDir ?? publicOutDir,\n },\n },\n },\n };\n },\n },\n serverPlugin(),\n rsc({\n entries: {\n rsc: \"@funstack/static/entries/rsc\",\n ssr: \"@funstack/static/entries/ssr\",\n client: \"@funstack/static/entries/client\",\n },\n serverHandler: false,\n }),\n {\n name: \"@funstack/static:config\",\n configResolved(config) {\n if (isMultiEntry) {\n resolvedEntriesModule = path.resolve(config.root, options.entries);\n } else {\n // For single-entry, we store both resolved paths to generate a\n // synthetic entries module in the virtual module loader.\n const resolvedRoot = path.resolve(config.root, options.root);\n const resolvedApp = path.resolve(config.root, options.app);\n // Encode as JSON for safe embedding in generated code\n resolvedEntriesModule = JSON.stringify({\n root: resolvedRoot,\n app: resolvedApp,\n });\n }\n if (clientInit) {\n resolvedClientInitEntry = path.resolve(config.root, clientInit);\n }\n },\n configEnvironment(_name, config) {\n if (!config.optimizeDeps) {\n config.optimizeDeps = {};\n }\n // Needed for properly bundling @vitejs/plugin-rsc for browser.\n // See: https://github.com/vitejs/vite-plugin-react/tree/79bf57cc8b9c77e33970ec2e876bd6d2f1568d5d/packages/plugin-rsc#using-vitejsplugin-rsc-as-a-framework-packages-dependencies\n if (config.optimizeDeps.include) {\n config.optimizeDeps.include = config.optimizeDeps.include.map(\n (entry) => {\n if (entry.startsWith(\"@vitejs/plugin-rsc\")) {\n entry = `@funstack/static > ${entry}`;\n }\n return entry;\n },\n );\n }\n if (!config.optimizeDeps.exclude) {\n config.optimizeDeps.exclude = [];\n }\n // Since code includes imports to virtual modules, we need to exclude\n // us from Optimize Deps.\n config.optimizeDeps.exclude.push(\"@funstack/static\");\n },\n },\n {\n name: \"@funstack/static:virtual-entry\",\n resolveId(id) {\n if (id === \"virtual:funstack/entries\") {\n return \"\\0virtual:funstack/entries\";\n }\n if (id === \"virtual:funstack/config\") {\n return \"\\0virtual:funstack/config\";\n }\n if (id === \"virtual:funstack/client-init\") {\n return \"\\0virtual:funstack/client-init\";\n }\n },\n load(id) {\n if (id === \"\\0virtual:funstack/entries\") {\n if (isMultiEntry) {\n // Re-export the user's entries module\n return `export { default } from \"${resolvedEntriesModule}\";`;\n }\n // Synthesize a single-entry array from root+app\n const { root, app } = JSON.parse(resolvedEntriesModule);\n return [\n `import Root from \"${root}\";`,\n `import App from \"${app}\";`,\n `export default function getEntries() {`,\n ` return [{ path: \"index.html\", root: { default: Root }, app: { default: App } }];`,\n `}`,\n ].join(\"\\n\");\n }\n if (id === \"\\0virtual:funstack/config\") {\n return `export const ssr = ${JSON.stringify(ssr)};`;\n }\n if (id === \"\\0virtual:funstack/client-init\") {\n if (resolvedClientInitEntry) {\n return `import \"${resolvedClientInitEntry}\";`;\n }\n return \"\";\n }\n },\n },\n {\n name: \"@funstack/static:build\",\n async buildApp(builder) {\n await buildApp(builder, this);\n },\n },\n ];\n}\n"],"mappings":";;;;;;AAyDA,SAAwB,eACtB,SACuB;CACvB,MAAM,EAAE,eAAe,eAAe,MAAM,OAAO,eAAe;CAElE,IAAI,wBAAgC;CACpC,IAAI;CAGJ,MAAM,eAAe,aAAa,WAAW,QAAQ,YAAY;AAEjE,QAAO;EACL;GACE,MAAM;GAEN,OAAO,QAAQ;AACb,WAAO,EACL,cAAc,EACZ,QAAQ,EACN,OAAO,EACL,QACE,OAAO,cAAc,QAAQ,OAAO,UAAU,cACjD,EACF,EACF,EACF;;GAEJ;EACD,cAAc;EACd,IAAI;GACF,SAAS;IACP,KAAK;IACL,KAAK;IACL,QAAQ;IACT;GACD,eAAe;GAChB,CAAC;EACF;GACE,MAAM;GACN,eAAe,QAAQ;AACrB,QAAI,aACF,yBAAwB,KAAK,QAAQ,OAAO,MAAM,QAAQ,QAAQ;SAC7D;KAGL,MAAM,eAAe,KAAK,QAAQ,OAAO,MAAM,QAAQ,KAAK;KAC5D,MAAM,cAAc,KAAK,QAAQ,OAAO,MAAM,QAAQ,IAAI;AAE1D,6BAAwB,KAAK,UAAU;MACrC,MAAM;MACN,KAAK;MACN,CAAC;;AAEJ,QAAI,WACF,2BAA0B,KAAK,QAAQ,OAAO,MAAM,WAAW;;GAGnE,kBAAkB,OAAO,QAAQ;AAC/B,QAAI,CAAC,OAAO,aACV,QAAO,eAAe,EAAE;AAI1B,QAAI,OAAO,aAAa,QACtB,QAAO,aAAa,UAAU,OAAO,aAAa,QAAQ,KACvD,UAAU;AACT,SAAI,MAAM,WAAW,qBAAqB,CACxC,SAAQ,sBAAsB;AAEhC,YAAO;MAEV;AAEH,QAAI,CAAC,OAAO,aAAa,QACvB,QAAO,aAAa,UAAU,EAAE;AAIlC,WAAO,aAAa,QAAQ,KAAK,mBAAmB;;GAEvD;EACD;GACE,MAAM;GACN,UAAU,IAAI;AACZ,QAAI,OAAO,2BACT,QAAO;AAET,QAAI,OAAO,0BACT,QAAO;AAET,QAAI,OAAO,+BACT,QAAO;;GAGX,KAAK,IAAI;AACP,QAAI,OAAO,8BAA8B;AACvC,SAAI,aAEF,QAAO,4BAA4B,sBAAsB;KAG3D,MAAM,EAAE,MAAM,QAAQ,KAAK,MAAM,sBAAsB;AACvD,YAAO;MACL,qBAAqB,KAAK;MAC1B,oBAAoB,IAAI;MACxB;MACA;MACA;MACD,CAAC,KAAK,KAAK;;AAEd,QAAI,OAAO,4BACT,QAAO,sBAAsB,KAAK,UAAU,IAAI,CAAC;AAEnD,QAAI,OAAO,kCAAkC;AAC3C,SAAI,wBACF,QAAO,WAAW,wBAAwB;AAE5C,YAAO;;;GAGZ;EACD;GACE,MAAM;GACN,MAAM,SAAS,SAAS;AACtB,UAAM,SAAS,SAAS,KAAK;;GAEhC;EACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/plugin/index.ts"],"sourcesContent":["import path from \"node:path\";\nimport type { Plugin } from \"vite\";\nimport rsc from \"@vitejs/plugin-rsc\";\nimport { buildApp } from \"../build/buildApp\";\nimport { serverPlugin } from \"./server\";\n\ninterface FunstackStaticBaseOptions {\n /**\n * Output directory for build.\n *\n * @default dist/public\n */\n publicOutDir?: string;\n /**\n * Enable server-side rendering of the App component.\n * When false, only the Root shell is SSR'd and the App renders client-side.\n * When true, both Root and App are SSR'd and the client hydrates.\n *\n * @default false\n */\n ssr?: boolean;\n /**\n * Path to a module that runs on the client side before React hydration.\n * Use this for client-side instrumentation like Sentry, analytics, or feature flags.\n * The module is imported for its side effects only (no exports needed).\n */\n clientInit?: string;\n}\n\ninterface SingleEntryOptions {\n /**\n * Root component of the page.\n * The file should `export default` a React component that renders the whole page.\n * (`<html>...</html>`).\n */\n root: string;\n /**\n * Entry point of your application.\n * The file should `export default` a React component that renders the application content.\n */\n app: string;\n entries?: never;\n}\n\ninterface MultipleEntriesOptions {\n root?: never;\n app?: never;\n /**\n * Path to a module that exports a function returning entry definitions.\n * Mutually exclusive with `root`+`app`.\n */\n entries: string;\n}\n\nexport type FunstackStaticOptions = FunstackStaticBaseOptions &\n (SingleEntryOptions | MultipleEntriesOptions);\n\nexport default function funstackStatic(\n options: FunstackStaticOptions,\n): (Plugin | Plugin[])[] {\n const { publicOutDir = \"dist/public\", ssr = false, clientInit } = options;\n\n let resolvedEntriesModule: string = \"__uninitialized__\";\n let resolvedClientInitEntry: string | undefined;\n\n // Determine whether user specified entries or root+app\n const isMultiEntry = \"entries\" in options && options.entries !== undefined;\n\n return [\n {\n name: \"@funstack/static:config-pre\",\n // Placed early because the rsc plugin sets the outDir to the default value\n config(config) {\n return {\n environments: {\n client: {\n build: {\n outDir:\n config.environments?.client?.build?.outDir ?? publicOutDir,\n },\n },\n },\n };\n },\n },\n serverPlugin(),\n rsc({\n entries: {\n rsc: \"@funstack/static/entries/rsc\",\n ssr: \"@funstack/static/entries/ssr\",\n client: \"@funstack/static/entries/client\",\n },\n serverHandler: false,\n }),\n {\n name: \"@funstack/static:config\",\n configResolved(config) {\n if (isMultiEntry) {\n resolvedEntriesModule = path.resolve(config.root, options.entries);\n } else {\n // For single-entry, we store both resolved paths to generate a\n // synthetic entries module in the virtual module loader.\n const resolvedRoot = path.resolve(config.root, options.root);\n const resolvedApp = path.resolve(config.root, options.app);\n // Encode as JSON for safe embedding in generated code\n resolvedEntriesModule = JSON.stringify({\n root: resolvedRoot,\n app: resolvedApp,\n });\n }\n if (clientInit) {\n resolvedClientInitEntry = path.resolve(config.root, clientInit);\n }\n },\n configEnvironment(_name, config) {\n if (!config.optimizeDeps) {\n config.optimizeDeps = {};\n }\n // Needed for properly bundling @vitejs/plugin-rsc for browser.\n // See: https://github.com/vitejs/vite-plugin-react/tree/79bf57cc8b9c77e33970ec2e876bd6d2f1568d5d/packages/plugin-rsc#using-vitejsplugin-rsc-as-a-framework-packages-dependencies\n if (config.optimizeDeps.include) {\n config.optimizeDeps.include = config.optimizeDeps.include.map(\n (entry) => {\n if (entry.startsWith(\"@vitejs/plugin-rsc\")) {\n entry = `@funstack/static > ${entry}`;\n }\n return entry;\n },\n );\n }\n if (!config.optimizeDeps.exclude) {\n config.optimizeDeps.exclude = [];\n }\n // Since code includes imports to virtual modules, we need to exclude\n // us from Optimize Deps.\n config.optimizeDeps.exclude.push(\"@funstack/static\");\n },\n },\n {\n name: \"@funstack/static:virtual-entry\",\n resolveId(id) {\n if (id === \"virtual:funstack/entries\") {\n return \"\\0virtual:funstack/entries\";\n }\n if (id === \"virtual:funstack/config\") {\n return \"\\0virtual:funstack/config\";\n }\n if (id === \"virtual:funstack/client-init\") {\n return \"\\0virtual:funstack/client-init\";\n }\n },\n load(id) {\n if (id === \"\\0virtual:funstack/entries\") {\n if (isMultiEntry) {\n // Re-export the user's entries module\n return `export { default } from \"${resolvedEntriesModule}\";`;\n }\n // Synthesize a single-entry array from root+app\n const { root, app } = JSON.parse(resolvedEntriesModule);\n return [\n `import Root from \"${root}\";`,\n `import App from \"${app}\";`,\n `export default function getEntries() {`,\n ` return [{ path: \"index.html\", root: { default: Root }, app: { default: App } }];`,\n `}`,\n ].join(\"\\n\");\n }\n if (id === \"\\0virtual:funstack/config\") {\n return `export const ssr = ${JSON.stringify(ssr)};`;\n }\n if (id === \"\\0virtual:funstack/client-init\") {\n if (resolvedClientInitEntry) {\n return `import \"${resolvedClientInitEntry}\";`;\n }\n return \"\";\n }\n },\n },\n {\n name: \"@funstack/static:build\",\n async buildApp(builder) {\n await buildApp(builder, this);\n },\n },\n ];\n}\n"],"mappings":";;;;;AAyDA,SAAwB,eACtB,SACuB;CACvB,MAAM,EAAE,eAAe,eAAe,MAAM,OAAO,eAAe;CAElE,IAAI,wBAAgC;CACpC,IAAI;CAGJ,MAAM,eAAe,aAAa,WAAW,QAAQ,YAAY,KAAA;AAEjE,QAAO;EACL;GACE,MAAM;GAEN,OAAO,QAAQ;AACb,WAAO,EACL,cAAc,EACZ,QAAQ,EACN,OAAO,EACL,QACE,OAAO,cAAc,QAAQ,OAAO,UAAU,cACjD,EACF,EACF,EACF;;GAEJ;EACD,cAAc;EACd,IAAI;GACF,SAAS;IACP,KAAK;IACL,KAAK;IACL,QAAQ;IACT;GACD,eAAe;GAChB,CAAC;EACF;GACE,MAAM;GACN,eAAe,QAAQ;AACrB,QAAI,aACF,yBAAwB,KAAK,QAAQ,OAAO,MAAM,QAAQ,QAAQ;SAC7D;KAGL,MAAM,eAAe,KAAK,QAAQ,OAAO,MAAM,QAAQ,KAAK;KAC5D,MAAM,cAAc,KAAK,QAAQ,OAAO,MAAM,QAAQ,IAAI;AAE1D,6BAAwB,KAAK,UAAU;MACrC,MAAM;MACN,KAAK;MACN,CAAC;;AAEJ,QAAI,WACF,2BAA0B,KAAK,QAAQ,OAAO,MAAM,WAAW;;GAGnE,kBAAkB,OAAO,QAAQ;AAC/B,QAAI,CAAC,OAAO,aACV,QAAO,eAAe,EAAE;AAI1B,QAAI,OAAO,aAAa,QACtB,QAAO,aAAa,UAAU,OAAO,aAAa,QAAQ,KACvD,UAAU;AACT,SAAI,MAAM,WAAW,qBAAqB,CACxC,SAAQ,sBAAsB;AAEhC,YAAO;MAEV;AAEH,QAAI,CAAC,OAAO,aAAa,QACvB,QAAO,aAAa,UAAU,EAAE;AAIlC,WAAO,aAAa,QAAQ,KAAK,mBAAmB;;GAEvD;EACD;GACE,MAAM;GACN,UAAU,IAAI;AACZ,QAAI,OAAO,2BACT,QAAO;AAET,QAAI,OAAO,0BACT,QAAO;AAET,QAAI,OAAO,+BACT,QAAO;;GAGX,KAAK,IAAI;AACP,QAAI,OAAO,8BAA8B;AACvC,SAAI,aAEF,QAAO,4BAA4B,sBAAsB;KAG3D,MAAM,EAAE,MAAM,QAAQ,KAAK,MAAM,sBAAsB;AACvD,YAAO;MACL,qBAAqB,KAAK;MAC1B,oBAAoB,IAAI;MACxB;MACA;MACA;MACD,CAAC,KAAK,KAAK;;AAEd,QAAI,OAAO,4BACT,QAAO,sBAAsB,KAAK,UAAU,IAAI,CAAC;AAEnD,QAAI,OAAO,kCAAkC;AAC3C,SAAI,wBACF,QAAO,WAAW,wBAAwB;AAE5C,YAAO;;;GAGZ;EACD;GACE,MAAM;GACN,MAAM,SAAS,SAAS;AACtB,UAAM,SAAS,SAAS,KAAK;;GAEhC;EACF"}
@@ -4,7 +4,6 @@ import path from "node:path";
4
4
  import { readFile } from "node:fs/promises";
5
5
  import { isRunnableDevEnvironment } from "vite";
6
6
  import { toNodeHandler } from "srvx/node";
7
-
8
7
  //#region src/plugin/server.ts
9
8
  const serverPlugin = () => {
10
9
  let resolvedOutDir = "__uninitialized__";
@@ -70,7 +69,7 @@ const serverPlugin = () => {
70
69
  }
71
70
  };
72
71
  };
73
-
74
72
  //#endregion
75
73
  export { serverPlugin };
74
+
76
75
  //# sourceMappingURL=server.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.mjs","names":[],"sources":["../../src/plugin/server.ts"],"sourcesContent":["import { isRunnableDevEnvironment, type Plugin } from \"vite\";\nimport { readFile } from \"node:fs/promises\";\nimport { toNodeHandler } from \"srvx/node\";\nimport path from \"node:path\";\nimport { getRSCEntryPoint } from \"./getRSCEntryPoint\";\nimport { urlPathToFileCandidates } from \"../util/urlPath\";\n\nexport const serverPlugin = (): Plugin => {\n let resolvedOutDir = \"__uninitialized__\";\n return {\n name: \"@funstack/static:server\",\n configResolved(config) {\n resolvedOutDir = path.resolve(\n config.root,\n config.environments.client.build.outDir,\n );\n },\n configureServer(server) {\n const rscEnv = server.environments.rsc;\n if (!isRunnableDevEnvironment(rscEnv)) {\n throw new Error(\"The rsc environment is not runnable\");\n }\n\n return () => {\n server.middlewares.use(async (req, res, next) => {\n try {\n const rscEntry = await getRSCEntryPoint(rscEnv);\n if (req.headers.accept?.includes(\"text/html\")) {\n // serveHTML now accepts a Request and routes by URL path\n const fetchHandler = toNodeHandler(rscEntry.serveHTML);\n await fetchHandler(req as any, res as any);\n return;\n }\n const fetchHandler = toNodeHandler(rscEntry.serveRSC);\n try {\n await fetchHandler(req as any, res as any);\n } catch (error) {\n if (rscEntry.isServeRSCError(error) && error.status === 404) {\n next();\n return;\n }\n next(error);\n }\n } catch (error) {\n next(error);\n }\n });\n };\n },\n configurePreviewServer(server) {\n return () => {\n server.middlewares.use(async (req, res, next) => {\n try {\n if (req.headers.accept?.includes(\"text/html\")) {\n const urlPath = new URL(req.url!, `http://${req.headers.host}`)\n .pathname;\n const candidates = urlPathToFileCandidates(urlPath);\n for (const candidate of candidates) {\n try {\n const html = await readFile(\n path.join(resolvedOutDir, candidate),\n \"utf-8\",\n );\n res.end(html);\n return;\n } catch {\n // Try next candidate\n }\n }\n // SPA fallback: try serving index.html or index.htm for unmatched routes\n for (const indexFile of [\"index.html\", \"index.htm\"]) {\n try {\n const html = await readFile(\n path.join(resolvedOutDir, indexFile),\n \"utf-8\",\n );\n res.end(html);\n return;\n } catch {\n // Try next candidate\n }\n }\n next();\n return;\n }\n } catch (error) {\n next(error);\n return;\n }\n next();\n });\n };\n },\n };\n};\n"],"mappings":";;;;;;;;AAOA,MAAa,qBAA6B;CACxC,IAAI,iBAAiB;AACrB,QAAO;EACL,MAAM;EACN,eAAe,QAAQ;AACrB,oBAAiB,KAAK,QACpB,OAAO,MACP,OAAO,aAAa,OAAO,MAAM,OAClC;;EAEH,gBAAgB,QAAQ;GACtB,MAAM,SAAS,OAAO,aAAa;AACnC,OAAI,CAAC,yBAAyB,OAAO,CACnC,OAAM,IAAI,MAAM,sCAAsC;AAGxD,gBAAa;AACX,WAAO,YAAY,IAAI,OAAO,KAAK,KAAK,SAAS;AAC/C,SAAI;MACF,MAAM,WAAW,MAAM,iBAAiB,OAAO;AAC/C,UAAI,IAAI,QAAQ,QAAQ,SAAS,YAAY,EAAE;AAG7C,aADqB,cAAc,SAAS,UAAU,CACnC,KAAY,IAAW;AAC1C;;MAEF,MAAM,eAAe,cAAc,SAAS,SAAS;AACrD,UAAI;AACF,aAAM,aAAa,KAAY,IAAW;eACnC,OAAO;AACd,WAAI,SAAS,gBAAgB,MAAM,IAAI,MAAM,WAAW,KAAK;AAC3D,cAAM;AACN;;AAEF,YAAK,MAAM;;cAEN,OAAO;AACd,WAAK,MAAM;;MAEb;;;EAGN,uBAAuB,QAAQ;AAC7B,gBAAa;AACX,WAAO,YAAY,IAAI,OAAO,KAAK,KAAK,SAAS;AAC/C,SAAI;AACF,UAAI,IAAI,QAAQ,QAAQ,SAAS,YAAY,EAAE;OAC7C,MAAM,UAAU,IAAI,IAAI,IAAI,KAAM,UAAU,IAAI,QAAQ,OAAO,CAC5D;OACH,MAAM,aAAa,wBAAwB,QAAQ;AACnD,YAAK,MAAM,aAAa,WACtB,KAAI;QACF,MAAM,OAAO,MAAM,SACjB,KAAK,KAAK,gBAAgB,UAAU,EACpC,QACD;AACD,YAAI,IAAI,KAAK;AACb;eACM;AAKV,YAAK,MAAM,aAAa,CAAC,cAAc,YAAY,CACjD,KAAI;QACF,MAAM,OAAO,MAAM,SACjB,KAAK,KAAK,gBAAgB,UAAU,EACpC,QACD;AACD,YAAI,IAAI,KAAK;AACb;eACM;AAIV,aAAM;AACN;;cAEK,OAAO;AACd,WAAK,MAAM;AACX;;AAEF,WAAM;MACN;;;EAGP"}
1
+ {"version":3,"file":"server.mjs","names":[],"sources":["../../src/plugin/server.ts"],"sourcesContent":["import { isRunnableDevEnvironment, type Plugin } from \"vite\";\nimport { readFile } from \"node:fs/promises\";\nimport { toNodeHandler } from \"srvx/node\";\nimport path from \"node:path\";\nimport { getRSCEntryPoint } from \"./getRSCEntryPoint\";\nimport { urlPathToFileCandidates } from \"../util/urlPath\";\n\nexport const serverPlugin = (): Plugin => {\n let resolvedOutDir = \"__uninitialized__\";\n return {\n name: \"@funstack/static:server\",\n configResolved(config) {\n resolvedOutDir = path.resolve(\n config.root,\n config.environments.client.build.outDir,\n );\n },\n configureServer(server) {\n const rscEnv = server.environments.rsc;\n if (!isRunnableDevEnvironment(rscEnv)) {\n throw new Error(\"The rsc environment is not runnable\");\n }\n\n return () => {\n server.middlewares.use(async (req, res, next) => {\n try {\n const rscEntry = await getRSCEntryPoint(rscEnv);\n if (req.headers.accept?.includes(\"text/html\")) {\n // serveHTML now accepts a Request and routes by URL path\n const fetchHandler = toNodeHandler(rscEntry.serveHTML);\n await fetchHandler(req as any, res as any);\n return;\n }\n const fetchHandler = toNodeHandler(rscEntry.serveRSC);\n try {\n await fetchHandler(req as any, res as any);\n } catch (error) {\n if (rscEntry.isServeRSCError(error) && error.status === 404) {\n next();\n return;\n }\n next(error);\n }\n } catch (error) {\n next(error);\n }\n });\n };\n },\n configurePreviewServer(server) {\n return () => {\n server.middlewares.use(async (req, res, next) => {\n try {\n if (req.headers.accept?.includes(\"text/html\")) {\n const urlPath = new URL(req.url!, `http://${req.headers.host}`)\n .pathname;\n const candidates = urlPathToFileCandidates(urlPath);\n for (const candidate of candidates) {\n try {\n const html = await readFile(\n path.join(resolvedOutDir, candidate),\n \"utf-8\",\n );\n res.end(html);\n return;\n } catch {\n // Try next candidate\n }\n }\n // SPA fallback: try serving index.html or index.htm for unmatched routes\n for (const indexFile of [\"index.html\", \"index.htm\"]) {\n try {\n const html = await readFile(\n path.join(resolvedOutDir, indexFile),\n \"utf-8\",\n );\n res.end(html);\n return;\n } catch {\n // Try next candidate\n }\n }\n next();\n return;\n }\n } catch (error) {\n next(error);\n return;\n }\n next();\n });\n };\n },\n };\n};\n"],"mappings":";;;;;;;AAOA,MAAa,qBAA6B;CACxC,IAAI,iBAAiB;AACrB,QAAO;EACL,MAAM;EACN,eAAe,QAAQ;AACrB,oBAAiB,KAAK,QACpB,OAAO,MACP,OAAO,aAAa,OAAO,MAAM,OAClC;;EAEH,gBAAgB,QAAQ;GACtB,MAAM,SAAS,OAAO,aAAa;AACnC,OAAI,CAAC,yBAAyB,OAAO,CACnC,OAAM,IAAI,MAAM,sCAAsC;AAGxD,gBAAa;AACX,WAAO,YAAY,IAAI,OAAO,KAAK,KAAK,SAAS;AAC/C,SAAI;MACF,MAAM,WAAW,MAAM,iBAAiB,OAAO;AAC/C,UAAI,IAAI,QAAQ,QAAQ,SAAS,YAAY,EAAE;AAG7C,aADqB,cAAc,SAAS,UAAU,CACnC,KAAY,IAAW;AAC1C;;MAEF,MAAM,eAAe,cAAc,SAAS,SAAS;AACrD,UAAI;AACF,aAAM,aAAa,KAAY,IAAW;eACnC,OAAO;AACd,WAAI,SAAS,gBAAgB,MAAM,IAAI,MAAM,WAAW,KAAK;AAC3D,cAAM;AACN;;AAEF,YAAK,MAAM;;cAEN,OAAO;AACd,WAAK,MAAM;;MAEb;;;EAGN,uBAAuB,QAAQ;AAC7B,gBAAa;AACX,WAAO,YAAY,IAAI,OAAO,KAAK,KAAK,SAAS;AAC/C,SAAI;AACF,UAAI,IAAI,QAAQ,QAAQ,SAAS,YAAY,EAAE;OAC7C,MAAM,UAAU,IAAI,IAAI,IAAI,KAAM,UAAU,IAAI,QAAQ,OAAO,CAC5D;OACH,MAAM,aAAa,wBAAwB,QAAQ;AACnD,YAAK,MAAM,aAAa,WACtB,KAAI;QACF,MAAM,OAAO,MAAM,SACjB,KAAK,KAAK,gBAAgB,UAAU,EACpC,QACD;AACD,YAAI,IAAI,KAAK;AACb;eACM;AAKV,YAAK,MAAM,aAAa,CAAC,cAAc,YAAY,CACjD,KAAI;QACF,MAAM,OAAO,MAAM,SACjB,KAAK,KAAK,gBAAgB,UAAU,EACpC,QACD;AACD,YAAI,IAAI,KAAK;AACb;eACM;AAIV,aAAM;AACN;;cAEK,OAAO;AACd,WAAK,MAAM;AACX;;AAEF,WAAM;MACN;;;EAGP"}
@@ -3,7 +3,6 @@ import { drainStream } from "../util/drainStream.mjs";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  import { renderToReadableStream } from "@vitejs/plugin-rsc/react/rsc";
5
5
  import { DeferredComponent } from "#rsc-client";
6
-
7
6
  //#region src/rsc/defer.tsx
8
7
  /**
9
8
  * Sanitizes a name for use in file paths.
@@ -115,7 +114,7 @@ function defer(element, options) {
115
114
  deferRegistry.register(element, id, name);
116
115
  return /* @__PURE__ */ jsx(DeferredComponent, { moduleID: id });
117
116
  }
118
-
119
117
  //#endregion
120
118
  export { defer, deferRegistry };
119
+
121
120
  //# sourceMappingURL=defer.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"defer.mjs","names":["#registry","#loadEntry"],"sources":["../../src/rsc/defer.tsx"],"sourcesContent":["import type { ReactElement, ReactNode } from \"react\";\nimport { renderToReadableStream } from \"@vitejs/plugin-rsc/react/rsc\";\nimport { DeferredComponent } from \"#rsc-client\";\nimport { drainStream } from \"../util/drainStream\";\nimport { getPayloadIDFor } from \"./rscModule\";\n\nexport interface DeferEntry {\n state: DeferEntryState;\n name?: string;\n drainPromise?: Promise<string>;\n}\n\n/**\n * Options for the defer function.\n */\nexport interface DeferOptions {\n /**\n * Optional name for debugging purposes.\n * In development: included in the RSC payload file name.\n * In production: logged when the payload file is emitted.\n */\n name?: string;\n}\n\nexport interface LoadedDeferEntry extends DeferEntry {\n state: Exclude<DeferEntryState, { state: \"pending\" }>;\n drainPromise: Promise<string>;\n}\n\ntype DeferEntryState =\n | {\n state: \"pending\";\n element: ReactElement;\n }\n | {\n state: \"streaming\";\n stream: ReadableStream<Uint8Array>;\n }\n | {\n state: \"ready\";\n }\n | {\n state: \"error\";\n error: unknown;\n };\n\n/**\n * Sanitizes a name for use in file paths.\n * Replaces non-alphanumeric characters with underscores and limits length.\n */\nfunction sanitizeName(name: string): string {\n return name\n .replace(/[^a-zA-Z0-9_-]/g, \"_\")\n .replace(/_+/g, \"_\")\n .replace(/^_|_$/g, \"\")\n .slice(0, 50);\n}\n\nexport class DeferRegistry {\n #registry = new Map<string, DeferEntry>();\n\n register(element: ReactElement, id: string, name?: string) {\n this.#registry.set(id, { state: { element, state: \"pending\" }, name });\n }\n\n load(id: string): LoadedDeferEntry | undefined {\n const entry = this.#registry.get(id);\n if (!entry) {\n return undefined;\n }\n return this.#loadEntry(entry);\n }\n\n #loadEntry(entry: DeferEntry): LoadedDeferEntry {\n const { state } = entry;\n switch (state.state) {\n case \"pending\": {\n const stream = renderToReadableStream<ReactNode>(state.element);\n const [stream1, stream2] = stream.tee();\n entry.state = { state: \"streaming\", stream: stream1 };\n const drainPromise = drainStream(stream2);\n entry.drainPromise = drainPromise;\n drainPromise.then(\n () => {\n entry.state = { state: \"ready\" };\n },\n (error) => {\n entry.state = { state: \"error\", error };\n },\n );\n return entry as LoadedDeferEntry;\n }\n case \"streaming\":\n case \"ready\":\n case \"error\":\n return entry as LoadedDeferEntry;\n }\n }\n\n has(id: string): boolean {\n return this.#registry.has(id);\n }\n\n /**\n * Iterates over all entries in parallel.\n * Yields results as each stream completes.\n */\n async *loadAll() {\n const errors: unknown[] = [];\n\n // Phase 1: Start all entries loading and collect drain promises.\n // We use drain promises (which drain stream2 from tee) instead of\n // draining stream1 directly, because stream1 may have been locked\n // by createFromReadableStream during SSR.\n const loadedEntries = Array.from(this.#registry, ([id, entry]) => {\n const loaded = this.#loadEntry(entry);\n return [id, loaded.drainPromise, entry.name] as const;\n });\n\n if (loadedEntries.length === 0) return;\n\n type Result = { id: string; data: string; name?: string };\n\n // Completion queue\n const completed: Array<Result | { error: unknown }> = [];\n let waiting: (() => void) | undefined;\n let remainingCount = loadedEntries.length;\n\n const onComplete = (result: Result | { error: unknown }) => {\n completed.push(result);\n remainingCount--;\n waiting?.();\n };\n\n // Phase 2: Await drain promises\n for (const [id, drainPromise, name] of loadedEntries) {\n drainPromise.then(\n (data) => onComplete({ id, data, name }),\n (error) => onComplete({ error }),\n );\n }\n\n // Phase 3: Yield from queue as results arrive\n while (remainingCount > 0 || completed.length > 0) {\n if (completed.length === 0) {\n await new Promise<void>((r) => {\n waiting = r;\n });\n waiting = undefined;\n }\n for (const result of completed.splice(0)) {\n if (\"error\" in result) {\n errors.push(result.error);\n } else {\n yield result;\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors);\n }\n }\n}\n\nexport const deferRegistry = new DeferRegistry();\n\n/**\n * Renders given Server Component into a separate RSC payload.\n *\n * During the client side rendering, fetching of the payload will be\n * deferred until the returned ReactNode is actually rendered.\n *\n * @param element - The React element to defer.\n * @param options - Optional configuration for the deferred payload.\n * @returns A ReactNode that virtually contains the result of rendering the given component.\n */\nexport function defer(\n element: ReactElement,\n options?: DeferOptions,\n): ReactNode {\n const name = options?.name;\n const sanitizedName = name ? sanitizeName(name) : undefined;\n const rawId = sanitizedName\n ? `${sanitizedName}-${crypto.randomUUID()}`\n : crypto.randomUUID();\n const id = getPayloadIDFor(rawId);\n deferRegistry.register(element, id, name);\n\n return <DeferredComponent moduleID={id} />;\n}\n"],"mappings":";;;;;;;;;;;AAkDA,SAAS,aAAa,MAAsB;AAC1C,QAAO,KACJ,QAAQ,mBAAmB,IAAI,CAC/B,QAAQ,OAAO,IAAI,CACnB,QAAQ,UAAU,GAAG,CACrB,MAAM,GAAG,GAAG;;AAGjB,IAAa,gBAAb,MAA2B;CACzB,4BAAY,IAAI,KAAyB;CAEzC,SAAS,SAAuB,IAAY,MAAe;AACzD,QAAKA,SAAU,IAAI,IAAI;GAAE,OAAO;IAAE;IAAS,OAAO;IAAW;GAAE;GAAM,CAAC;;CAGxE,KAAK,IAA0C;EAC7C,MAAM,QAAQ,MAAKA,SAAU,IAAI,GAAG;AACpC,MAAI,CAAC,MACH;AAEF,SAAO,MAAKC,UAAW,MAAM;;CAG/B,WAAW,OAAqC;EAC9C,MAAM,EAAE,UAAU;AAClB,UAAQ,MAAM,OAAd;GACE,KAAK,WAAW;IAEd,MAAM,CAAC,SAAS,WADD,uBAAkC,MAAM,QAAQ,CAC7B,KAAK;AACvC,UAAM,QAAQ;KAAE,OAAO;KAAa,QAAQ;KAAS;IACrD,MAAM,eAAe,YAAY,QAAQ;AACzC,UAAM,eAAe;AACrB,iBAAa,WACL;AACJ,WAAM,QAAQ,EAAE,OAAO,SAAS;QAEjC,UAAU;AACT,WAAM,QAAQ;MAAE,OAAO;MAAS;MAAO;MAE1C;AACD,WAAO;;GAET,KAAK;GACL,KAAK;GACL,KAAK,QACH,QAAO;;;CAIb,IAAI,IAAqB;AACvB,SAAO,MAAKD,SAAU,IAAI,GAAG;;;;;;CAO/B,OAAO,UAAU;EACf,MAAM,SAAoB,EAAE;EAM5B,MAAM,gBAAgB,MAAM,KAAK,MAAKA,WAAY,CAAC,IAAI,WAAW;AAEhE,UAAO;IAAC;IADO,MAAKC,UAAW,MAAM,CAClB;IAAc,MAAM;IAAK;IAC5C;AAEF,MAAI,cAAc,WAAW,EAAG;EAKhC,MAAM,YAAgD,EAAE;EACxD,IAAI;EACJ,IAAI,iBAAiB,cAAc;EAEnC,MAAM,cAAc,WAAwC;AAC1D,aAAU,KAAK,OAAO;AACtB;AACA,cAAW;;AAIb,OAAK,MAAM,CAAC,IAAI,cAAc,SAAS,cACrC,cAAa,MACV,SAAS,WAAW;GAAE;GAAI;GAAM;GAAM,CAAC,GACvC,UAAU,WAAW,EAAE,OAAO,CAAC,CACjC;AAIH,SAAO,iBAAiB,KAAK,UAAU,SAAS,GAAG;AACjD,OAAI,UAAU,WAAW,GAAG;AAC1B,UAAM,IAAI,SAAe,MAAM;AAC7B,eAAU;MACV;AACF,cAAU;;AAEZ,QAAK,MAAM,UAAU,UAAU,OAAO,EAAE,CACtC,KAAI,WAAW,OACb,QAAO,KAAK,OAAO,MAAM;OAEzB,OAAM;;AAKZ,MAAI,OAAO,SAAS,EAClB,OAAM,IAAI,eAAe,OAAO;;;AAKtC,MAAa,gBAAgB,IAAI,eAAe;;;;;;;;;;;AAYhD,SAAgB,MACd,SACA,SACW;CACX,MAAM,OAAO,SAAS;CACtB,MAAM,gBAAgB,OAAO,aAAa,KAAK,GAAG;CAIlD,MAAM,KAAK,gBAHG,gBACV,GAAG,cAAc,GAAG,OAAO,YAAY,KACvC,OAAO,YAAY,CACU;AACjC,eAAc,SAAS,SAAS,IAAI,KAAK;AAEzC,QAAO,oBAAC,qBAAkB,UAAU,KAAM"}
1
+ {"version":3,"file":"defer.mjs","names":["#registry","#loadEntry"],"sources":["../../src/rsc/defer.tsx"],"sourcesContent":["import type { ReactElement, ReactNode } from \"react\";\nimport { renderToReadableStream } from \"@vitejs/plugin-rsc/react/rsc\";\nimport { DeferredComponent } from \"#rsc-client\";\nimport { drainStream } from \"../util/drainStream\";\nimport { getPayloadIDFor } from \"./rscModule\";\n\nexport interface DeferEntry {\n state: DeferEntryState;\n name?: string;\n drainPromise?: Promise<string>;\n}\n\n/**\n * Options for the defer function.\n */\nexport interface DeferOptions {\n /**\n * Optional name for debugging purposes.\n * In development: included in the RSC payload file name.\n * In production: logged when the payload file is emitted.\n */\n name?: string;\n}\n\nexport interface LoadedDeferEntry extends DeferEntry {\n state: Exclude<DeferEntryState, { state: \"pending\" }>;\n drainPromise: Promise<string>;\n}\n\ntype DeferEntryState =\n | {\n state: \"pending\";\n element: ReactElement;\n }\n | {\n state: \"streaming\";\n stream: ReadableStream<Uint8Array>;\n }\n | {\n state: \"ready\";\n }\n | {\n state: \"error\";\n error: unknown;\n };\n\n/**\n * Sanitizes a name for use in file paths.\n * Replaces non-alphanumeric characters with underscores and limits length.\n */\nfunction sanitizeName(name: string): string {\n return name\n .replace(/[^a-zA-Z0-9_-]/g, \"_\")\n .replace(/_+/g, \"_\")\n .replace(/^_|_$/g, \"\")\n .slice(0, 50);\n}\n\nexport class DeferRegistry {\n #registry = new Map<string, DeferEntry>();\n\n register(element: ReactElement, id: string, name?: string) {\n this.#registry.set(id, { state: { element, state: \"pending\" }, name });\n }\n\n load(id: string): LoadedDeferEntry | undefined {\n const entry = this.#registry.get(id);\n if (!entry) {\n return undefined;\n }\n return this.#loadEntry(entry);\n }\n\n #loadEntry(entry: DeferEntry): LoadedDeferEntry {\n const { state } = entry;\n switch (state.state) {\n case \"pending\": {\n const stream = renderToReadableStream<ReactNode>(state.element);\n const [stream1, stream2] = stream.tee();\n entry.state = { state: \"streaming\", stream: stream1 };\n const drainPromise = drainStream(stream2);\n entry.drainPromise = drainPromise;\n drainPromise.then(\n () => {\n entry.state = { state: \"ready\" };\n },\n (error) => {\n entry.state = { state: \"error\", error };\n },\n );\n return entry as LoadedDeferEntry;\n }\n case \"streaming\":\n case \"ready\":\n case \"error\":\n return entry as LoadedDeferEntry;\n }\n }\n\n has(id: string): boolean {\n return this.#registry.has(id);\n }\n\n /**\n * Iterates over all entries in parallel.\n * Yields results as each stream completes.\n */\n async *loadAll() {\n const errors: unknown[] = [];\n\n // Phase 1: Start all entries loading and collect drain promises.\n // We use drain promises (which drain stream2 from tee) instead of\n // draining stream1 directly, because stream1 may have been locked\n // by createFromReadableStream during SSR.\n const loadedEntries = Array.from(this.#registry, ([id, entry]) => {\n const loaded = this.#loadEntry(entry);\n return [id, loaded.drainPromise, entry.name] as const;\n });\n\n if (loadedEntries.length === 0) return;\n\n type Result = { id: string; data: string; name?: string };\n\n // Completion queue\n const completed: Array<Result | { error: unknown }> = [];\n let waiting: (() => void) | undefined;\n let remainingCount = loadedEntries.length;\n\n const onComplete = (result: Result | { error: unknown }) => {\n completed.push(result);\n remainingCount--;\n waiting?.();\n };\n\n // Phase 2: Await drain promises\n for (const [id, drainPromise, name] of loadedEntries) {\n drainPromise.then(\n (data) => onComplete({ id, data, name }),\n (error) => onComplete({ error }),\n );\n }\n\n // Phase 3: Yield from queue as results arrive\n while (remainingCount > 0 || completed.length > 0) {\n if (completed.length === 0) {\n await new Promise<void>((r) => {\n waiting = r;\n });\n waiting = undefined;\n }\n for (const result of completed.splice(0)) {\n if (\"error\" in result) {\n errors.push(result.error);\n } else {\n yield result;\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors);\n }\n }\n}\n\nexport const deferRegistry = new DeferRegistry();\n\n/**\n * Renders given Server Component into a separate RSC payload.\n *\n * During the client side rendering, fetching of the payload will be\n * deferred until the returned ReactNode is actually rendered.\n *\n * @param element - The React element to defer.\n * @param options - Optional configuration for the deferred payload.\n * @returns A ReactNode that virtually contains the result of rendering the given component.\n */\nexport function defer(\n element: ReactElement,\n options?: DeferOptions,\n): ReactNode {\n const name = options?.name;\n const sanitizedName = name ? sanitizeName(name) : undefined;\n const rawId = sanitizedName\n ? `${sanitizedName}-${crypto.randomUUID()}`\n : crypto.randomUUID();\n const id = getPayloadIDFor(rawId);\n deferRegistry.register(element, id, name);\n\n return <DeferredComponent moduleID={id} />;\n}\n"],"mappings":";;;;;;;;;;AAkDA,SAAS,aAAa,MAAsB;AAC1C,QAAO,KACJ,QAAQ,mBAAmB,IAAI,CAC/B,QAAQ,OAAO,IAAI,CACnB,QAAQ,UAAU,GAAG,CACrB,MAAM,GAAG,GAAG;;AAGjB,IAAa,gBAAb,MAA2B;CACzB,4BAAY,IAAI,KAAyB;CAEzC,SAAS,SAAuB,IAAY,MAAe;AACzD,QAAA,SAAe,IAAI,IAAI;GAAE,OAAO;IAAE;IAAS,OAAO;IAAW;GAAE;GAAM,CAAC;;CAGxE,KAAK,IAA0C;EAC7C,MAAM,QAAQ,MAAA,SAAe,IAAI,GAAG;AACpC,MAAI,CAAC,MACH;AAEF,SAAO,MAAA,UAAgB,MAAM;;CAG/B,WAAW,OAAqC;EAC9C,MAAM,EAAE,UAAU;AAClB,UAAQ,MAAM,OAAd;GACE,KAAK,WAAW;IAEd,MAAM,CAAC,SAAS,WADD,uBAAkC,MAAM,QAAQ,CAC7B,KAAK;AACvC,UAAM,QAAQ;KAAE,OAAO;KAAa,QAAQ;KAAS;IACrD,MAAM,eAAe,YAAY,QAAQ;AACzC,UAAM,eAAe;AACrB,iBAAa,WACL;AACJ,WAAM,QAAQ,EAAE,OAAO,SAAS;QAEjC,UAAU;AACT,WAAM,QAAQ;MAAE,OAAO;MAAS;MAAO;MAE1C;AACD,WAAO;;GAET,KAAK;GACL,KAAK;GACL,KAAK,QACH,QAAO;;;CAIb,IAAI,IAAqB;AACvB,SAAO,MAAA,SAAe,IAAI,GAAG;;;;;;CAO/B,OAAO,UAAU;EACf,MAAM,SAAoB,EAAE;EAM5B,MAAM,gBAAgB,MAAM,KAAK,MAAA,WAAiB,CAAC,IAAI,WAAW;AAEhE,UAAO;IAAC;IADO,MAAA,UAAgB,MAAM,CAClB;IAAc,MAAM;IAAK;IAC5C;AAEF,MAAI,cAAc,WAAW,EAAG;EAKhC,MAAM,YAAgD,EAAE;EACxD,IAAI;EACJ,IAAI,iBAAiB,cAAc;EAEnC,MAAM,cAAc,WAAwC;AAC1D,aAAU,KAAK,OAAO;AACtB;AACA,cAAW;;AAIb,OAAK,MAAM,CAAC,IAAI,cAAc,SAAS,cACrC,cAAa,MACV,SAAS,WAAW;GAAE;GAAI;GAAM;GAAM,CAAC,GACvC,UAAU,WAAW,EAAE,OAAO,CAAC,CACjC;AAIH,SAAO,iBAAiB,KAAK,UAAU,SAAS,GAAG;AACjD,OAAI,UAAU,WAAW,GAAG;AAC1B,UAAM,IAAI,SAAe,MAAM;AAC7B,eAAU;MACV;AACF,cAAU,KAAA;;AAEZ,QAAK,MAAM,UAAU,UAAU,OAAO,EAAE,CACtC,KAAI,WAAW,OACb,QAAO,KAAK,OAAO,MAAM;OAEzB,OAAM;;AAKZ,MAAI,OAAO,SAAS,EAClB,OAAM,IAAI,eAAe,OAAO;;;AAKtC,MAAa,gBAAgB,IAAI,eAAe;;;;;;;;;;;AAYhD,SAAgB,MACd,SACA,SACW;CACX,MAAM,OAAO,SAAS;CACtB,MAAM,gBAAgB,OAAO,aAAa,KAAK,GAAG,KAAA;CAIlD,MAAM,KAAK,gBAHG,gBACV,GAAG,cAAc,GAAG,OAAO,YAAY,KACvC,OAAO,YAAY,CACU;AACjC,eAAc,SAAS,SAAS,IAAI,KAAK;AAEzC,QAAO,oBAAC,mBAAD,EAAmB,UAAU,IAAM,CAAA"}
@@ -1,14 +1,13 @@
1
1
  import { extractIDFromModulePath } from "./rscModule.mjs";
2
2
  import { urlPathToFileCandidates } from "../util/urlPath.mjs";
3
- import { devMainRscPath } from "./request.mjs";
3
+ import "./request.mjs";
4
4
  import { stripBasePath } from "../util/basePath.mjs";
5
- import { defer, deferRegistry } from "./defer.mjs";
5
+ import { deferRegistry } from "./defer.mjs";
6
6
  import { generateAppMarker } from "./marker.mjs";
7
7
  import { resolveApp, resolveRoot } from "./resolveEntry.mjs";
8
8
  import { jsx } from "react/jsx-runtime";
9
9
  import { ssr } from "virtual:funstack/config";
10
10
  import { renderToReadableStream } from "@vitejs/plugin-rsc/rsc";
11
-
12
11
  //#region src/rsc/entry.tsx
13
12
  async function loadEntriesList() {
14
13
  const getEntries = (await import("virtual:funstack/entries")).default;
@@ -114,7 +113,7 @@ function isServeRSCError(error) {
114
113
  async function serveRSC(request) {
115
114
  const timings = [];
116
115
  const pathname = stripBasePath(new URL(request.url).pathname);
117
- if (pathname === devMainRscPath) {
116
+ if (pathname === "/funstack__/rsc") {
118
117
  const entriesStart = performance.now();
119
118
  const entries = await loadEntriesList();
120
119
  timings.push(`entries;dur=${performance.now() - entriesStart}`);
@@ -199,7 +198,7 @@ async function build() {
199
198
  };
200
199
  }
201
200
  if (import.meta.hot) import.meta.hot.accept();
202
-
203
201
  //#endregion
204
202
  export { build, isServeRSCError, serveHTML, serveRSC };
203
+
205
204
  //# sourceMappingURL=entry.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"entry.mjs","names":["ssrEnabled"],"sources":["../../src/rsc/entry.tsx"],"sourcesContent":["import \"./defer\";\nimport { renderToReadableStream } from \"@vitejs/plugin-rsc/rsc\";\nimport { devMainRscPath } from \"./request\";\nimport { generateAppMarker } from \"./marker\";\nimport { deferRegistry } from \"./defer\";\nimport { extractIDFromModulePath } from \"./rscModule\";\nimport { stripBasePath } from \"../util/basePath\";\nimport { urlPathToFileCandidates } from \"../util/urlPath\";\nimport { resolveRoot, resolveApp } from \"./resolveEntry\";\nimport type { EntryDefinition, GetEntriesResult } from \"../entryDefinition\";\n\nexport type RscPayload = {\n root: React.ReactNode;\n};\n\nexport type EntryBuildResult = {\n path: string;\n html: ReadableStream<Uint8Array>;\n appRsc: ReadableStream<Uint8Array>;\n};\n\nimport { ssr as ssrEnabled } from \"virtual:funstack/config\";\n\nasync function loadEntriesList(): Promise<EntryDefinition[]> {\n const getEntries = (await import(\"virtual:funstack/entries\")).default;\n const result: GetEntriesResult = getEntries();\n const entries: EntryDefinition[] = [];\n for await (const entry of result) {\n entries.push(entry);\n }\n return entries;\n}\n\n/**\n * Find the entry matching a URL path from a list of entries.\n */\nfunction findEntryForUrlPath(\n entries: EntryDefinition[],\n urlPath: string,\n): EntryDefinition | undefined {\n const candidates = urlPathToFileCandidates(urlPath);\n for (const candidate of candidates) {\n const entry = entries.find((e) => e.path === candidate);\n if (entry) {\n return entry;\n }\n }\n return undefined;\n}\n\n/**\n * Renders a single entry to an HTML response.\n */\nasync function renderEntryToResponse(\n entry: EntryDefinition,\n timings: string[],\n): Promise<Response> {\n const marker = generateAppMarker();\n\n const resolveStart = performance.now();\n const Root = await resolveRoot(entry.root);\n const appNode = await resolveApp(entry.app);\n timings.push(`resolve;dur=${performance.now() - resolveStart}`);\n\n const ssrModuleStart = performance.now();\n const ssrEntryModule = await import.meta.viteRsc.loadModule<\n typeof import(\"../ssr/entry\")\n >(\"ssr\");\n timings.push(`ssr-module;dur=${performance.now() - ssrModuleStart}`);\n\n if (ssrEnabled) {\n // SSR on: single RSC stream with full tree\n const rscStart = performance.now();\n const rootRscStream = renderToReadableStream<RscPayload>({\n root: <Root>{appNode}</Root>,\n });\n timings.push(`rsc;dur=${performance.now() - rscStart}`);\n\n const ssrStart = performance.now();\n const ssrResult = await ssrEntryModule.renderHTML(rootRscStream, {\n appEntryMarker: marker,\n build: false,\n ssr: true,\n deferRegistry,\n });\n timings.push(`ssr;dur=${performance.now() - ssrStart}`);\n\n return new Response(ssrResult.stream, {\n status: ssrResult.status,\n headers: {\n \"Content-type\": \"text/html\",\n \"Server-Timing\": timings.join(\", \"),\n },\n });\n } else {\n // SSR off: shell RSC for SSR, full RSC for client\n const rscStart = performance.now();\n const shellRscStream = renderToReadableStream<RscPayload>({\n root: (\n <Root>\n <span id={marker} />\n </Root>\n ),\n });\n const clientRscStream = renderToReadableStream<RscPayload>({\n root: <Root>{appNode}</Root>,\n });\n timings.push(`rsc;dur=${performance.now() - rscStart}`);\n\n const ssrStart = performance.now();\n const ssrResult = await ssrEntryModule.renderHTML(shellRscStream, {\n appEntryMarker: marker,\n build: false,\n ssr: false,\n clientRscStream,\n });\n timings.push(`ssr;dur=${performance.now() - ssrStart}`);\n\n return new Response(ssrResult.stream, {\n status: ssrResult.status,\n headers: {\n \"Content-type\": \"text/html\",\n \"Server-Timing\": timings.join(\", \"),\n },\n });\n }\n}\n\n/**\n * Entrypoint to serve HTML response in dev environment.\n * Accepts a Request to determine which entry to render based on URL path.\n */\nexport async function serveHTML(request: Request): Promise<Response> {\n const timings: string[] = [];\n\n const entriesStart = performance.now();\n const entries = await loadEntriesList();\n timings.push(`entries;dur=${performance.now() - entriesStart}`);\n\n const url = new URL(request.url);\n const urlPath = stripBasePath(url.pathname);\n let entry = findEntryForUrlPath(entries, urlPath);\n\n // SPA fallback: if no entry matched, fall back to index.html or index.htm entry\n if (!entry) {\n entry = entries.find(\n (e) => e.path === \"index.html\" || e.path === \"index.htm\",\n );\n }\n\n if (!entry) {\n return new Response(\"Not Found\", {\n status: 404,\n headers: { \"Content-type\": \"text/plain\" },\n });\n }\n\n return renderEntryToResponse(entry, timings);\n}\n\nclass ServeRSCError extends Error {\n status: 404 | 500;\n constructor(message: string, status: 404 | 500) {\n super(message);\n this.name = \"ServeRSCError\";\n this.status = status;\n }\n}\n\nexport function isServeRSCError(error: unknown): error is ServeRSCError {\n return error instanceof Error && error.name === \"ServeRSCError\";\n}\n\n/**\n * Serves an RSC stream response\n */\nexport async function serveRSC(request: Request): Promise<Response> {\n const timings: string[] = [];\n const url = new URL(request.url);\n const pathname = stripBasePath(url.pathname);\n if (pathname === devMainRscPath) {\n // root RSC stream is requested (HMR re-fetch always sends full tree)\n // For HMR, re-render the first entry (single-entry mode) or index.html entry\n const entriesStart = performance.now();\n const entries = await loadEntriesList();\n timings.push(`entries;dur=${performance.now() - entriesStart}`);\n\n // Use the first entry for HMR re-fetch\n const entry = entries[0];\n if (!entry) {\n throw new ServeRSCError(\"No entries defined\", 404);\n }\n\n const resolveStart = performance.now();\n const Root = await resolveRoot(entry.root);\n const appNode = await resolveApp(entry.app);\n timings.push(`resolve;dur=${performance.now() - resolveStart}`);\n\n const rscStart = performance.now();\n const rootRscStream = renderToReadableStream<RscPayload>({\n root: <Root>{appNode}</Root>,\n });\n timings.push(`rsc;dur=${performance.now() - rscStart}`);\n\n return new Response(rootRscStream, {\n status: 200,\n headers: {\n \"content-type\": \"text/x-component;charset=utf-8\",\n \"Server-Timing\": timings.join(\", \"),\n },\n });\n }\n\n const moduleId = extractIDFromModulePath(pathname);\n if (!moduleId) {\n throw new ServeRSCError(`Invalid RSC module path: ${pathname}`, 404);\n }\n\n const deferLoadStart = performance.now();\n const entry = deferRegistry.load(moduleId);\n if (!entry) {\n throw new ServeRSCError(`RSC component not found: ${moduleId}`, 404);\n }\n timings.push(`defer-load;dur=${performance.now() - deferLoadStart}`);\n\n const { state } = entry;\n switch (state.state) {\n case \"streaming\": {\n return new Response(state.stream, {\n status: 200,\n headers: {\n \"content-type\": \"text/x-component;charset=utf-8\",\n \"Server-Timing\": timings.join(\", \"),\n },\n });\n }\n case \"ready\": {\n return new Response(await entry.drainPromise, {\n status: 200,\n headers: {\n \"content-type\": \"text/x-component;charset=utf-8\",\n \"Server-Timing\": timings.join(\", \"),\n },\n });\n }\n case \"error\": {\n throw new ServeRSCError(\n `Failed to load RSC component: ${state.error}`,\n 500,\n );\n }\n }\n}\n\n/**\n * Build handler — iterates over all entries and returns per-entry results\n * along with the shared defer registry.\n */\nexport async function build() {\n const getEntries = (await import(\"virtual:funstack/entries\")).default;\n\n const ssrEntryModule = await import.meta.viteRsc.loadModule<\n typeof import(\"../ssr/entry\")\n >(\"ssr\");\n\n const results: EntryBuildResult[] = [];\n for await (const entry of getEntries()) {\n const Root = await resolveRoot(entry.root);\n const appNode = await resolveApp(entry.app);\n\n const marker = generateAppMarker();\n\n let rootRscStream: ReadableStream<Uint8Array>;\n let appRscStream: ReadableStream<Uint8Array>;\n\n if (ssrEnabled) {\n // SSR on: both streams have full tree\n rootRscStream = renderToReadableStream<RscPayload>({\n root: <Root>{appNode}</Root>,\n });\n appRscStream = renderToReadableStream<RscPayload>({\n root: <Root>{appNode}</Root>,\n });\n } else {\n // SSR off: root stream has shell, app stream has App only\n rootRscStream = renderToReadableStream<RscPayload>({\n root: (\n <Root>\n <span id={marker} />\n </Root>\n ),\n });\n appRscStream = renderToReadableStream<RscPayload>({\n root: appNode,\n });\n }\n\n const ssrResult = await ssrEntryModule.renderHTML(rootRscStream, {\n appEntryMarker: marker,\n build: true,\n ssr: ssrEnabled,\n deferRegistry,\n });\n\n results.push({\n path: entry.path,\n html: ssrResult.stream,\n appRsc: appRscStream,\n });\n }\n\n return {\n entries: results,\n deferRegistry,\n };\n}\n\nexport { defer } from \"./defer\";\n\nif (import.meta.hot) {\n import.meta.hot.accept();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"entry.mjs","names":["ssrEnabled"],"sources":["../../src/rsc/entry.tsx"],"sourcesContent":["import \"./defer\";\nimport { renderToReadableStream } from \"@vitejs/plugin-rsc/rsc\";\nimport { devMainRscPath } from \"./request\";\nimport { generateAppMarker } from \"./marker\";\nimport { deferRegistry } from \"./defer\";\nimport { extractIDFromModulePath } from \"./rscModule\";\nimport { stripBasePath } from \"../util/basePath\";\nimport { urlPathToFileCandidates } from \"../util/urlPath\";\nimport { resolveRoot, resolveApp } from \"./resolveEntry\";\nimport type { EntryDefinition, GetEntriesResult } from \"../entryDefinition\";\n\nexport type RscPayload = {\n root: React.ReactNode;\n};\n\nexport type EntryBuildResult = {\n path: string;\n html: ReadableStream<Uint8Array>;\n appRsc: ReadableStream<Uint8Array>;\n};\n\nimport { ssr as ssrEnabled } from \"virtual:funstack/config\";\n\nasync function loadEntriesList(): Promise<EntryDefinition[]> {\n const getEntries = (await import(\"virtual:funstack/entries\")).default;\n const result: GetEntriesResult = getEntries();\n const entries: EntryDefinition[] = [];\n for await (const entry of result) {\n entries.push(entry);\n }\n return entries;\n}\n\n/**\n * Find the entry matching a URL path from a list of entries.\n */\nfunction findEntryForUrlPath(\n entries: EntryDefinition[],\n urlPath: string,\n): EntryDefinition | undefined {\n const candidates = urlPathToFileCandidates(urlPath);\n for (const candidate of candidates) {\n const entry = entries.find((e) => e.path === candidate);\n if (entry) {\n return entry;\n }\n }\n return undefined;\n}\n\n/**\n * Renders a single entry to an HTML response.\n */\nasync function renderEntryToResponse(\n entry: EntryDefinition,\n timings: string[],\n): Promise<Response> {\n const marker = generateAppMarker();\n\n const resolveStart = performance.now();\n const Root = await resolveRoot(entry.root);\n const appNode = await resolveApp(entry.app);\n timings.push(`resolve;dur=${performance.now() - resolveStart}`);\n\n const ssrModuleStart = performance.now();\n const ssrEntryModule = await import.meta.viteRsc.loadModule<\n typeof import(\"../ssr/entry\")\n >(\"ssr\");\n timings.push(`ssr-module;dur=${performance.now() - ssrModuleStart}`);\n\n if (ssrEnabled) {\n // SSR on: single RSC stream with full tree\n const rscStart = performance.now();\n const rootRscStream = renderToReadableStream<RscPayload>({\n root: <Root>{appNode}</Root>,\n });\n timings.push(`rsc;dur=${performance.now() - rscStart}`);\n\n const ssrStart = performance.now();\n const ssrResult = await ssrEntryModule.renderHTML(rootRscStream, {\n appEntryMarker: marker,\n build: false,\n ssr: true,\n deferRegistry,\n });\n timings.push(`ssr;dur=${performance.now() - ssrStart}`);\n\n return new Response(ssrResult.stream, {\n status: ssrResult.status,\n headers: {\n \"Content-type\": \"text/html\",\n \"Server-Timing\": timings.join(\", \"),\n },\n });\n } else {\n // SSR off: shell RSC for SSR, full RSC for client\n const rscStart = performance.now();\n const shellRscStream = renderToReadableStream<RscPayload>({\n root: (\n <Root>\n <span id={marker} />\n </Root>\n ),\n });\n const clientRscStream = renderToReadableStream<RscPayload>({\n root: <Root>{appNode}</Root>,\n });\n timings.push(`rsc;dur=${performance.now() - rscStart}`);\n\n const ssrStart = performance.now();\n const ssrResult = await ssrEntryModule.renderHTML(shellRscStream, {\n appEntryMarker: marker,\n build: false,\n ssr: false,\n clientRscStream,\n });\n timings.push(`ssr;dur=${performance.now() - ssrStart}`);\n\n return new Response(ssrResult.stream, {\n status: ssrResult.status,\n headers: {\n \"Content-type\": \"text/html\",\n \"Server-Timing\": timings.join(\", \"),\n },\n });\n }\n}\n\n/**\n * Entrypoint to serve HTML response in dev environment.\n * Accepts a Request to determine which entry to render based on URL path.\n */\nexport async function serveHTML(request: Request): Promise<Response> {\n const timings: string[] = [];\n\n const entriesStart = performance.now();\n const entries = await loadEntriesList();\n timings.push(`entries;dur=${performance.now() - entriesStart}`);\n\n const url = new URL(request.url);\n const urlPath = stripBasePath(url.pathname);\n let entry = findEntryForUrlPath(entries, urlPath);\n\n // SPA fallback: if no entry matched, fall back to index.html or index.htm entry\n if (!entry) {\n entry = entries.find(\n (e) => e.path === \"index.html\" || e.path === \"index.htm\",\n );\n }\n\n if (!entry) {\n return new Response(\"Not Found\", {\n status: 404,\n headers: { \"Content-type\": \"text/plain\" },\n });\n }\n\n return renderEntryToResponse(entry, timings);\n}\n\nclass ServeRSCError extends Error {\n status: 404 | 500;\n constructor(message: string, status: 404 | 500) {\n super(message);\n this.name = \"ServeRSCError\";\n this.status = status;\n }\n}\n\nexport function isServeRSCError(error: unknown): error is ServeRSCError {\n return error instanceof Error && error.name === \"ServeRSCError\";\n}\n\n/**\n * Serves an RSC stream response\n */\nexport async function serveRSC(request: Request): Promise<Response> {\n const timings: string[] = [];\n const url = new URL(request.url);\n const pathname = stripBasePath(url.pathname);\n if (pathname === devMainRscPath) {\n // root RSC stream is requested (HMR re-fetch always sends full tree)\n // For HMR, re-render the first entry (single-entry mode) or index.html entry\n const entriesStart = performance.now();\n const entries = await loadEntriesList();\n timings.push(`entries;dur=${performance.now() - entriesStart}`);\n\n // Use the first entry for HMR re-fetch\n const entry = entries[0];\n if (!entry) {\n throw new ServeRSCError(\"No entries defined\", 404);\n }\n\n const resolveStart = performance.now();\n const Root = await resolveRoot(entry.root);\n const appNode = await resolveApp(entry.app);\n timings.push(`resolve;dur=${performance.now() - resolveStart}`);\n\n const rscStart = performance.now();\n const rootRscStream = renderToReadableStream<RscPayload>({\n root: <Root>{appNode}</Root>,\n });\n timings.push(`rsc;dur=${performance.now() - rscStart}`);\n\n return new Response(rootRscStream, {\n status: 200,\n headers: {\n \"content-type\": \"text/x-component;charset=utf-8\",\n \"Server-Timing\": timings.join(\", \"),\n },\n });\n }\n\n const moduleId = extractIDFromModulePath(pathname);\n if (!moduleId) {\n throw new ServeRSCError(`Invalid RSC module path: ${pathname}`, 404);\n }\n\n const deferLoadStart = performance.now();\n const entry = deferRegistry.load(moduleId);\n if (!entry) {\n throw new ServeRSCError(`RSC component not found: ${moduleId}`, 404);\n }\n timings.push(`defer-load;dur=${performance.now() - deferLoadStart}`);\n\n const { state } = entry;\n switch (state.state) {\n case \"streaming\": {\n return new Response(state.stream, {\n status: 200,\n headers: {\n \"content-type\": \"text/x-component;charset=utf-8\",\n \"Server-Timing\": timings.join(\", \"),\n },\n });\n }\n case \"ready\": {\n return new Response(await entry.drainPromise, {\n status: 200,\n headers: {\n \"content-type\": \"text/x-component;charset=utf-8\",\n \"Server-Timing\": timings.join(\", \"),\n },\n });\n }\n case \"error\": {\n throw new ServeRSCError(\n `Failed to load RSC component: ${state.error}`,\n 500,\n );\n }\n }\n}\n\n/**\n * Build handler — iterates over all entries and returns per-entry results\n * along with the shared defer registry.\n */\nexport async function build() {\n const getEntries = (await import(\"virtual:funstack/entries\")).default;\n\n const ssrEntryModule = await import.meta.viteRsc.loadModule<\n typeof import(\"../ssr/entry\")\n >(\"ssr\");\n\n const results: EntryBuildResult[] = [];\n for await (const entry of getEntries()) {\n const Root = await resolveRoot(entry.root);\n const appNode = await resolveApp(entry.app);\n\n const marker = generateAppMarker();\n\n let rootRscStream: ReadableStream<Uint8Array>;\n let appRscStream: ReadableStream<Uint8Array>;\n\n if (ssrEnabled) {\n // SSR on: both streams have full tree\n rootRscStream = renderToReadableStream<RscPayload>({\n root: <Root>{appNode}</Root>,\n });\n appRscStream = renderToReadableStream<RscPayload>({\n root: <Root>{appNode}</Root>,\n });\n } else {\n // SSR off: root stream has shell, app stream has App only\n rootRscStream = renderToReadableStream<RscPayload>({\n root: (\n <Root>\n <span id={marker} />\n </Root>\n ),\n });\n appRscStream = renderToReadableStream<RscPayload>({\n root: appNode,\n });\n }\n\n const ssrResult = await ssrEntryModule.renderHTML(rootRscStream, {\n appEntryMarker: marker,\n build: true,\n ssr: ssrEnabled,\n deferRegistry,\n });\n\n results.push({\n path: entry.path,\n html: ssrResult.stream,\n appRsc: appRscStream,\n });\n }\n\n return {\n entries: results,\n deferRegistry,\n };\n}\n\nexport { defer } from \"./defer\";\n\nif (import.meta.hot) {\n import.meta.hot.accept();\n}\n"],"mappings":"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"}
@@ -9,7 +9,7 @@ const markerPrefix = "__FUNSTACK_APP_ENTRY__";
9
9
  function generateAppMarker() {
10
10
  return `${markerPrefix}${crypto.randomUUID()}`;
11
11
  }
12
-
13
12
  //#endregion
14
13
  export { generateAppMarker };
14
+
15
15
  //# sourceMappingURL=marker.mjs.map
@@ -3,7 +3,7 @@
3
3
  * Path of RSC stream endpoint in development environment.
4
4
  */
5
5
  const devMainRscPath = "/funstack__/rsc";
6
-
7
6
  //#endregion
8
7
  export { devMainRscPath };
8
+
9
9
  //# sourceMappingURL=request.mjs.map
@@ -1,5 +1,4 @@
1
1
  import { createElement } from "react";
2
-
3
2
  //#region src/rsc/resolveEntry.ts
4
3
  /**
5
4
  * Resolves the root field of an EntryDefinition to a concrete React component.
@@ -23,7 +22,7 @@ async function resolveApp(app) {
23
22
  if (isAppModule(resolved)) return createElement(resolved.default);
24
23
  return resolved;
25
24
  }
26
-
27
25
  //#endregion
28
26
  export { resolveApp, resolveRoot };
27
+
29
28
  //# sourceMappingURL=resolveEntry.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolveEntry.mjs","names":[],"sources":["../../src/rsc/resolveEntry.ts"],"sourcesContent":["import { createElement } from \"react\";\nimport type { EntryDefinition } from \"../entryDefinition\";\n\n/**\n * Resolves the root field of an EntryDefinition to a concrete React component.\n */\nexport async function resolveRoot(\n root: EntryDefinition[\"root\"],\n): Promise<React.ComponentType<{ children: React.ReactNode }>> {\n const module = typeof root === \"function\" ? await root() : await root;\n return module.default;\n}\n\n/**\n * Checks whether a value is an AppModule (has a `default` property that is a function).\n */\nfunction isAppModule(\n value: unknown,\n): value is { default: React.ComponentType } {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"default\" in value &&\n typeof (value as Record<string, unknown>).default === \"function\"\n );\n}\n\n/**\n * Resolves the app field of an EntryDefinition to a React node.\n */\nexport async function resolveApp(\n app: EntryDefinition[\"app\"],\n): Promise<React.ReactNode> {\n if (typeof app === \"function\") {\n // Lazy import: () => Promise<{ default: Component }>\n const module = await app();\n return createElement(module.default);\n }\n if (isAppModule(app)) {\n // Sync module object: { default: Component }\n return createElement(app.default);\n }\n // Could be a Promise<AppModule> or a ReactNode (including Promise<ReactNode>).\n // Await it and check the resolved value.\n const resolved = await app;\n if (isAppModule(resolved)) {\n return createElement(resolved.default);\n }\n // ReactNode (JSX of a server component, or a resolved ReactNode)\n return resolved;\n}\n"],"mappings":";;;;;;AAMA,eAAsB,YACpB,MAC6D;AAE7D,SADe,OAAO,SAAS,aAAa,MAAM,MAAM,GAAG,MAAM,MACnD;;;;;AAMhB,SAAS,YACP,OAC2C;AAC3C,QACE,OAAO,UAAU,YACjB,UAAU,QACV,aAAa,SACb,OAAQ,MAAkC,YAAY;;;;;AAO1D,eAAsB,WACpB,KAC0B;AAC1B,KAAI,OAAO,QAAQ,WAGjB,QAAO,eADQ,MAAM,KAAK,EACE,QAAQ;AAEtC,KAAI,YAAY,IAAI,CAElB,QAAO,cAAc,IAAI,QAAQ;CAInC,MAAM,WAAW,MAAM;AACvB,KAAI,YAAY,SAAS,CACvB,QAAO,cAAc,SAAS,QAAQ;AAGxC,QAAO"}
1
+ {"version":3,"file":"resolveEntry.mjs","names":[],"sources":["../../src/rsc/resolveEntry.ts"],"sourcesContent":["import { createElement } from \"react\";\nimport type { EntryDefinition } from \"../entryDefinition\";\n\n/**\n * Resolves the root field of an EntryDefinition to a concrete React component.\n */\nexport async function resolveRoot(\n root: EntryDefinition[\"root\"],\n): Promise<React.ComponentType<{ children: React.ReactNode }>> {\n const module = typeof root === \"function\" ? await root() : await root;\n return module.default;\n}\n\n/**\n * Checks whether a value is an AppModule (has a `default` property that is a function).\n */\nfunction isAppModule(\n value: unknown,\n): value is { default: React.ComponentType } {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"default\" in value &&\n typeof (value as Record<string, unknown>).default === \"function\"\n );\n}\n\n/**\n * Resolves the app field of an EntryDefinition to a React node.\n */\nexport async function resolveApp(\n app: EntryDefinition[\"app\"],\n): Promise<React.ReactNode> {\n if (typeof app === \"function\") {\n // Lazy import: () => Promise<{ default: Component }>\n const module = await app();\n return createElement(module.default);\n }\n if (isAppModule(app)) {\n // Sync module object: { default: Component }\n return createElement(app.default);\n }\n // Could be a Promise<AppModule> or a ReactNode (including Promise<ReactNode>).\n // Await it and check the resolved value.\n const resolved = await app;\n if (isAppModule(resolved)) {\n return createElement(resolved.default);\n }\n // ReactNode (JSX of a server component, or a resolved ReactNode)\n return resolved;\n}\n"],"mappings":";;;;;AAMA,eAAsB,YACpB,MAC6D;AAE7D,SADe,OAAO,SAAS,aAAa,MAAM,MAAM,GAAG,MAAM,MACnD;;;;;AAMhB,SAAS,YACP,OAC2C;AAC3C,QACE,OAAO,UAAU,YACjB,UAAU,QACV,aAAa,SACb,OAAQ,MAAkC,YAAY;;;;;AAO1D,eAAsB,WACpB,KAC0B;AAC1B,KAAI,OAAO,QAAQ,WAGjB,QAAO,eADQ,MAAM,KAAK,EACE,QAAQ;AAEtC,KAAI,YAAY,IAAI,CAElB,QAAO,cAAc,IAAI,QAAQ;CAInC,MAAM,WAAW,MAAM;AACvB,KAAI,YAAY,SAAS,CACvB,QAAO,cAAc,SAAS,QAAQ;AAGxC,QAAO"}
@@ -19,7 +19,7 @@ function extractIDFromModulePath(modulePath) {
19
19
  if (!modulePath.startsWith(rscModulePathPrefix) || !modulePath.endsWith(rscModulePathSuffix)) return;
20
20
  return modulePath.slice(12, -4);
21
21
  }
22
-
23
22
  //#endregion
24
23
  export { extractIDFromModulePath, getModulePathFor, getPayloadIDFor };
24
+
25
25
  //# sourceMappingURL=rscModule.mjs.map
@@ -1,8 +1,7 @@
1
1
  import { getModulePathFor } from "../rsc/rscModule.mjs";
2
2
  import { withBasePath } from "../util/basePath.mjs";
3
3
  import { createFromFetch } from "@vitejs/plugin-rsc/browser";
4
- import React, { createContext, use } from "react";
5
-
4
+ import { createContext, use } from "react";
6
5
  //#region src/rsc-client/clientWrapper.tsx
7
6
  const RegistryContext = createContext(void 0);
8
7
  const DeferredComponent = ({ moduleID }) => {
@@ -38,7 +37,7 @@ function getClientRSCStream(modulePath) {
38
37
  }
39
38
  return stream;
40
39
  }
41
-
42
40
  //#endregion
43
41
  export { DeferredComponent, RegistryContext };
42
+
44
43
  //# sourceMappingURL=clientWrapper.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"clientWrapper.mjs","names":[],"sources":["../../src/rsc-client/clientWrapper.tsx"],"sourcesContent":["import React from \"react\";\nimport { createFromFetch } from \"@vitejs/plugin-rsc/browser\";\nimport { getModulePathFor } from \"../rsc/rscModule\";\nimport { createContext, use } from \"react\";\nimport type { LoadedDeferEntry, DeferRegistry } from \"../rsc/defer\";\nimport { withBasePath } from \"../util/basePath\";\n\ninterface DeferContextValue {\n registry: DeferRegistry;\n createFromReadableStream: <T>(\n stream: ReadableStream<Uint8Array>,\n ) => Promise<T>;\n}\n\nexport const RegistryContext = createContext<DeferContextValue | undefined>(\n undefined,\n);\n\ninterface DeferredComponentProps {\n moduleID: string;\n}\n\nexport const DeferredComponent: React.FC<DeferredComponentProps> = ({\n moduleID,\n}) => {\n const deferContext = use(RegistryContext);\n const modulePath = getModulePathFor(moduleID);\n if (deferContext) {\n const entry = deferContext.registry.load(moduleID);\n if (!entry) {\n throw new Error(`Module entry not found for ID '${moduleID}'`);\n }\n return getRSCStreamFromRegistry(\n entry,\n deferContext.createFromReadableStream,\n );\n }\n const stream = getClientRSCStream(withBasePath(modulePath));\n return use(stream);\n};\n\nconst moduleToStreamMap = new Map<string, Promise<React.ReactNode>>();\n\nasync function getRSCStreamFromRegistry(\n entry: LoadedDeferEntry,\n createFromReadableStream: <T>(\n stream: ReadableStream<Uint8Array>,\n ) => Promise<T>,\n): Promise<React.ReactNode> {\n switch (entry.state.state) {\n case \"streaming\": {\n return createFromReadableStream<React.ReactNode>(entry.state.stream);\n }\n case \"ready\": {\n const data = await entry.drainPromise;\n const stream = new ReadableStream<Uint8Array>({\n start(controller) {\n const encoder = new TextEncoder();\n controller.enqueue(encoder.encode(data));\n controller.close();\n },\n });\n return createFromReadableStream<React.ReactNode>(stream);\n }\n case \"error\": {\n return Promise.reject(entry.state.error);\n }\n }\n}\n\nfunction getClientRSCStream(modulePath: string) {\n let stream = moduleToStreamMap.get(modulePath);\n if (!stream) {\n stream = createFromFetch<React.ReactNode>(fetch(modulePath));\n moduleToStreamMap.set(modulePath, stream);\n }\n return stream;\n}\n"],"mappings":";;;;;;AAcA,MAAa,kBAAkB,cAC7B,OACD;AAMD,MAAa,qBAAuD,EAClE,eACI;CACJ,MAAM,eAAe,IAAI,gBAAgB;CACzC,MAAM,aAAa,iBAAiB,SAAS;AAC7C,KAAI,cAAc;EAChB,MAAM,QAAQ,aAAa,SAAS,KAAK,SAAS;AAClD,MAAI,CAAC,MACH,OAAM,IAAI,MAAM,kCAAkC,SAAS,GAAG;AAEhE,SAAO,yBACL,OACA,aAAa,yBACd;;AAGH,QAAO,IADQ,mBAAmB,aAAa,WAAW,CAAC,CACzC;;AAGpB,MAAM,oCAAoB,IAAI,KAAuC;AAErE,eAAe,yBACb,OACA,0BAG0B;AAC1B,SAAQ,MAAM,MAAM,OAApB;EACE,KAAK,YACH,QAAO,yBAA0C,MAAM,MAAM,OAAO;EAEtE,KAAK,SAAS;GACZ,MAAM,OAAO,MAAM,MAAM;AAQzB,UAAO,yBAPQ,IAAI,eAA2B,EAC5C,MAAM,YAAY;IAChB,MAAM,UAAU,IAAI,aAAa;AACjC,eAAW,QAAQ,QAAQ,OAAO,KAAK,CAAC;AACxC,eAAW,OAAO;MAErB,CAAC,CACsD;;EAE1D,KAAK,QACH,QAAO,QAAQ,OAAO,MAAM,MAAM,MAAM;;;AAK9C,SAAS,mBAAmB,YAAoB;CAC9C,IAAI,SAAS,kBAAkB,IAAI,WAAW;AAC9C,KAAI,CAAC,QAAQ;AACX,WAAS,gBAAiC,MAAM,WAAW,CAAC;AAC5D,oBAAkB,IAAI,YAAY,OAAO;;AAE3C,QAAO"}
1
+ {"version":3,"file":"clientWrapper.mjs","names":[],"sources":["../../src/rsc-client/clientWrapper.tsx"],"sourcesContent":["import React from \"react\";\nimport { createFromFetch } from \"@vitejs/plugin-rsc/browser\";\nimport { getModulePathFor } from \"../rsc/rscModule\";\nimport { createContext, use } from \"react\";\nimport type { LoadedDeferEntry, DeferRegistry } from \"../rsc/defer\";\nimport { withBasePath } from \"../util/basePath\";\n\ninterface DeferContextValue {\n registry: DeferRegistry;\n createFromReadableStream: <T>(\n stream: ReadableStream<Uint8Array>,\n ) => Promise<T>;\n}\n\nexport const RegistryContext = createContext<DeferContextValue | undefined>(\n undefined,\n);\n\ninterface DeferredComponentProps {\n moduleID: string;\n}\n\nexport const DeferredComponent: React.FC<DeferredComponentProps> = ({\n moduleID,\n}) => {\n const deferContext = use(RegistryContext);\n const modulePath = getModulePathFor(moduleID);\n if (deferContext) {\n const entry = deferContext.registry.load(moduleID);\n if (!entry) {\n throw new Error(`Module entry not found for ID '${moduleID}'`);\n }\n return getRSCStreamFromRegistry(\n entry,\n deferContext.createFromReadableStream,\n );\n }\n const stream = getClientRSCStream(withBasePath(modulePath));\n return use(stream);\n};\n\nconst moduleToStreamMap = new Map<string, Promise<React.ReactNode>>();\n\nasync function getRSCStreamFromRegistry(\n entry: LoadedDeferEntry,\n createFromReadableStream: <T>(\n stream: ReadableStream<Uint8Array>,\n ) => Promise<T>,\n): Promise<React.ReactNode> {\n switch (entry.state.state) {\n case \"streaming\": {\n return createFromReadableStream<React.ReactNode>(entry.state.stream);\n }\n case \"ready\": {\n const data = await entry.drainPromise;\n const stream = new ReadableStream<Uint8Array>({\n start(controller) {\n const encoder = new TextEncoder();\n controller.enqueue(encoder.encode(data));\n controller.close();\n },\n });\n return createFromReadableStream<React.ReactNode>(stream);\n }\n case \"error\": {\n return Promise.reject(entry.state.error);\n }\n }\n}\n\nfunction getClientRSCStream(modulePath: string) {\n let stream = moduleToStreamMap.get(modulePath);\n if (!stream) {\n stream = createFromFetch<React.ReactNode>(fetch(modulePath));\n moduleToStreamMap.set(modulePath, stream);\n }\n return stream;\n}\n"],"mappings":";;;;;AAcA,MAAa,kBAAkB,cAC7B,KAAA,EACD;AAMD,MAAa,qBAAuD,EAClE,eACI;CACJ,MAAM,eAAe,IAAI,gBAAgB;CACzC,MAAM,aAAa,iBAAiB,SAAS;AAC7C,KAAI,cAAc;EAChB,MAAM,QAAQ,aAAa,SAAS,KAAK,SAAS;AAClD,MAAI,CAAC,MACH,OAAM,IAAI,MAAM,kCAAkC,SAAS,GAAG;AAEhE,SAAO,yBACL,OACA,aAAa,yBACd;;AAGH,QAAO,IADQ,mBAAmB,aAAa,WAAW,CAAC,CACzC;;AAGpB,MAAM,oCAAoB,IAAI,KAAuC;AAErE,eAAe,yBACb,OACA,0BAG0B;AAC1B,SAAQ,MAAM,MAAM,OAApB;EACE,KAAK,YACH,QAAO,yBAA0C,MAAM,MAAM,OAAO;EAEtE,KAAK,SAAS;GACZ,MAAM,OAAO,MAAM,MAAM;AAQzB,UAAO,yBAPQ,IAAI,eAA2B,EAC5C,MAAM,YAAY;IAChB,MAAM,UAAU,IAAI,aAAa;AACjC,eAAW,QAAQ,QAAQ,OAAO,KAAK,CAAC;AACxC,eAAW,OAAO;MAErB,CAAC,CACsD;;EAE1D,KAAK,QACH,QAAO,QAAQ,OAAO,MAAM,MAAM,MAAM;;;AAK9C,SAAS,mBAAmB,YAAoB;CAC9C,IAAI,SAAS,kBAAkB,IAAI,WAAW;AAC9C,KAAI,CAAC,QAAQ;AACX,WAAS,gBAAiC,MAAM,WAAW,CAAC;AAC5D,oBAAkB,IAAI,YAAY,OAAO;;AAE3C,QAAO"}
@@ -1,5 +1,3 @@
1
1
  "use client";
2
-
3
- import { DeferredComponent, RegistryContext } from "./clientWrapper.mjs";
4
-
5
- export { };
2
+ import "./clientWrapper.mjs";
3
+ export {};
@@ -8,7 +8,6 @@ import { renderToReadableStream } from "react-dom/server.edge";
8
8
  import { prerender } from "react-dom/static";
9
9
  import { injectRSCPayload } from "rsc-html-stream/server";
10
10
  import { preload } from "react-dom";
11
-
12
11
  //#region src/ssr/entry.tsx
13
12
  async function renderHTML(rscStream, options) {
14
13
  const [rscStream1, rscStream2] = rscStream.tee();
@@ -43,6 +42,7 @@ async function renderHTML(rscStream, options) {
43
42
  nonce: options?.nonce
44
43
  });
45
44
  } catch (e) {
45
+ if (options.build) throw e;
46
46
  status = 500;
47
47
  htmlStream = await renderToReadableStream(/* @__PURE__ */ jsx("html", { children: /* @__PURE__ */ jsx("body", { children: /* @__PURE__ */ jsx("noscript", { children: "Internal Server Error: SSR failed" }) }) }), {
48
48
  bootstrapScriptContent: `globalThis.__NO_HYDRATE=1;` + bootstrapScriptContent,
@@ -59,7 +59,7 @@ async function renderHTML(rscStream, options) {
59
59
  status
60
60
  };
61
61
  }
62
-
63
62
  //#endregion
64
63
  export { renderHTML };
64
+
65
65
  //# sourceMappingURL=entry.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"entry.mjs","names":[],"sources":["../../src/ssr/entry.tsx"],"sourcesContent":["import { createFromReadableStream } from \"@vitejs/plugin-rsc/ssr\";\nimport { use } from \"react\";\nimport { renderToReadableStream } from \"react-dom/server.edge\";\nimport { prerender } from \"react-dom/static\";\nimport { injectRSCPayload } from \"rsc-html-stream/server\";\nimport type { RscPayload } from \"../rsc/entry\";\nimport { appClientManifestVar } from \"../client/globals\";\nimport { rscPayloadPlaceholder } from \"../build/rscPath\";\nimport { preload } from \"react-dom\";\nimport type { DeferRegistry } from \"../rsc/defer\";\nimport { RegistryContext } from \"#rsc-client\";\n\nexport async function renderHTML(\n rscStream: ReadableStream<Uint8Array>,\n options: {\n appEntryMarker: string;\n build: boolean;\n ssr?: boolean;\n nonce?: string;\n deferRegistry?: DeferRegistry;\n clientRscStream?: ReadableStream<Uint8Array>;\n },\n): Promise<{ stream: ReadableStream<Uint8Array>; status?: number }> {\n const [rscStream1, rscStream2] = rscStream.tee();\n\n let payload: Promise<RscPayload> | undefined;\n function SsrRoot() {\n // Tip: calling `createFromReadableStream` inside a component\n // makes `preinit`/`preload` work properly.\n payload ??= createFromReadableStream<RscPayload>(rscStream1);\n if (options.build) {\n preload(rscPayloadPlaceholder, {\n crossOrigin: \"anonymous\",\n as: \"fetch\",\n });\n }\n return (\n <RegistryContext\n value={\n options.deferRegistry\n ? {\n registry: options.deferRegistry,\n createFromReadableStream,\n }\n : undefined\n }\n >\n {use(payload).root}\n </RegistryContext>\n );\n }\n\n let bootstrapScriptContent: string = \"\";\n if (options.build) {\n if (options.ssr) {\n // SSR on: no marker needed, client hydrates full document\n bootstrapScriptContent += `globalThis.${appClientManifestVar}={stream:\"${rscPayloadPlaceholder}\"};\\n`;\n } else {\n // SSR off: marker needed for client to find mount point\n bootstrapScriptContent += `globalThis.${appClientManifestVar}={marker:\"${options.appEntryMarker}\",stream:\"${rscPayloadPlaceholder}\"};\\n`;\n }\n }\n bootstrapScriptContent +=\n await import.meta.viteRsc.loadBootstrapScriptContent(\"index\");\n\n let htmlStream: ReadableStream<Uint8Array>;\n let status: number | undefined;\n try {\n if (options.build) {\n const { prelude, postponed } = await prerender(<SsrRoot />, {\n bootstrapScriptContent,\n });\n if (postponed !== null) {\n throw new Error(\"Unexpected postponed state during prerendering\");\n }\n\n htmlStream = prelude;\n } else {\n htmlStream = await renderToReadableStream(<SsrRoot />, {\n bootstrapScriptContent,\n nonce: options?.nonce,\n });\n }\n } catch (e) {\n // In this case, RSC payload is still sent to client and we let client render from scratch anyway.\n // This triggers the error boundary on client side.\n status = 500;\n htmlStream = await renderToReadableStream(\n <html>\n <body>\n <noscript>Internal Server Error: SSR failed</noscript>\n </body>\n </html>,\n {\n bootstrapScriptContent:\n `globalThis.__NO_HYDRATE=1;` + bootstrapScriptContent,\n nonce: options?.nonce,\n },\n );\n }\n\n let responseStream = htmlStream;\n\n // Inject RSC payload into HTML for client consumption.\n // In dev: always inject (client reads from inline stream).\n // In build+SSR: skip (HTML already has full content, client hydrates directly).\n // In build+no-SSR: skip (client fetches RSC from separate file).\n if (!options.build) {\n const streamToInject = options.clientRscStream ?? rscStream2;\n responseStream = responseStream.pipeThrough(\n injectRSCPayload(streamToInject, {\n nonce: options?.nonce,\n }),\n );\n }\n\n return { stream: responseStream, status };\n}\n"],"mappings":";;;;;;;;;;;;AAYA,eAAsB,WACpB,WACA,SAQkE;CAClE,MAAM,CAAC,YAAY,cAAc,UAAU,KAAK;CAEhD,IAAI;CACJ,SAAS,UAAU;AAGjB,cAAY,yBAAqC,WAAW;AAC5D,MAAI,QAAQ,MACV,SAAQ,uBAAuB;GAC7B,aAAa;GACb,IAAI;GACL,CAAC;AAEJ,SACE,oBAAC;GACC,OACE,QAAQ,gBACJ;IACE,UAAU,QAAQ;IAClB;IACD,GACD;aAGL,IAAI,QAAQ,CAAC;IACE;;CAItB,IAAI,yBAAiC;AACrC,KAAI,QAAQ,MACV,KAAI,QAAQ,IAEV,2BAA0B,cAAc,qBAAqB,YAAY,sBAAsB;KAG/F,2BAA0B,cAAc,qBAAqB,YAAY,QAAQ,eAAe,YAAY,sBAAsB;AAGtI,2BACE,MAAM,OAAO,KAAK,QAAQ,2BAA2B,QAAQ;CAE/D,IAAI;CACJ,IAAI;AACJ,KAAI;AACF,MAAI,QAAQ,OAAO;GACjB,MAAM,EAAE,SAAS,cAAc,MAAM,UAAU,oBAAC,YAAU,EAAE,EAC1D,wBACD,CAAC;AACF,OAAI,cAAc,KAChB,OAAM,IAAI,MAAM,iDAAiD;AAGnE,gBAAa;QAEb,cAAa,MAAM,uBAAuB,oBAAC,YAAU,EAAE;GACrD;GACA,OAAO,SAAS;GACjB,CAAC;UAEG,GAAG;AAGV,WAAS;AACT,eAAa,MAAM,uBACjB,oBAAC,oBACC,oBAAC,oBACC,oBAAC,wBAAS,sCAA4C,GACjD,GACF,EACP;GACE,wBACE,+BAA+B;GACjC,OAAO,SAAS;GACjB,CACF;;CAGH,IAAI,iBAAiB;AAMrB,KAAI,CAAC,QAAQ,OAAO;EAClB,MAAM,iBAAiB,QAAQ,mBAAmB;AAClD,mBAAiB,eAAe,YAC9B,iBAAiB,gBAAgB,EAC/B,OAAO,SAAS,OACjB,CAAC,CACH;;AAGH,QAAO;EAAE,QAAQ;EAAgB;EAAQ"}
1
+ {"version":3,"file":"entry.mjs","names":[],"sources":["../../src/ssr/entry.tsx"],"sourcesContent":["import { createFromReadableStream } from \"@vitejs/plugin-rsc/ssr\";\nimport { use } from \"react\";\nimport { renderToReadableStream } from \"react-dom/server.edge\";\nimport { prerender } from \"react-dom/static\";\nimport { injectRSCPayload } from \"rsc-html-stream/server\";\nimport type { RscPayload } from \"../rsc/entry\";\nimport { appClientManifestVar } from \"../client/globals\";\nimport { rscPayloadPlaceholder } from \"../build/rscPath\";\nimport { preload } from \"react-dom\";\nimport type { DeferRegistry } from \"../rsc/defer\";\nimport { RegistryContext } from \"#rsc-client\";\n\nexport async function renderHTML(\n rscStream: ReadableStream<Uint8Array>,\n options: {\n appEntryMarker: string;\n build: boolean;\n ssr?: boolean;\n nonce?: string;\n deferRegistry?: DeferRegistry;\n clientRscStream?: ReadableStream<Uint8Array>;\n },\n): Promise<{ stream: ReadableStream<Uint8Array>; status?: number }> {\n const [rscStream1, rscStream2] = rscStream.tee();\n\n let payload: Promise<RscPayload> | undefined;\n function SsrRoot() {\n // Tip: calling `createFromReadableStream` inside a component\n // makes `preinit`/`preload` work properly.\n payload ??= createFromReadableStream<RscPayload>(rscStream1);\n if (options.build) {\n preload(rscPayloadPlaceholder, {\n crossOrigin: \"anonymous\",\n as: \"fetch\",\n });\n }\n return (\n <RegistryContext\n value={\n options.deferRegistry\n ? {\n registry: options.deferRegistry,\n createFromReadableStream,\n }\n : undefined\n }\n >\n {use(payload).root}\n </RegistryContext>\n );\n }\n\n let bootstrapScriptContent: string = \"\";\n if (options.build) {\n if (options.ssr) {\n // SSR on: no marker needed, client hydrates full document\n bootstrapScriptContent += `globalThis.${appClientManifestVar}={stream:\"${rscPayloadPlaceholder}\"};\\n`;\n } else {\n // SSR off: marker needed for client to find mount point\n bootstrapScriptContent += `globalThis.${appClientManifestVar}={marker:\"${options.appEntryMarker}\",stream:\"${rscPayloadPlaceholder}\"};\\n`;\n }\n }\n bootstrapScriptContent +=\n await import.meta.viteRsc.loadBootstrapScriptContent(\"index\");\n\n let htmlStream: ReadableStream<Uint8Array>;\n let status: number | undefined;\n try {\n if (options.build) {\n const { prelude, postponed } = await prerender(<SsrRoot />, {\n bootstrapScriptContent,\n });\n if (postponed !== null) {\n throw new Error(\"Unexpected postponed state during prerendering\");\n }\n\n htmlStream = prelude;\n } else {\n htmlStream = await renderToReadableStream(<SsrRoot />, {\n bootstrapScriptContent,\n nonce: options?.nonce,\n });\n }\n } catch (e) {\n if (options.build) {\n // In build mode, abort the build so the error is not silently swallowed.\n throw e;\n }\n // In dev mode, RSC payload is still sent to client and we let client render from scratch anyway.\n // This triggers the error boundary on client side.\n status = 500;\n htmlStream = await renderToReadableStream(\n <html>\n <body>\n <noscript>Internal Server Error: SSR failed</noscript>\n </body>\n </html>,\n {\n bootstrapScriptContent:\n `globalThis.__NO_HYDRATE=1;` + bootstrapScriptContent,\n nonce: options?.nonce,\n },\n );\n }\n\n let responseStream = htmlStream;\n\n // Inject RSC payload into HTML for client consumption.\n // In dev: always inject (client reads from inline stream).\n // In build+SSR: skip (HTML already has full content, client hydrates directly).\n // In build+no-SSR: skip (client fetches RSC from separate file).\n if (!options.build) {\n const streamToInject = options.clientRscStream ?? rscStream2;\n responseStream = responseStream.pipeThrough(\n injectRSCPayload(streamToInject, {\n nonce: options?.nonce,\n }),\n );\n }\n\n return { stream: responseStream, status };\n}\n"],"mappings":";;;;;;;;;;;AAYA,eAAsB,WACpB,WACA,SAQkE;CAClE,MAAM,CAAC,YAAY,cAAc,UAAU,KAAK;CAEhD,IAAI;CACJ,SAAS,UAAU;AAGjB,cAAY,yBAAqC,WAAW;AAC5D,MAAI,QAAQ,MACV,SAAQ,uBAAuB;GAC7B,aAAa;GACb,IAAI;GACL,CAAC;AAEJ,SACE,oBAAC,iBAAD;GACE,OACE,QAAQ,gBACJ;IACE,UAAU,QAAQ;IAClB;IACD,GACD,KAAA;aAGL,IAAI,QAAQ,CAAC;GACE,CAAA;;CAItB,IAAI,yBAAiC;AACrC,KAAI,QAAQ,MACV,KAAI,QAAQ,IAEV,2BAA0B,cAAc,qBAAqB,YAAY,sBAAsB;KAG/F,2BAA0B,cAAc,qBAAqB,YAAY,QAAQ,eAAe,YAAY,sBAAsB;AAGtI,2BACE,MAAM,OAAO,KAAK,QAAQ,2BAA2B,QAAQ;CAE/D,IAAI;CACJ,IAAI;AACJ,KAAI;AACF,MAAI,QAAQ,OAAO;GACjB,MAAM,EAAE,SAAS,cAAc,MAAM,UAAU,oBAAC,SAAD,EAAW,CAAA,EAAE,EAC1D,wBACD,CAAC;AACF,OAAI,cAAc,KAChB,OAAM,IAAI,MAAM,iDAAiD;AAGnE,gBAAa;QAEb,cAAa,MAAM,uBAAuB,oBAAC,SAAD,EAAW,CAAA,EAAE;GACrD;GACA,OAAO,SAAS;GACjB,CAAC;UAEG,GAAG;AACV,MAAI,QAAQ,MAEV,OAAM;AAIR,WAAS;AACT,eAAa,MAAM,uBACjB,oBAAC,QAAD,EAAA,UACE,oBAAC,QAAD,EAAA,UACE,oBAAC,YAAD,EAAA,UAAU,qCAA4C,CAAA,EACjD,CAAA,EACF,CAAA,EACP;GACE,wBACE,+BAA+B;GACjC,OAAO,SAAS;GACjB,CACF;;CAGH,IAAI,iBAAiB;AAMrB,KAAI,CAAC,QAAQ,OAAO;EAClB,MAAM,iBAAiB,QAAQ,mBAAmB;AAClD,mBAAiB,eAAe,YAC9B,iBAAiB,gBAAgB,EAC/B,OAAO,SAAS,OACjB,CAAC,CACH;;AAGH,QAAO;EAAE,QAAQ;EAAgB;EAAQ"}
@@ -22,7 +22,7 @@ function withBasePath(path) {
22
22
  if (base === "/") return path;
23
23
  return (base.endsWith("/") ? base.slice(0, -1) : base) + path;
24
24
  }
25
-
26
25
  //#endregion
27
26
  export { stripBasePath, withBasePath };
27
+
28
28
  //# sourceMappingURL=basePath.mjs.map
@@ -6,7 +6,7 @@ async function drainStream(stream) {
6
6
  chunks.push(decoder.decode());
7
7
  return chunks.join("");
8
8
  }
9
-
10
9
  //#endregion
11
10
  export { drainStream };
11
+
12
12
  //# sourceMappingURL=drainStream.mjs.map
@@ -16,7 +16,7 @@ function urlPathToFileCandidates(urlPath) {
16
16
  `${stripped}/index.htm`
17
17
  ];
18
18
  }
19
-
20
19
  //#endregion
21
20
  export { urlPathToFileCandidates };
21
+
22
22
  //# sourceMappingURL=urlPath.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funstack/static",
3
- "version": "0.0.10",
3
+ "version": "1.0.0",
4
4
  "description": "FUNSTACK static library",
5
5
  "type": "module",
6
6
  "repository": {
@@ -47,23 +47,23 @@
47
47
  "license": "MIT",
48
48
  "devDependencies": {
49
49
  "@playwright/test": "^1.58.2",
50
- "@types/node": "^25.2.3",
50
+ "@types/node": "^25.3.5",
51
51
  "@types/react": "^19.2.14",
52
52
  "@types/react-dom": "^19.2.3",
53
53
  "jsdom": "^28.1.0",
54
54
  "react": "^19.2.4",
55
55
  "react-dom": "^19.2.4",
56
- "tsdown": "^0.20.3",
56
+ "tsdown": "^0.21.0",
57
57
  "typescript": "^5.9.3",
58
58
  "vite": "^7.3.1",
59
59
  "vitest": "^4.0.18"
60
60
  },
61
61
  "dependencies": {
62
62
  "@funstack/skill-installer": "^1.0.0",
63
- "@vitejs/plugin-rsc": "^0.5.19",
63
+ "@vitejs/plugin-rsc": "^0.5.21",
64
64
  "react-error-boundary": "^6.1.1",
65
65
  "rsc-html-stream": "^0.0.7",
66
- "srvx": "^0.11.4"
66
+ "srvx": "^0.11.8"
67
67
  },
68
68
  "peerDependencies": {
69
69
  "react": "^19.2.3",