@funstack/static 0.0.10 → 1.1.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 (70) 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 +7 -8
  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 +3 -4
  10. package/dist/build/rscPath.mjs.map +1 -1
  11. package/dist/build/rscProcessor.mjs +4 -4
  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 +10 -6
  22. package/dist/docs/MigratingFromViteSPA.md +5 -5
  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 +26 -7
  27. package/dist/docs/index.md +5 -2
  28. package/dist/docs/learn/DeferAndActivity.md +3 -3
  29. package/dist/docs/learn/HowItWorks.md +3 -3
  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.d.mts +14 -0
  44. package/dist/plugin/index.d.mts.map +1 -1
  45. package/dist/plugin/index.mjs +6 -5
  46. package/dist/plugin/index.mjs.map +1 -1
  47. package/dist/plugin/server.mjs +1 -2
  48. package/dist/plugin/server.mjs.map +1 -1
  49. package/dist/rsc/defer.d.mts.map +1 -1
  50. package/dist/rsc/defer.mjs +3 -3
  51. package/dist/rsc/defer.mjs.map +1 -1
  52. package/dist/rsc/entry.mjs +4 -5
  53. package/dist/rsc/entry.mjs.map +1 -1
  54. package/dist/rsc/marker.mjs +1 -1
  55. package/dist/rsc/request.mjs +1 -1
  56. package/dist/rsc/resolveEntry.mjs +1 -2
  57. package/dist/rsc/resolveEntry.mjs.map +1 -1
  58. package/dist/rsc/rscModule.mjs +8 -8
  59. package/dist/rsc/rscModule.mjs.map +1 -1
  60. package/dist/rsc-client/clientWrapper.mjs +2 -3
  61. package/dist/rsc-client/clientWrapper.mjs.map +1 -1
  62. package/dist/rsc-client/entry.d.mts +1 -0
  63. package/dist/rsc-client/entry.mjs +2 -4
  64. package/dist/ssr/entry.mjs +2 -2
  65. package/dist/ssr/entry.mjs.map +1 -1
  66. package/dist/util/basePath.mjs +1 -1
  67. package/dist/util/drainStream.mjs +1 -1
  68. package/dist/util/urlPath.mjs +1 -1
  69. package/package.json +5 -5
  70. package/skills/funstack-static-knowledge/SKILL.md +1 -1
@@ -51,7 +51,7 @@ export default defineConfig({
51
51
  });
52
52
  ```
53
53
 
54
- See [Multiple Entrypoints](/funstack-static/learn/multiple-entrypoints) for a full guide.
54
+ See [Multiple Entrypoints](/advanced/multiple-entrypoints) for a full guide.
55
55
 
56
56
  ## Options
57
57
 
@@ -158,7 +158,7 @@ export default function getEntries(): EntryDefinition[] {
158
158
  }
159
159
  ```
160
160
 
161
- See [Multiple Entrypoints](/funstack-static/learn/multiple-entrypoints) for details on the `EntryDefinition` type and advanced usage patterns like async generators.
161
+ See [Multiple Entrypoints](/advanced/multiple-entrypoints) for details on the `EntryDefinition` type and advanced usage patterns like async generators.
162
162
 
163
163
  ### publicOutDir (optional)
164
164
 
@@ -226,13 +226,32 @@ Sentry.init({
226
226
 
227
227
  **Note:** Errors in the client init module will propagate normally and prevent the app from rendering.
228
228
 
229
+ ### rscPayloadDir (optional)
230
+
231
+ **Type:** `string`
232
+ **Default:** `"fun:rsc-payload"`
233
+
234
+ Directory name used for RSC payload files in the build output. The final file paths follow the pattern `/funstack__/{rscPayloadDir}/{hash}.txt`.
235
+
236
+ Change this if your hosting platform has issues with the default directory name. For example, Cloudflare Workers redirects URLs containing colons to percent-encoded equivalents, adding an extra round trip.
237
+
238
+ **Important:** The value is used as a marker for string replacement during the build process. Choose a value that is unique enough that it does not appear in your application's source code. The default value `"fun:rsc-payload"` is designed to be unlikely to collide with user code.
239
+
240
+ ```typescript
241
+ funstackStatic({
242
+ root: "./src/root.tsx",
243
+ app: "./src/App.tsx",
244
+ rscPayloadDir: "fun-rsc-payload", // Avoid colons for Cloudflare Workers
245
+ });
246
+ ```
247
+
229
248
  ## Full Example
230
249
 
231
250
  ### Single-Entry
232
251
 
233
252
  ```typescript
234
253
  // vite.config.ts
235
- import { funstackStatic } from "@funstack/static";
254
+ import funstackStatic from "@funstack/static";
236
255
  import { defineConfig } from "vite";
237
256
 
238
257
  export default defineConfig({
@@ -274,7 +293,7 @@ You can use the same Vite commands you would use in a normal Vite project:
274
293
 
275
294
  ## See Also
276
295
 
277
- - [Getting Started](/funstack-static/getting-started) - Quick start guide
278
- - [Multiple Entrypoints](/funstack-static/learn/multiple-entrypoints) - Multi-page static site generation
279
- - [defer()](/funstack-static/api/defer) - Deferred rendering for streaming
280
- - [React Server Components](/funstack-static/learn/rsc) - Understanding RSC
296
+ - [Getting Started](/getting-started) - Quick start guide
297
+ - [Multiple Entrypoints](/advanced/multiple-entrypoints) - Multi-page static site generation
298
+ - [defer()](/api/defer) - Deferred rendering for streaming
299
+ - [React Server Components](/learn/rsc) - Understanding RSC
@@ -8,6 +8,11 @@ A Vite plugin for building static sites with React Server Components.
8
8
  - [Getting Started](./GettingStarted.md) - Welcome to **FUNSTACK Static**! Build high-performance Single Page Applications powered by React Server Components - no server required at runtime.
9
9
  - [Migrating from Vite SPA](./MigratingFromViteSPA.md) - Already have a Vite-powered React SPA? This guide walks you through migrating to FUNSTACK Static to unlock React Server Components and improved performance.
10
10
 
11
+ ### Advanced
12
+
13
+ - [Multiple Entrypoints (SSG)](./advanced/MultipleEntrypoints.md) - By default, FUNSTACK Static produces a single `index.html` from one `root` + `app` pair. The **multiple entries** feature lets you produce multiple HTML pages from a single project, targeting SSG (Static Site Generation) use cases where a site has distinct pages like `index.html`, `about.html`, and `blog/post-1.html`.
14
+ - [Server-Side Rendering](./advanced/SSR.md) - In FUNSTACK Static, **Server-Side Rendering (SSR)** means a build-time process that pre-renders your React components (including client components) to HTML. This can make the initial paint faster.
15
+
11
16
  ### API
12
17
 
13
18
  - [defer()](./api/Defer.md) - The `defer()` function enables deferred rendering for React Server Components, reducing initial data load.
@@ -19,7 +24,5 @@ A Vite plugin for building static sites with React Server Components.
19
24
  - [Prefetching with defer() and Activity](./learn/DeferAndActivity.md) - When using `defer()` to split RSC payloads, content is fetched on-demand as it renders. But what if you want to start fetching _before_ the user actually needs it? By combining `defer()` with React 19's `<Activity>` component, you can prefetch deferred payloads in the background so they're ready instantly when revealed.
20
25
  - [How It Works](./learn/HowItWorks.md) - FUNSTACK Static is a React framework that leverages React Server Components (RSC) to build a fully static Single Page Application. The result is a set of files that can be deployed to **any static file hosting service** - no server required at runtime.
21
26
  - [Using lazy() in Server Components](./learn/LazyServerComponents.md) - React's `lazy()` API is typically associated with client-side code splitting. However, it can also be used in server environments to reduce the initial response time of the development server by deferring the work needed to compute your application.
22
- - [Multiple Entrypoints](./learn/MultipleEntrypoints.md) - By default, FUNSTACK Static produces a single `index.html` from one `root` + `app` pair. The **multiple entries** feature lets you produce multiple HTML pages from a single project, targeting SSG (Static Site Generation) use cases where a site has distinct pages like `index.html`, `about.html`, and `blog/post-1.html`.
23
27
  - [Optimizing RSC Payloads](./learn/OptimizingPayloads.md) - FUNSTACK Static uses React Server Components (RSC) to pre-render your application at build time. By default, all content is bundled into a single RSC payload. This page explains how to split that payload into smaller chunks for better loading performance.
24
28
  - [React Server Components](./learn/RSC.md) - [React Server Components (RSC)](https://react.dev/reference/rsc/server-components) are a new paradigm for building React applications where components can run on the server (or at build time) rather than in the browser.
25
- - [Server-Side Rendering](./learn/SSR.md) - In FUNSTACK Static, **Server-Side Rendering (SSR)** means a build-time process that pre-renders your React components (including client components) to HTML. This can make the initial paint faster.
@@ -171,6 +171,6 @@ Avoid this pattern when:
171
171
 
172
172
  ## See Also
173
173
 
174
- - [Optimizing RSC Payloads](/funstack-static/learn/optimizing-payloads) - Using `defer()` to split RSC payloads
175
- - [defer()](/funstack-static/api/defer) - API reference with full signature and technical details
176
- - [React Server Components](/funstack-static/learn/rsc) - Understanding RSC fundamentals
174
+ - [Optimizing RSC Payloads](/learn/optimizing-payloads) - Using `defer()` to split RSC payloads
175
+ - [defer()](/api/defer) - API reference with full signature and technical details
176
+ - [React Server Components](/learn/rsc) - Understanding RSC fundamentals
@@ -50,7 +50,7 @@ dist/public
50
50
  └── index.html
51
51
  ```
52
52
 
53
- The RSC payload files under `funstack__` are loaded by the client-side code to bootstrap the application with server-rendered content.
53
+ The RSC payload files under `funstack__` are loaded by the client-side code to bootstrap the application with server-rendered content. The `fun:rsc-payload` directory name is [configurable](/api/funstack-static#rscpayloaddir-optional) via the `rscPayloadDir` option.
54
54
 
55
55
  This can been seen as an **optimized version of traditional client-only SPAs**, where the entire application is bundled into JavaScript files. By using RSC, some of the rendering work is offloaded to the build time, resulting in smaller JavaScript bundles combined with RSC payloads that require less client-side processing (parsing is easier, no JavaScript execution needed).
56
56
 
@@ -105,5 +105,5 @@ Note that in both modes, React Server Components are still used - the `ssr` opti
105
105
 
106
106
  ## See Also
107
107
 
108
- - [React Server Components](/funstack-static/learn/rsc) - Understanding RSC in depth
109
- - [Getting Started](/funstack-static/getting-started) - Set up your first project
108
+ - [React Server Components](/learn/rsc) - Understanding RSC in depth
109
+ - [Getting Started](/getting-started) - Set up your first project
@@ -115,6 +115,6 @@ For small applications with few routes, the overhead of `lazy()` may not be wort
115
115
 
116
116
  ## See Also
117
117
 
118
- - [Optimizing RSC Payloads](/funstack-static/learn/optimizing-payloads) - Using `defer()` to split RSC payloads
119
- - [How It Works](/funstack-static/learn/how-it-works) - Overall FUNSTACK Static architecture
120
- - [defer()](/funstack-static/api/defer) - API reference for the defer function
118
+ - [Optimizing RSC Payloads](/learn/optimizing-payloads) - Using `defer()` to split RSC payloads
119
+ - [How It Works](/learn/how-it-works) - Overall FUNSTACK Static architecture
120
+ - [defer()](/api/defer) - API reference for the defer function
@@ -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"}
@@ -22,6 +22,20 @@ interface FunstackStaticBaseOptions {
22
22
  * The module is imported for its side effects only (no exports needed).
23
23
  */
24
24
  clientInit?: string;
25
+ /**
26
+ * Directory name used for RSC payload files in the build output.
27
+ * The final path will be `/funstack__/{rscPayloadDir}/{hash}.txt`.
28
+ *
29
+ * Change this if your hosting platform has issues with the default
30
+ * directory name (e.g. Cloudflare Workers redirects URLs containing colons).
31
+ *
32
+ * The value is used as a marker for string replacement during the build
33
+ * process, so it should be unique enough that it does not appear in your
34
+ * application's source code.
35
+ *
36
+ * @default "fun:rsc-payload"
37
+ */
38
+ rscPayloadDir?: string;
25
39
  }
26
40
  interface SingleEntryOptions {
27
41
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/plugin/index.ts"],"mappings":";;;UAMU,yBAAA;;AALyB;;;;EAWjC,YAAA;EAQA;;;;AAMU;;;EANV,GAAA;EAeA;;;;;EATA,UAAA;AAAA;AAAA,UAGQ,kBAAA;EAesB;;;;;EAT9B,IAAA;EAmBU;;;;EAdV,GAAA;EACA,OAAA;AAAA;AAAA,UAGQ,sBAAA;EACR,IAAA;EACA,GAAA;EASsB;;;AAAwB;EAJ9C,OAAA;AAAA;AAAA,KAGU,qBAAA,GAAwB,yBAAA,IACjC,kBAAA,GAAqB,sBAAA;AAAA,iBAEA,cAAA,CACtB,OAAA,EAAS,qBAAA,IACP,MAAA,GAAS,MAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/plugin/index.ts"],"mappings":";;;UAOU,yBAAA;;AANyB;;;;EAYjC,YAAA;EAQA;;;;;AAoBa;;EApBb,GAAA;EAuB0B;;;;;EAjB1B,UAAA;EAgCQ;;;;;;;;;AAUV;;;;EA5BE,aAAA;AAAA;AAAA,UAGQ,kBAAA;EA0BoC;;;;;EApB5C,IAAA;EAsBsB;;;;EAjBtB,GAAA;EACA,OAAA;AAAA;AAAA,UAGQ,sBAAA;EACR,IAAA;EACA,GAAA;EAaE;;;;EARF,OAAA;AAAA;AAAA,KAGU,qBAAA,GAAwB,yBAAA,IACjC,kBAAA,GAAqB,sBAAA;AAAA,iBAEA,cAAA,CACtB,OAAA,EAAS,qBAAA,IACP,MAAA,GAAS,MAAA"}
@@ -1,11 +1,12 @@
1
+ import { defaultRscPayloadDir } from "../rsc/rscModule.mjs";
1
2
  import { buildApp } from "../build/buildApp.mjs";
2
3
  import { serverPlugin } from "./server.mjs";
3
4
  import path from "node:path";
4
5
  import rsc from "@vitejs/plugin-rsc";
5
-
6
6
  //#region src/plugin/index.ts
7
7
  function funstackStatic(options) {
8
- const { publicOutDir = "dist/public", ssr = false, clientInit } = options;
8
+ const { publicOutDir = "dist/public", ssr = false, clientInit, rscPayloadDir = defaultRscPayloadDir } = options;
9
+ if (!rscPayloadDir || rscPayloadDir.includes("/") || rscPayloadDir.includes("\\") || rscPayloadDir === ".." || rscPayloadDir === ".") throw new Error(`[funstack] Invalid rscPayloadDir: "${rscPayloadDir}". Must be a non-empty single path segment without slashes.`);
9
10
  let resolvedEntriesModule = "__uninitialized__";
10
11
  let resolvedClientInitEntry;
11
12
  const isMultiEntry = "entries" in options && options.entries !== void 0;
@@ -68,7 +69,7 @@ function funstackStatic(options) {
68
69
  `}`
69
70
  ].join("\n");
70
71
  }
71
- if (id === "\0virtual:funstack/config") return `export const ssr = ${JSON.stringify(ssr)};`;
72
+ if (id === "\0virtual:funstack/config") return [`export const ssr = ${JSON.stringify(ssr)};`, `export const rscPayloadDir = ${JSON.stringify(rscPayloadDir)};`].join("\n");
72
73
  if (id === "\0virtual:funstack/client-init") {
73
74
  if (resolvedClientInitEntry) return `import "${resolvedClientInitEntry}";`;
74
75
  return "";
@@ -78,12 +79,12 @@ function funstackStatic(options) {
78
79
  {
79
80
  name: "@funstack/static:build",
80
81
  async buildApp(builder) {
81
- await buildApp(builder, this);
82
+ await buildApp(builder, this, { rscPayloadDir });
82
83
  }
83
84
  }
84
85
  ];
85
86
  }
86
-
87
87
  //#endregion
88
88
  export { funstackStatic as default };
89
+
89
90
  //# 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\";\nimport { defaultRscPayloadDir } from \"../rsc/rscModule\";\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 * Directory name used for RSC payload files in the build output.\n * The final path will be `/funstack__/{rscPayloadDir}/{hash}.txt`.\n *\n * Change this if your hosting platform has issues with the default\n * directory name (e.g. Cloudflare Workers redirects URLs containing colons).\n *\n * The value is used as a marker for string replacement during the build\n * process, so it should be unique enough that it does not appear in your\n * application's source code.\n *\n * @default \"fun:rsc-payload\"\n */\n rscPayloadDir?: 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 {\n publicOutDir = \"dist/public\",\n ssr = false,\n clientInit,\n rscPayloadDir = defaultRscPayloadDir,\n } = options;\n\n // Validate rscPayloadDir to prevent path traversal or invalid segments\n if (\n !rscPayloadDir ||\n rscPayloadDir.includes(\"/\") ||\n rscPayloadDir.includes(\"\\\\\") ||\n rscPayloadDir === \"..\" ||\n rscPayloadDir === \".\"\n ) {\n throw new Error(\n `[funstack] Invalid rscPayloadDir: \"${rscPayloadDir}\". Must be a non-empty single path segment without slashes.`,\n );\n }\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 [\n `export const ssr = ${JSON.stringify(ssr)};`,\n `export const rscPayloadDir = ${JSON.stringify(rscPayloadDir)};`,\n ].join(\"\\n\");\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, { rscPayloadDir });\n },\n },\n ];\n}\n"],"mappings":";;;;;;AAwEA,SAAwB,eACtB,SACuB;CACvB,MAAM,EACJ,eAAe,eACf,MAAM,OACN,YACA,gBAAgB,yBACd;AAGJ,KACE,CAAC,iBACD,cAAc,SAAS,IAAI,IAC3B,cAAc,SAAS,KAAK,IAC5B,kBAAkB,QAClB,kBAAkB,IAElB,OAAM,IAAI,MACR,sCAAsC,cAAc,6DACrD;CAGH,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,CACL,sBAAsB,KAAK,UAAU,IAAI,CAAC,IAC1C,gCAAgC,KAAK,UAAU,cAAc,CAAC,GAC/D,CAAC,KAAK,KAAK;AAEd,QAAI,OAAO,kCAAkC;AAC3C,SAAI,wBACF,QAAO,WAAW,wBAAwB;AAE5C,YAAO;;;GAGZ;EACD;GACE,MAAM;GACN,MAAM,SAAS,SAAS;AACtB,UAAM,SAAS,SAAS,MAAM,EAAE,eAAe,CAAC;;GAEnD;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"}
@@ -1 +1 @@
1
- {"version":3,"file":"defer.d.mts","names":[],"sources":["../../src/rsc/defer.tsx"],"mappings":";;;UAMiB,UAAA;EACf,KAAA,EAAO,eAAA;EACP,IAAA;EACA,YAAA,GAAe,OAAA;AAAA;;;;UAMA,YAAA;EANf;;;;AAMF;EAME,IAAA;AAAA;AAAA,UAGe,gBAAA,SAAyB,UAAA;EACxC,KAAA,EAAO,OAAA,CAAQ,eAAA;IAAmB,KAAA;EAAA;EAClC,YAAA,EAAc,OAAA;AAAA;AAAA,KAGX,eAAA;EAEC,KAAA;EACA,OAAA,EAAS,YAAA;AAAA;EAGT,KAAA;EACA,MAAA,EAAQ,cAAA,CAAe,UAAA;AAAA;EAGvB,KAAA;AAAA;EAGA,KAAA;EACA,KAAA;AAAA;AAAA,cAeO,aAAA;EAAA;EAGX,QAAA,CAAS,OAAA,EAAS,YAAA,EAAc,EAAA,UAAY,IAAA;EAI5C,IAAA,CAAK,EAAA,WAAa,gBAAA;EAkClB,GAAA,CAAI,EAAA;EAnES;;;;EA2EN,OAAA,CAAA,GAAO,cAAA;;;;;;;;;;;;AAjDhB;;;;iBAuHgB,KAAA,CACd,OAAA,EAAS,YAAA,EACT,OAAA,GAAU,YAAA,GACT,SAAA"}
1
+ {"version":3,"file":"defer.d.mts","names":[],"sources":["../../src/rsc/defer.tsx"],"mappings":";;;UAOiB,UAAA;EACf,KAAA,EAAO,eAAA;EACP,IAAA;EACA,YAAA,GAAe,OAAA;AAAA;;;;UAMA,YAAA;EANf;;;;AAMF;EAME,IAAA;AAAA;AAAA,UAGe,gBAAA,SAAyB,UAAA;EACxC,KAAA,EAAO,OAAA,CAAQ,eAAA;IAAmB,KAAA;EAAA;EAClC,YAAA,EAAc,OAAA;AAAA;AAAA,KAGX,eAAA;EAEC,KAAA;EACA,OAAA,EAAS,YAAA;AAAA;EAGT,KAAA;EACA,MAAA,EAAQ,cAAA,CAAe,UAAA;AAAA;EAGvB,KAAA;AAAA;EAGA,KAAA;EACA,KAAA;AAAA;AAAA,cAeO,aAAA;EAAA;EAGX,QAAA,CAAS,OAAA,EAAS,YAAA,EAAc,EAAA,UAAY,IAAA;EAI5C,IAAA,CAAK,EAAA,WAAa,gBAAA;EAkClB,GAAA,CAAI,EAAA;EAnES;;;;EA2EN,OAAA,CAAA,GAAO,cAAA;;;;;;;;;;;;AAjDhB;;;;iBAuHgB,KAAA,CACd,OAAA,EAAS,YAAA,EACT,OAAA,GAAU,YAAA,GACT,SAAA"}
@@ -1,9 +1,9 @@
1
1
  import { getPayloadIDFor } from "./rscModule.mjs";
2
2
  import { drainStream } from "../util/drainStream.mjs";
3
3
  import { jsx } from "react/jsx-runtime";
4
+ import { rscPayloadDir } from "virtual:funstack/config";
4
5
  import { renderToReadableStream } from "@vitejs/plugin-rsc/react/rsc";
5
6
  import { DeferredComponent } from "#rsc-client";
6
-
7
7
  //#region src/rsc/defer.tsx
8
8
  /**
9
9
  * Sanitizes a name for use in file paths.
@@ -111,11 +111,11 @@ const deferRegistry = new DeferRegistry();
111
111
  function defer(element, options) {
112
112
  const name = options?.name;
113
113
  const sanitizedName = name ? sanitizeName(name) : void 0;
114
- const id = getPayloadIDFor(sanitizedName ? `${sanitizedName}-${crypto.randomUUID()}` : crypto.randomUUID());
114
+ const id = getPayloadIDFor(sanitizedName ? `${sanitizedName}-${crypto.randomUUID()}` : crypto.randomUUID(), rscPayloadDir);
115
115
  deferRegistry.register(element, id, name);
116
116
  return /* @__PURE__ */ jsx(DeferredComponent, { moduleID: id });
117
117
  }
118
-
119
118
  //#endregion
120
119
  export { defer, deferRegistry };
120
+
121
121
  //# 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\";\nimport { rscPayloadDir } from \"virtual:funstack/config\";\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, rscPayloadDir);\n deferRegistry.register(element, id, name);\n\n return <DeferredComponent moduleID={id} />;\n}\n"],"mappings":";;;;;;;;;;;AAmDA,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,EACW,cAAc;AAChD,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