@funstack/static 0.0.9 → 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.
- package/README.md +6 -4
- package/dist/bin/skill-installer.mjs +5 -4
- package/dist/bin/skill-installer.mjs.map +1 -1
- package/dist/build/buildApp.mjs +1 -2
- package/dist/build/buildApp.mjs.map +1 -1
- package/dist/build/contentHash.mjs +1 -1
- package/dist/build/dependencyGraph.mjs +1 -1
- package/dist/build/dependencyGraph.mjs.map +1 -1
- package/dist/build/rscPath.mjs +1 -2
- package/dist/build/rscPath.mjs.map +1 -1
- package/dist/build/rscProcessor.mjs +1 -2
- package/dist/build/rscProcessor.mjs.map +1 -1
- package/dist/build/validateEntryPath.mjs +1 -1
- package/dist/client/entry.d.mts +1 -1
- package/dist/client/entry.mjs +2 -3
- package/dist/client/entry.mjs.map +1 -1
- package/dist/client/error-boundary.mjs +2 -4
- package/dist/client/error-boundary.mjs.map +1 -1
- package/dist/client/globals.mjs +2 -4
- package/dist/client/globals.mjs.map +1 -1
- package/dist/docs/GettingStarted.md +11 -5
- package/dist/docs/MigratingFromViteSPA.md +4 -4
- package/dist/docs/{learn → advanced}/MultipleEntrypoints.md +14 -42
- package/dist/docs/{learn → advanced}/SSR.md +3 -3
- package/dist/docs/api/EntryDefinition.md +2 -2
- package/dist/docs/api/FunstackStatic.md +6 -6
- package/dist/docs/index.md +5 -2
- package/dist/docs/learn/DeferAndActivity.md +3 -3
- package/dist/docs/learn/HowItWorks.md +2 -2
- package/dist/docs/learn/LazyServerComponents.md +3 -3
- package/dist/docs/learn/OptimizingPayloads.md +4 -4
- package/dist/docs/learn/RSC.md +3 -3
- package/dist/entries/client.d.mts +1 -1
- package/dist/entries/client.mjs +1 -2
- package/dist/entries/rsc-client.d.mts +0 -1
- package/dist/entries/rsc-client.mjs +1 -3
- package/dist/entries/rsc.mjs +1 -2
- package/dist/entries/server.mjs +1 -2
- package/dist/entries/ssr.mjs +1 -2
- package/dist/entryDefinition.mjs +1 -1
- package/dist/index.mjs +1 -2
- package/dist/plugin/getRSCEntryPoint.mjs +1 -1
- package/dist/plugin/getRSCEntryPoint.mjs.map +1 -1
- package/dist/plugin/index.mjs +1 -2
- package/dist/plugin/index.mjs.map +1 -1
- package/dist/plugin/server.mjs +1 -2
- package/dist/plugin/server.mjs.map +1 -1
- package/dist/rsc/defer.d.mts +0 -1
- package/dist/rsc/defer.d.mts.map +1 -1
- package/dist/rsc/defer.mjs +3 -7
- package/dist/rsc/defer.mjs.map +1 -1
- package/dist/rsc/entry.mjs +5 -6
- package/dist/rsc/entry.mjs.map +1 -1
- package/dist/rsc/marker.mjs +1 -1
- package/dist/rsc/request.mjs +1 -1
- package/dist/rsc/resolveEntry.mjs +1 -2
- package/dist/rsc/resolveEntry.mjs.map +1 -1
- package/dist/rsc/rscModule.mjs +1 -1
- package/dist/rsc-client/clientWrapper.d.mts +5 -1
- package/dist/rsc-client/clientWrapper.d.mts.map +1 -1
- package/dist/rsc-client/clientWrapper.mjs +14 -15
- package/dist/rsc-client/clientWrapper.mjs.map +1 -1
- package/dist/rsc-client/entry.mjs +2 -4
- package/dist/ssr/entry.d.mts.map +1 -1
- package/dist/ssr/entry.mjs +13 -5
- package/dist/ssr/entry.mjs.map +1 -1
- package/dist/util/basePath.mjs +1 -1
- package/dist/util/drainStream.mjs +1 -1
- package/dist/util/urlPath.mjs +1 -1
- package/package.json +5 -5
- package/dist/rsc-client/entry.d.mts +0 -1
|
@@ -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](/
|
|
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()](/
|
|
106
|
-
- [How It Works](/
|
|
107
|
-
- [React Server Components](/
|
|
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
|
package/dist/docs/learn/RSC.md
CHANGED
|
@@ -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](/
|
|
178
|
-
- [defer()](/
|
|
179
|
-
- [funstackStatic()](/
|
|
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
|
-
|
|
1
|
+
export { };
|
package/dist/entries/client.mjs
CHANGED
package/dist/entries/rsc.mjs
CHANGED
package/dist/entries/server.mjs
CHANGED
package/dist/entries/ssr.mjs
CHANGED
package/dist/entryDefinition.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {};
|
package/dist/index.mjs
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/plugin/index.mjs
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/plugin/server.mjs
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/rsc/defer.d.mts
CHANGED
package/dist/rsc/defer.d.mts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/rsc/defer.mjs
CHANGED
|
@@ -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.
|
|
@@ -39,11 +38,8 @@ var DeferRegistry = class {
|
|
|
39
38
|
};
|
|
40
39
|
const drainPromise = drainStream(stream2);
|
|
41
40
|
entry.drainPromise = drainPromise;
|
|
42
|
-
drainPromise.then((
|
|
43
|
-
entry.state = {
|
|
44
|
-
state: "ready",
|
|
45
|
-
data
|
|
46
|
-
};
|
|
41
|
+
drainPromise.then(() => {
|
|
42
|
+
entry.state = { state: "ready" };
|
|
47
43
|
}, (error) => {
|
|
48
44
|
entry.state = {
|
|
49
45
|
state: "error",
|
|
@@ -118,7 +114,7 @@ function defer(element, options) {
|
|
|
118
114
|
deferRegistry.register(element, id, name);
|
|
119
115
|
return /* @__PURE__ */ jsx(DeferredComponent, { moduleID: id });
|
|
120
116
|
}
|
|
121
|
-
|
|
122
117
|
//#endregion
|
|
123
118
|
export { defer, deferRegistry };
|
|
119
|
+
|
|
124
120
|
//# sourceMappingURL=defer.mjs.map
|
package/dist/rsc/defer.mjs.map
CHANGED
|
@@ -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
|
|
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"}
|
package/dist/rsc/entry.mjs
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { extractIDFromModulePath } from "./rscModule.mjs";
|
|
2
2
|
import { urlPathToFileCandidates } from "../util/urlPath.mjs";
|
|
3
|
-
import
|
|
3
|
+
import "./request.mjs";
|
|
4
4
|
import { stripBasePath } from "../util/basePath.mjs";
|
|
5
|
-
import {
|
|
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 ===
|
|
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}`);
|
|
@@ -150,7 +149,7 @@ async function serveRSC(request) {
|
|
|
150
149
|
"Server-Timing": timings.join(", ")
|
|
151
150
|
}
|
|
152
151
|
});
|
|
153
|
-
case "ready": return new Response(
|
|
152
|
+
case "ready": return new Response(await entry.drainPromise, {
|
|
154
153
|
status: 200,
|
|
155
154
|
headers: {
|
|
156
155
|
"content-type": "text/x-component;charset=utf-8",
|
|
@@ -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
|
package/dist/rsc/entry.mjs.map
CHANGED
|
@@ -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(state.data, {\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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"}
|
package/dist/rsc/marker.mjs
CHANGED
package/dist/rsc/request.mjs
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/rsc/rscModule.mjs
CHANGED
|
@@ -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
|
|
@@ -2,7 +2,11 @@ import { DeferRegistry } from "../rsc/defer.mjs";
|
|
|
2
2
|
import React from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/rsc-client/clientWrapper.d.ts
|
|
5
|
-
|
|
5
|
+
interface DeferContextValue {
|
|
6
|
+
registry: DeferRegistry;
|
|
7
|
+
createFromReadableStream: <T>(stream: ReadableStream<Uint8Array>) => Promise<T>;
|
|
8
|
+
}
|
|
9
|
+
declare const RegistryContext: React.Context<DeferContextValue | undefined>;
|
|
6
10
|
interface DeferredComponentProps {
|
|
7
11
|
moduleID: string;
|
|
8
12
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clientWrapper.d.mts","names":[],"sources":["../../src/rsc-client/clientWrapper.tsx"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"clientWrapper.d.mts","names":[],"sources":["../../src/rsc-client/clientWrapper.tsx"],"mappings":";;;;UAOU,iBAAA;EACR,QAAA,EAAU,aAAA;EACV,wBAAA,MACE,MAAA,EAAQ,cAAA,CAAe,UAAA,MACpB,OAAA,CAAQ,CAAA;AAAA;AAAA,cAGF,eAAA,EAAe,KAAA,CAAA,OAAA,CAAA,iBAAA;AAAA,UAIlB,sBAAA;EACR,QAAA;AAAA;AAAA,cAGW,iBAAA,EAAmB,KAAA,CAAM,EAAA,CAAG,sBAAA"}
|
|
@@ -1,31 +1,30 @@
|
|
|
1
1
|
import { getModulePathFor } from "../rsc/rscModule.mjs";
|
|
2
2
|
import { withBasePath } from "../util/basePath.mjs";
|
|
3
|
-
import { createFromFetch
|
|
4
|
-
import
|
|
5
|
-
|
|
3
|
+
import { createFromFetch } from "@vitejs/plugin-rsc/browser";
|
|
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 }) => {
|
|
9
|
-
const
|
|
8
|
+
const deferContext = use(RegistryContext);
|
|
10
9
|
const modulePath = getModulePathFor(moduleID);
|
|
11
|
-
if (
|
|
12
|
-
const entry = registry.load(moduleID);
|
|
10
|
+
if (deferContext) {
|
|
11
|
+
const entry = deferContext.registry.load(moduleID);
|
|
13
12
|
if (!entry) throw new Error(`Module entry not found for ID '${moduleID}'`);
|
|
14
|
-
return getRSCStreamFromRegistry(entry);
|
|
13
|
+
return getRSCStreamFromRegistry(entry, deferContext.createFromReadableStream);
|
|
15
14
|
}
|
|
16
15
|
return use(getClientRSCStream(withBasePath(modulePath)));
|
|
17
16
|
};
|
|
18
17
|
const moduleToStreamMap = /* @__PURE__ */ new Map();
|
|
19
|
-
async function getRSCStreamFromRegistry(entry) {
|
|
18
|
+
async function getRSCStreamFromRegistry(entry, createFromReadableStream) {
|
|
20
19
|
switch (entry.state.state) {
|
|
21
20
|
case "streaming": return createFromReadableStream(entry.state.stream);
|
|
22
21
|
case "ready": {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
const data = await entry.drainPromise;
|
|
23
|
+
return createFromReadableStream(new ReadableStream({ start(controller) {
|
|
24
|
+
const encoder = new TextEncoder();
|
|
25
|
+
controller.enqueue(encoder.encode(data));
|
|
26
|
+
controller.close();
|
|
27
|
+
} }));
|
|
29
28
|
}
|
|
30
29
|
case "error": return Promise.reject(entry.state.error);
|
|
31
30
|
}
|
|
@@ -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 {
|
|
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"}
|
package/dist/ssr/entry.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.d.mts","names":[],"sources":["../../src/ssr/entry.tsx"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"entry.d.mts","names":[],"sources":["../../src/ssr/entry.tsx"],"mappings":";;;iBAYsB,UAAA,CACpB,SAAA,EAAW,cAAA,CAAe,UAAA,GAC1B,OAAA;EACE,cAAA;EACA,KAAA;EACA,GAAA;EACA,KAAA;EACA,aAAA,GAAgB,aAAA;EAChB,eAAA,GAAkB,cAAA,CAAe,UAAA;AAAA,IAElC,OAAA;EAAU,MAAA,EAAQ,cAAA,CAAe,UAAA;EAAa,MAAA;AAAA"}
|