@funstack/static 0.0.2 → 0.0.3
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 +12 -0
- package/dist/bin/skill-installer.d.mts +1 -0
- package/dist/bin/skill-installer.mjs +12 -0
- package/dist/bin/skill-installer.mjs.map +1 -0
- package/dist/build/buildApp.mjs +4 -3
- package/dist/build/buildApp.mjs.map +1 -1
- package/dist/build/rscProcessor.mjs +9 -3
- package/dist/build/rscProcessor.mjs.map +1 -1
- package/dist/client/entry.d.mts +1 -0
- package/dist/client/entry.mjs +17 -9
- package/dist/client/entry.mjs.map +1 -1
- package/dist/client/globals.mjs.map +1 -1
- package/dist/docs/FAQ.md +5 -0
- package/dist/docs/GettingStarted.md +180 -0
- package/dist/docs/api/Defer.md +110 -0
- package/dist/docs/api/FunstackStatic.md +184 -0
- package/dist/docs/index.md +20 -0
- package/dist/docs/learn/HowItWorks.md +109 -0
- package/dist/docs/learn/OptimizingPayloads.md +105 -0
- package/dist/docs/learn/RSC.md +179 -0
- package/dist/docs/learn/SSR.md +104 -0
- package/dist/entries/client.d.mts +1 -1
- package/dist/entries/rsc-client.d.mts +2 -2
- package/dist/entries/rsc-client.mjs +2 -2
- package/dist/entries/server.d.mts +2 -2
- package/dist/plugin/index.d.mts +17 -1
- package/dist/plugin/index.d.mts.map +1 -1
- package/dist/plugin/index.mjs +10 -1
- package/dist/plugin/index.mjs.map +1 -1
- package/dist/rsc/defer.d.mts +18 -3
- package/dist/rsc/defer.d.mts.map +1 -1
- package/dist/rsc/defer.mjs +34 -14
- package/dist/rsc/defer.mjs.map +1 -1
- package/dist/rsc/entry.d.mts.map +1 -1
- package/dist/rsc/entry.mjs +85 -20
- package/dist/rsc/entry.mjs.map +1 -1
- package/dist/rsc-client/clientWrapper.d.mts +3 -3
- package/dist/rsc-client/clientWrapper.d.mts.map +1 -1
- package/dist/rsc-client/clientWrapper.mjs +2 -2
- package/dist/rsc-client/clientWrapper.mjs.map +1 -1
- package/dist/rsc-client/entry.d.mts +1 -1
- package/dist/rsc-client/entry.mjs +1 -1
- package/dist/ssr/entry.d.mts +2 -0
- package/dist/ssr/entry.d.mts.map +1 -1
- package/dist/ssr/entry.mjs +6 -2
- package/dist/ssr/entry.mjs.map +1 -1
- package/package.json +26 -11
- package/skills/funstack-static-knowledge/SKILL.md +44 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Server-Side Rendering
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
FUNSTACK Static supports a `ssr` option to enable SSR. This page explains when to enable this option and what to consider.
|
|
6
|
+
|
|
7
|
+
## What is SSR?
|
|
8
|
+
|
|
9
|
+
By default, FUNSTACK Static renders only the Root shell to HTML. The App component is delivered as an RSC payload and rendered client-side. When you enable `ssr: true`, the App component is also rendered to HTML at build time:
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
funstackStatic({
|
|
13
|
+
root: "./src/root.tsx",
|
|
14
|
+
app: "./src/App.tsx",
|
|
15
|
+
ssr: true,
|
|
16
|
+
});
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Pros
|
|
20
|
+
|
|
21
|
+
### Faster Paint on Initial Page Load
|
|
22
|
+
|
|
23
|
+
With SSR enabled, the full page content is visible in the HTML before JavaScript loads. Users see meaningful content immediately, rather than waiting for JavaScript to download, parse, and execute.
|
|
24
|
+
|
|
25
|
+
This improves perceived performance, especially on:
|
|
26
|
+
|
|
27
|
+
- Slower network connections
|
|
28
|
+
- Lower-powered devices
|
|
29
|
+
- Pages with significant content above the fold
|
|
30
|
+
|
|
31
|
+
The browser can start painting content as soon as the HTML arrives, while JavaScript loads in the background. Once loaded, React hydrates the existing HTML to make it interactive.
|
|
32
|
+
|
|
33
|
+
## Cons
|
|
34
|
+
|
|
35
|
+
### Client Components Must Be SSR-Capable
|
|
36
|
+
|
|
37
|
+
When SSR is enabled, your client components run during the build process to generate HTML. This means they must work in a server-like environment where browser APIs are not available.
|
|
38
|
+
|
|
39
|
+
Components that directly access browser-only APIs will cause build errors:
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
"use client";
|
|
43
|
+
|
|
44
|
+
// This will fail during SSR build
|
|
45
|
+
export function BrowserOnlyComponent() {
|
|
46
|
+
const width = window.innerWidth; // window is not defined during SSR
|
|
47
|
+
return <div>Width: {width}</div>;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
To make components SSR-capable, use `useSyncExternalStore` with a server snapshot:
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
"use client";
|
|
55
|
+
|
|
56
|
+
import { useSyncExternalStore } from "react";
|
|
57
|
+
|
|
58
|
+
function subscribeToWidth(callback: () => void) {
|
|
59
|
+
window.addEventListener("resize", callback);
|
|
60
|
+
return () => window.removeEventListener("resize", callback);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getWidth() {
|
|
64
|
+
return window.innerWidth;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getServerWidth() {
|
|
68
|
+
return 0; // Fallback value during SSR
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function SSRSafeComponent() {
|
|
72
|
+
const width = useSyncExternalStore(
|
|
73
|
+
subscribeToWidth,
|
|
74
|
+
getWidth,
|
|
75
|
+
getServerWidth,
|
|
76
|
+
);
|
|
77
|
+
return <div>Width: {width}</div>;
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Common browser APIs to watch for:
|
|
82
|
+
|
|
83
|
+
- `window` and `document`
|
|
84
|
+
- `localStorage` and `sessionStorage`
|
|
85
|
+
- `location` (use your router's APIs instead)
|
|
86
|
+
|
|
87
|
+
## When to Use SSR
|
|
88
|
+
|
|
89
|
+
**Enable SSR when:**
|
|
90
|
+
|
|
91
|
+
- You want the fastest possible initial paint
|
|
92
|
+
- Your client components are already SSR-compatible
|
|
93
|
+
- You're building content-heavy pages
|
|
94
|
+
|
|
95
|
+
**Keep SSR disabled when:**
|
|
96
|
+
|
|
97
|
+
- Your app relies heavily on browser-only APIs
|
|
98
|
+
- You prefer simpler client component development
|
|
99
|
+
- Initial paint speed is not a priority
|
|
100
|
+
|
|
101
|
+
## See Also
|
|
102
|
+
|
|
103
|
+
- [How It Works](/funstack-static/learn/how-it-works) - Understanding the build process
|
|
104
|
+
- [funstackStatic()](/funstack-static/api/funstack-static) - Configuration reference
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import "../client/entry.mjs";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DeferredComponent, RegistryContext } from "../rsc-client/clientWrapper.mjs";
|
|
2
2
|
import "../rsc-client/entry.mjs";
|
|
3
|
-
export {
|
|
3
|
+
export { DeferredComponent, RegistryContext };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { DeferredComponent, RegistryContext } from "../rsc-client/clientWrapper.mjs";
|
|
4
4
|
import "../rsc-client/entry.mjs";
|
|
5
5
|
|
|
6
|
-
export {
|
|
6
|
+
export { DeferredComponent, RegistryContext };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { defer } from "../rsc/defer.mjs";
|
|
2
|
-
export { defer };
|
|
1
|
+
import { DeferOptions, defer } from "../rsc/defer.mjs";
|
|
2
|
+
export { type DeferOptions, defer };
|
package/dist/plugin/index.d.mts
CHANGED
|
@@ -19,11 +19,27 @@ interface FunstackStaticOptions {
|
|
|
19
19
|
* @default dist/public
|
|
20
20
|
*/
|
|
21
21
|
publicOutDir?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Enable server-side rendering of the App component.
|
|
24
|
+
* When false, only the Root shell is SSR'd and the App renders client-side.
|
|
25
|
+
* When true, both Root and App are SSR'd and the client hydrates.
|
|
26
|
+
*
|
|
27
|
+
* @default false
|
|
28
|
+
*/
|
|
29
|
+
ssr?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Path to a module that runs on the client side before React hydration.
|
|
32
|
+
* Use this for client-side instrumentation like Sentry, analytics, or feature flags.
|
|
33
|
+
* The module is imported for its side effects only (no exports needed).
|
|
34
|
+
*/
|
|
35
|
+
clientInit?: string;
|
|
22
36
|
}
|
|
23
37
|
declare function funstackStatic({
|
|
24
38
|
root,
|
|
25
39
|
app,
|
|
26
|
-
publicOutDir
|
|
40
|
+
publicOutDir,
|
|
41
|
+
ssr,
|
|
42
|
+
clientInit
|
|
27
43
|
}: FunstackStaticOptions): (Plugin | Plugin[])[];
|
|
28
44
|
//#endregion
|
|
29
45
|
export { FunstackStaticOptions, funstackStatic };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/plugin/index.ts"],"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/plugin/index.ts"],"mappings":";;;UAMiB,qBAAA;;AAAjB;;;;EAME,IAAA;EAKA;;;;EAAA,GAAA;EAoBU;AACX;;;;EAfC,YAAA;EAoBA;;;;;;;EAZA,GAAA;EAUA;;;;;EAJA,UAAA;AAAA;AAAA,iBAGsB,cAAA,CAAA;EACtB,IAAA;EACA,GAAA;EACA,YAAA;EACA,GAAA;EACA;AAAA,GACC,qBAAA,IAAyB,MAAA,GAAS,MAAA"}
|
package/dist/plugin/index.mjs
CHANGED
|
@@ -4,9 +4,10 @@ import path from "node:path";
|
|
|
4
4
|
import rsc from "@vitejs/plugin-rsc";
|
|
5
5
|
|
|
6
6
|
//#region src/plugin/index.ts
|
|
7
|
-
function funstackStatic({ root, app, publicOutDir = "dist/public" }) {
|
|
7
|
+
function funstackStatic({ root, app, publicOutDir = "dist/public", ssr = false, clientInit }) {
|
|
8
8
|
let resolvedRootEntry = "__uninitialized__";
|
|
9
9
|
let resolvedAppEntry = "__uninitialized__";
|
|
10
|
+
let resolvedClientInitEntry;
|
|
10
11
|
return [
|
|
11
12
|
{
|
|
12
13
|
name: "@funstack/static:config-pre",
|
|
@@ -28,6 +29,7 @@ function funstackStatic({ root, app, publicOutDir = "dist/public" }) {
|
|
|
28
29
|
configResolved(config) {
|
|
29
30
|
resolvedRootEntry = path.resolve(config.root, root);
|
|
30
31
|
resolvedAppEntry = path.resolve(config.root, app);
|
|
32
|
+
if (clientInit) resolvedClientInitEntry = path.resolve(config.root, clientInit);
|
|
31
33
|
},
|
|
32
34
|
configEnvironment(_name, config) {
|
|
33
35
|
if (config.optimizeDeps?.include) config.optimizeDeps.include = config.optimizeDeps.include.map((entry) => {
|
|
@@ -41,10 +43,17 @@ function funstackStatic({ root, app, publicOutDir = "dist/public" }) {
|
|
|
41
43
|
resolveId(id) {
|
|
42
44
|
if (id === "virtual:funstack/root") return "\0virtual:funstack/root";
|
|
43
45
|
if (id === "virtual:funstack/app") return "\0virtual:funstack/app";
|
|
46
|
+
if (id === "virtual:funstack/config") return "\0virtual:funstack/config";
|
|
47
|
+
if (id === "virtual:funstack/client-init") return "\0virtual:funstack/client-init";
|
|
44
48
|
},
|
|
45
49
|
load(id) {
|
|
46
50
|
if (id === "\0virtual:funstack/root") return `export { default } from "${resolvedRootEntry}";`;
|
|
47
51
|
if (id === "\0virtual:funstack/app") return `export { default } from "${resolvedAppEntry}";`;
|
|
52
|
+
if (id === "\0virtual:funstack/config") return `export const ssr = ${JSON.stringify(ssr)};`;
|
|
53
|
+
if (id === "\0virtual:funstack/client-init") {
|
|
54
|
+
if (resolvedClientInitEntry) return `import "${resolvedClientInitEntry}";`;
|
|
55
|
+
return "";
|
|
56
|
+
}
|
|
48
57
|
}
|
|
49
58
|
},
|
|
50
59
|
{
|
|
@@ -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\nexport interface FunstackStaticOptions {\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 /**\n * Output directory for build.\n *\n * @default dist/public\n */\n publicOutDir?: string;\n}\n\nexport default function funstackStatic({\n root,\n app,\n publicOutDir = \"dist/public\",\n}: FunstackStaticOptions): (Plugin | Plugin[])[] {\n let resolvedRootEntry: string = \"__uninitialized__\";\n let resolvedAppEntry: string = \"__uninitialized__\";\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 resolvedRootEntry = path.resolve(config.root, root);\n resolvedAppEntry = path.resolve(config.root, app);\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 configEnvironment(_name, config) {\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 },\n },\n {\n name: \"@funstack/static:virtual-entry\",\n resolveId(id) {\n if (id === \"virtual:funstack/root\") {\n return \"\\0virtual:funstack/root\";\n }\n if (id === \"virtual:funstack/app\") {\n return \"\\0virtual:funstack/app\";\n }\n },\n load(id) {\n if (id === \"\\0virtual:funstack/root\") {\n return `export { default } from \"${resolvedRootEntry}\";`;\n }\n if (id === \"\\0virtual:funstack/app\") {\n return `export { default } from \"${resolvedAppEntry}\";`;\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\nexport interface FunstackStaticOptions {\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 /**\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\nexport default function funstackStatic({\n root,\n app,\n publicOutDir = \"dist/public\",\n ssr = false,\n clientInit,\n}: FunstackStaticOptions): (Plugin | Plugin[])[] {\n let resolvedRootEntry: string = \"__uninitialized__\";\n let resolvedAppEntry: string = \"__uninitialized__\";\n let resolvedClientInitEntry: string | 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 resolvedRootEntry = path.resolve(config.root, root);\n resolvedAppEntry = path.resolve(config.root, app);\n if (clientInit) {\n resolvedClientInitEntry = path.resolve(config.root, clientInit);\n }\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 configEnvironment(_name, config) {\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 },\n },\n {\n name: \"@funstack/static:virtual-entry\",\n resolveId(id) {\n if (id === \"virtual:funstack/root\") {\n return \"\\0virtual:funstack/root\";\n }\n if (id === \"virtual:funstack/app\") {\n return \"\\0virtual:funstack/app\";\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/root\") {\n return `export { default } from \"${resolvedRootEntry}\";`;\n }\n if (id === \"\\0virtual:funstack/app\") {\n return `export { default } from \"${resolvedAppEntry}\";`;\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":";;;;;;AAwCA,SAAwB,eAAe,EACrC,MACA,KACA,eAAe,eACf,MAAM,OACN,cAC+C;CAC/C,IAAI,oBAA4B;CAChC,IAAI,mBAA2B;CAC/B,IAAI;AAEJ,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,wBAAoB,KAAK,QAAQ,OAAO,MAAM,KAAK;AACnD,uBAAmB,KAAK,QAAQ,OAAO,MAAM,IAAI;AACjD,QAAI,WACF,2BAA0B,KAAK,QAAQ,OAAO,MAAM,WAAW;;GAKnE,kBAAkB,OAAO,QAAQ;AAC/B,QAAI,OAAO,cAAc,QACvB,QAAO,aAAa,UAAU,OAAO,aAAa,QAAQ,KACvD,UAAU;AACT,SAAI,MAAM,WAAW,qBAAqB,CACxC,SAAQ,sBAAsB;AAEhC,YAAO;MAEV;;GAGN;EACD;GACE,MAAM;GACN,UAAU,IAAI;AACZ,QAAI,OAAO,wBACT,QAAO;AAET,QAAI,OAAO,uBACT,QAAO;AAET,QAAI,OAAO,0BACT,QAAO;AAET,QAAI,OAAO,+BACT,QAAO;;GAGX,KAAK,IAAI;AACP,QAAI,OAAO,0BACT,QAAO,4BAA4B,kBAAkB;AAEvD,QAAI,OAAO,yBACT,QAAO,4BAA4B,iBAAiB;AAEtD,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/rsc/defer.d.mts
CHANGED
|
@@ -3,6 +3,18 @@ import { ReactElement, ReactNode } from "react";
|
|
|
3
3
|
//#region src/rsc/defer.d.ts
|
|
4
4
|
interface DeferEntry {
|
|
5
5
|
state: DeferEntryState;
|
|
6
|
+
name?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Options for the defer function.
|
|
10
|
+
*/
|
|
11
|
+
interface DeferOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Optional name for debugging purposes.
|
|
14
|
+
* In development: included in the RSC payload file name.
|
|
15
|
+
* In production: logged when the payload file is emitted.
|
|
16
|
+
*/
|
|
17
|
+
name?: string;
|
|
6
18
|
}
|
|
7
19
|
interface LoadedDeferEntry extends DeferEntry {
|
|
8
20
|
state: Exclude<DeferEntryState, {
|
|
@@ -24,7 +36,7 @@ type DeferEntryState = {
|
|
|
24
36
|
};
|
|
25
37
|
declare class DeferRegistry {
|
|
26
38
|
#private;
|
|
27
|
-
register(element: ReactElement, id: string): void;
|
|
39
|
+
register(element: ReactElement, id: string, name?: string): void;
|
|
28
40
|
load(id: string): LoadedDeferEntry | undefined;
|
|
29
41
|
has(id: string): boolean;
|
|
30
42
|
/**
|
|
@@ -34,6 +46,7 @@ declare class DeferRegistry {
|
|
|
34
46
|
loadAll(): AsyncGenerator<{
|
|
35
47
|
id: string;
|
|
36
48
|
data: string;
|
|
49
|
+
name?: string;
|
|
37
50
|
}, void, unknown>;
|
|
38
51
|
}
|
|
39
52
|
/**
|
|
@@ -42,9 +55,11 @@ declare class DeferRegistry {
|
|
|
42
55
|
* During the client side rendering, fetching of the payload will be
|
|
43
56
|
* deferred until the returned ReactNode is actually rendered.
|
|
44
57
|
*
|
|
58
|
+
* @param element - The React element to defer.
|
|
59
|
+
* @param options - Optional configuration for the deferred payload.
|
|
45
60
|
* @returns A ReactNode that virtually contains the result of rendering the given component.
|
|
46
61
|
*/
|
|
47
|
-
declare function defer(element: ReactElement): ReactNode;
|
|
62
|
+
declare function defer(element: ReactElement, options?: DeferOptions): ReactNode;
|
|
48
63
|
//#endregion
|
|
49
|
-
export { DeferRegistry, defer };
|
|
64
|
+
export { DeferOptions, DeferRegistry, defer };
|
|
50
65
|
//# sourceMappingURL=defer.d.mts.map
|
package/dist/rsc/defer.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defer.d.mts","names":[],"sources":["../../src/rsc/defer.tsx"],"
|
|
1
|
+
{"version":3,"file":"defer.d.mts","names":[],"sources":["../../src/rsc/defer.tsx"],"mappings":";;;UAMiB,UAAA;EACf,KAAA,EAAO,eAAA;EACP,IAAA;AAAA;;;;UAMe,YAAA;EANf;;;AAMF;;EAME,IAAA;AAAA;AAAA,UAGe,gBAAA,SAAyB,UAAA;EACxC,KAAA,EAAO,OAAA,CAAQ,eAAA;IAAmB,KAAA;EAAA;AAAA;AAAA,KAG/B,eAAA;EAEC,KAAA;EACA,OAAA,EAAS,YAAA;AAAA;EAGT,KAAA;EACA,MAAA,EAAQ,cAAA,CAAe,UAAA;AAAA;EAGvB,KAAA;EACA,IAAA;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;EAwClB,GAAA,CAAI,EAAA;EAtEsB;;;;EA8EnB,OAAA,CAAA,GAAO,cAAA;;;;;;;;;AAvDhB;;;;;;;iBA2IgB,KAAA,CACd,OAAA,EAAS,YAAA,EACT,OAAA,GAAU,YAAA,GACT,SAAA"}
|
package/dist/rsc/defer.mjs
CHANGED
|
@@ -2,16 +2,26 @@ import { getPayloadIDFor } from "./rscModule.mjs";
|
|
|
2
2
|
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
|
-
import {
|
|
5
|
+
import { DeferredComponent } from "#rsc-client";
|
|
6
6
|
|
|
7
7
|
//#region src/rsc/defer.tsx
|
|
8
|
+
/**
|
|
9
|
+
* Sanitizes a name for use in file paths.
|
|
10
|
+
* Replaces non-alphanumeric characters with underscores and limits length.
|
|
11
|
+
*/
|
|
12
|
+
function sanitizeName(name) {
|
|
13
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").slice(0, 50);
|
|
14
|
+
}
|
|
8
15
|
var DeferRegistry = class {
|
|
9
16
|
#registry = /* @__PURE__ */ new Map();
|
|
10
|
-
register(element, id) {
|
|
11
|
-
this.#registry.set(id, {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
register(element, id, name) {
|
|
18
|
+
this.#registry.set(id, {
|
|
19
|
+
state: {
|
|
20
|
+
element,
|
|
21
|
+
state: "pending"
|
|
22
|
+
},
|
|
23
|
+
name
|
|
24
|
+
});
|
|
15
25
|
}
|
|
16
26
|
load(id) {
|
|
17
27
|
const entry = this.#registry.get(id);
|
|
@@ -58,7 +68,11 @@ var DeferRegistry = class {
|
|
|
58
68
|
*/
|
|
59
69
|
async *loadAll() {
|
|
60
70
|
const errors = [];
|
|
61
|
-
const loadedEntries = Array.from(this.#registry, ([id, entry]) => [
|
|
71
|
+
const loadedEntries = Array.from(this.#registry, ([id, entry]) => [
|
|
72
|
+
id,
|
|
73
|
+
this.#loadEntry(entry),
|
|
74
|
+
entry.name
|
|
75
|
+
]);
|
|
62
76
|
if (loadedEntries.length === 0) return;
|
|
63
77
|
const completed = [];
|
|
64
78
|
let waiting;
|
|
@@ -68,19 +82,21 @@ var DeferRegistry = class {
|
|
|
68
82
|
remainingCount--;
|
|
69
83
|
waiting?.();
|
|
70
84
|
};
|
|
71
|
-
for (const [id, loadedEntry] of loadedEntries) (async () => {
|
|
85
|
+
for (const [id, loadedEntry, name] of loadedEntries) (async () => {
|
|
72
86
|
try {
|
|
73
87
|
switch (loadedEntry.state.state) {
|
|
74
88
|
case "streaming":
|
|
75
89
|
onComplete({
|
|
76
90
|
id,
|
|
77
|
-
data: await drainStream(loadedEntry.state.stream)
|
|
91
|
+
data: await drainStream(loadedEntry.state.stream),
|
|
92
|
+
name
|
|
78
93
|
});
|
|
79
94
|
break;
|
|
80
95
|
case "ready":
|
|
81
96
|
onComplete({
|
|
82
97
|
id,
|
|
83
|
-
data: loadedEntry.state.data
|
|
98
|
+
data: loadedEntry.state.data,
|
|
99
|
+
name
|
|
84
100
|
});
|
|
85
101
|
break;
|
|
86
102
|
case "error":
|
|
@@ -111,12 +127,16 @@ const deferRegistry = new DeferRegistry();
|
|
|
111
127
|
* During the client side rendering, fetching of the payload will be
|
|
112
128
|
* deferred until the returned ReactNode is actually rendered.
|
|
113
129
|
*
|
|
130
|
+
* @param element - The React element to defer.
|
|
131
|
+
* @param options - Optional configuration for the deferred payload.
|
|
114
132
|
* @returns A ReactNode that virtually contains the result of rendering the given component.
|
|
115
133
|
*/
|
|
116
|
-
function defer(element) {
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
134
|
+
function defer(element, options) {
|
|
135
|
+
const name = options?.name;
|
|
136
|
+
const sanitizedName = name ? sanitizeName(name) : void 0;
|
|
137
|
+
const id = getPayloadIDFor(sanitizedName ? `${sanitizedName}-${crypto.randomUUID()}` : crypto.randomUUID());
|
|
138
|
+
deferRegistry.register(element, id, name);
|
|
139
|
+
return /* @__PURE__ */ jsx(DeferredComponent, { moduleID: id });
|
|
120
140
|
}
|
|
121
141
|
|
|
122
142
|
//#endregion
|
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 {
|
|
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}\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}\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 data: string;\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 (async () => {\n const chunks: string[] = [];\n const decoder = new TextDecoder();\n for await (const chunk of stream2) {\n chunks.push(decoder.decode(chunk, { stream: true }));\n }\n chunks.push(decoder.decode());\n entry.state = {\n state: \"ready\",\n data: chunks.join(\"\"),\n };\n })().catch((error) => {\n entry.state = { state: \"error\", error };\n });\n return entry as LoadedDeferEntry;\n }\n case \"streaming\":\n case \"ready\":\n case \"error\": {\n return entry as LoadedDeferEntry;\n }\n }\n state satisfies never;\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\n const loadedEntries = Array.from(\n this.#registry,\n ([id, entry]) => [id, this.#loadEntry(entry), 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: Start all operations (each pushes to queue when done)\n for (const [id, loadedEntry, name] of loadedEntries) {\n (async () => {\n try {\n switch (loadedEntry.state.state) {\n case \"streaming\":\n onComplete({\n id,\n data: await drainStream(loadedEntry.state.stream),\n name,\n });\n break;\n case \"ready\":\n onComplete({ id, data: loadedEntry.state.data, name });\n break;\n case \"error\":\n onComplete({ error: loadedEntry.state.error });\n break;\n }\n } catch (error) {\n onComplete({ error });\n }\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":"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"}
|
package/dist/rsc/entry.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.d.mts","names":[],"sources":["../../src/rsc/entry.tsx"],"
|
|
1
|
+
{"version":3,"file":"entry.d.mts","names":[],"sources":["../../src/rsc/entry.tsx"],"mappings":";;;KAQY,UAAA;EACV,IAAA,EAAM,KAAA,CAAM,SAAA;AAAA;;;;iBA2BQ,SAAA,CAAA,GAAa,OAAA,CAAQ,QAAA;AAAA,cA+ErC,aAAA,SAAsB,KAAA;EAC1B,MAAA;cACY,OAAA,UAAiB,MAAA;AAAA;AAAA,iBAOf,eAAA,CAAgB,KAAA,YAAiB,KAAA,IAAS,aAAA;;;;iBAOpC,QAAA,CAAS,OAAA,EAAS,OAAA,GAAU,OAAA,CAAQ,QAAA;AAlBzD;;;AAAA,iBA2FqB,KAAA,CAAA,GAAK,OAAA"}
|
package/dist/rsc/entry.mjs
CHANGED
|
@@ -4,30 +4,71 @@ import { stripBasePath } from "../util/basePath.mjs";
|
|
|
4
4
|
import { defer, deferRegistry } from "./defer.mjs";
|
|
5
5
|
import { generateAppMarker } from "./marker.mjs";
|
|
6
6
|
import { jsx } from "react/jsx-runtime";
|
|
7
|
+
import { ssr } from "virtual:funstack/config";
|
|
7
8
|
import { renderToReadableStream } from "@vitejs/plugin-rsc/rsc";
|
|
8
9
|
|
|
9
10
|
//#region src/rsc/entry.tsx
|
|
10
|
-
async function
|
|
11
|
+
async function loadEntries() {
|
|
11
12
|
const Root = (await import("virtual:funstack/root")).default;
|
|
12
13
|
const App = (await import("virtual:funstack/app")).default;
|
|
13
14
|
if (Root === void 0) throw new Error("Failed to load RSC root entry module. Check your entry file to ensure it has a default export.");
|
|
14
15
|
if (App === void 0) throw new Error("Failed to load RSC app entry module. Check your entry file to ensure it has a default export.");
|
|
15
|
-
return
|
|
16
|
+
return {
|
|
17
|
+
Root,
|
|
18
|
+
App
|
|
19
|
+
};
|
|
16
20
|
}
|
|
17
21
|
/**
|
|
18
22
|
* Entrypoint to serve HTML response in dev environment
|
|
19
23
|
*/
|
|
20
24
|
async function serveHTML() {
|
|
25
|
+
const timings = [];
|
|
21
26
|
const marker = generateAppMarker();
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
const entriesStart = performance.now();
|
|
28
|
+
const { Root, App } = await loadEntries();
|
|
29
|
+
timings.push(`entries;dur=${performance.now() - entriesStart}`);
|
|
30
|
+
const ssrModuleStart = performance.now();
|
|
31
|
+
const ssrEntryModule = await import.meta.viteRsc.loadModule("ssr");
|
|
32
|
+
timings.push(`ssr-module;dur=${performance.now() - ssrModuleStart}`);
|
|
33
|
+
if (ssr) {
|
|
34
|
+
const rscStart = performance.now();
|
|
35
|
+
const rootRscStream = renderToReadableStream({ root: /* @__PURE__ */ jsx(Root, { children: /* @__PURE__ */ jsx(App, {}) }) });
|
|
36
|
+
timings.push(`rsc;dur=${performance.now() - rscStart}`);
|
|
37
|
+
const ssrStart = performance.now();
|
|
38
|
+
const ssrResult = await ssrEntryModule.renderHTML(rootRscStream, {
|
|
39
|
+
appEntryMarker: marker,
|
|
40
|
+
build: false,
|
|
41
|
+
ssr: true
|
|
42
|
+
});
|
|
43
|
+
timings.push(`ssr;dur=${performance.now() - ssrStart}`);
|
|
44
|
+
return new Response(ssrResult.stream, {
|
|
45
|
+
status: ssrResult.status,
|
|
46
|
+
headers: {
|
|
47
|
+
"Content-type": "text/html",
|
|
48
|
+
"Server-Timing": timings.join(", ")
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
const rscStart = performance.now();
|
|
53
|
+
const shellRscStream = renderToReadableStream({ root: /* @__PURE__ */ jsx(Root, { children: /* @__PURE__ */ jsx("span", { id: marker }) }) });
|
|
54
|
+
const clientRscStream = renderToReadableStream({ root: /* @__PURE__ */ jsx(Root, { children: /* @__PURE__ */ jsx(App, {}) }) });
|
|
55
|
+
timings.push(`rsc;dur=${performance.now() - rscStart}`);
|
|
56
|
+
const ssrStart = performance.now();
|
|
57
|
+
const ssrResult = await ssrEntryModule.renderHTML(shellRscStream, {
|
|
58
|
+
appEntryMarker: marker,
|
|
59
|
+
build: false,
|
|
60
|
+
ssr: false,
|
|
61
|
+
clientRscStream
|
|
62
|
+
});
|
|
63
|
+
timings.push(`ssr;dur=${performance.now() - ssrStart}`);
|
|
64
|
+
return new Response(ssrResult.stream, {
|
|
65
|
+
status: ssrResult.status,
|
|
66
|
+
headers: {
|
|
67
|
+
"Content-type": "text/html",
|
|
68
|
+
"Server-Timing": timings.join(", ")
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
31
72
|
}
|
|
32
73
|
var ServeRSCError = class extends Error {
|
|
33
74
|
status;
|
|
@@ -44,27 +85,44 @@ function isServeRSCError(error) {
|
|
|
44
85
|
* Servers an RSC stream response
|
|
45
86
|
*/
|
|
46
87
|
async function serveRSC(request) {
|
|
88
|
+
const timings = [];
|
|
47
89
|
const pathname = stripBasePath(new URL(request.url).pathname);
|
|
48
90
|
if (pathname === devMainRscPath) {
|
|
49
|
-
const
|
|
91
|
+
const entriesStart = performance.now();
|
|
92
|
+
const { Root, App } = await loadEntries();
|
|
93
|
+
timings.push(`entries;dur=${performance.now() - entriesStart}`);
|
|
94
|
+
const rscStart = performance.now();
|
|
95
|
+
const rootRscStream = renderToReadableStream({ root: /* @__PURE__ */ jsx(Root, { children: /* @__PURE__ */ jsx(App, {}) }) });
|
|
96
|
+
timings.push(`rsc;dur=${performance.now() - rscStart}`);
|
|
50
97
|
return new Response(rootRscStream, {
|
|
51
98
|
status: 200,
|
|
52
|
-
headers: {
|
|
99
|
+
headers: {
|
|
100
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
101
|
+
"Server-Timing": timings.join(", ")
|
|
102
|
+
}
|
|
53
103
|
});
|
|
54
104
|
}
|
|
55
105
|
const moduleId = extractIDFromModulePath(pathname);
|
|
56
106
|
if (!moduleId) throw new ServeRSCError(`Invalid RSC module path: ${pathname}`, 404);
|
|
107
|
+
const deferLoadStart = performance.now();
|
|
57
108
|
const entry = deferRegistry.load(moduleId);
|
|
58
109
|
if (!entry) throw new ServeRSCError(`RSC component not found: ${moduleId}`, 404);
|
|
110
|
+
timings.push(`defer-load;dur=${performance.now() - deferLoadStart}`);
|
|
59
111
|
const { state } = entry;
|
|
60
112
|
switch (state.state) {
|
|
61
113
|
case "streaming": return new Response(state.stream, {
|
|
62
114
|
status: 200,
|
|
63
|
-
headers: {
|
|
115
|
+
headers: {
|
|
116
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
117
|
+
"Server-Timing": timings.join(", ")
|
|
118
|
+
}
|
|
64
119
|
});
|
|
65
120
|
case "ready": return new Response(state.data, {
|
|
66
121
|
status: 200,
|
|
67
|
-
headers: {
|
|
122
|
+
headers: {
|
|
123
|
+
"content-type": "text/x-component;charset=utf-8",
|
|
124
|
+
"Server-Timing": timings.join(", ")
|
|
125
|
+
}
|
|
68
126
|
});
|
|
69
127
|
case "error": throw new ServeRSCError(`Failed to load RSC component: ${state.error}`, 500);
|
|
70
128
|
}
|
|
@@ -74,14 +132,21 @@ async function serveRSC(request) {
|
|
|
74
132
|
*/
|
|
75
133
|
async function build() {
|
|
76
134
|
const marker = generateAppMarker();
|
|
77
|
-
const Root =
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
135
|
+
const { Root, App } = await loadEntries();
|
|
136
|
+
let rootRscStream;
|
|
137
|
+
let appRscStream;
|
|
138
|
+
if (ssr) {
|
|
139
|
+
rootRscStream = renderToReadableStream({ root: /* @__PURE__ */ jsx(Root, { children: /* @__PURE__ */ jsx(App, {}) }) });
|
|
140
|
+
appRscStream = renderToReadableStream({ root: /* @__PURE__ */ jsx(Root, { children: /* @__PURE__ */ jsx(App, {}) }) });
|
|
141
|
+
} else {
|
|
142
|
+
rootRscStream = renderToReadableStream({ root: /* @__PURE__ */ jsx(Root, { children: /* @__PURE__ */ jsx("span", { id: marker }) }) });
|
|
143
|
+
appRscStream = renderToReadableStream({ root: /* @__PURE__ */ jsx(App, {}) });
|
|
144
|
+
}
|
|
81
145
|
return {
|
|
82
146
|
html: (await (await import.meta.viteRsc.loadModule("ssr")).renderHTML(rootRscStream, {
|
|
83
147
|
appEntryMarker: marker,
|
|
84
|
-
build: true
|
|
148
|
+
build: true,
|
|
149
|
+
ssr
|
|
85
150
|
})).stream,
|
|
86
151
|
appRsc: appRscStream,
|
|
87
152
|
deferRegistry
|
package/dist/rsc/entry.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.mjs","names":[],"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\";\n\nexport type RscPayload = {\n root: React.ReactNode;\n};\n\nasync function devMainRSCStream() {\n const Root = (await import(\"virtual:funstack/root\")).default;\n const App = (await import(\"virtual:funstack/app\")).default;\n\n // Sanity check; this may happen when user-provided entry file\n // does not have a default export.\n if (Root === undefined) {\n throw new Error(\n \"Failed to load RSC root entry module. Check your entry file to ensure it has a default export.\",\n );\n }\n if (App === undefined) {\n throw new Error(\n \"Failed to load RSC app entry module. Check your entry file to ensure it has a default export.\",\n );\n }\n\n const rootRscStream = renderToReadableStream<RscPayload>({\n root: (\n <Root>\n <App />\n </Root>\n ),\n });\n return rootRscStream;\n}\n\n/**\n * Entrypoint to serve HTML response in dev environment\n */\nexport async function serveHTML(): Promise<Response> {\n const marker = generateAppMarker();\n\n const rootRscStream = await devMainRSCStream();\n\n const ssrEntryModule = await import.meta.viteRsc.loadModule<\n typeof import(\"../ssr/entry\")\n >(\"ssr\");\n const ssrResult = await ssrEntryModule.renderHTML(rootRscStream, {\n appEntryMarker: marker,\n build: false,\n });\n\n // respond html\n return new Response(ssrResult.stream, {\n status: ssrResult.status,\n headers: {\n \"Content-type\": \"text/html\",\n },\n });\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 * Servers an RSC stream response\n */\nexport async function serveRSC(request: Request): Promise<Response> {\n const url = new URL(request.url);\n const pathname = stripBasePath(url.pathname);\n if (pathname === devMainRscPath) {\n // root RSC stream is requested\n const rootRscStream = await devMainRSCStream();\n return new Response(rootRscStream, {\n status: 200,\n headers: {\n \"content-type\": \"text/x-component;charset=utf-8\",\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 entry = deferRegistry.load(moduleId);\n if (!entry) {\n throw new ServeRSCError(`RSC component not found: ${moduleId}`, 404);\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 },\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 },\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\n */\nexport async function build() {\n const marker = generateAppMarker();\n\n const Root = (await import(\"virtual:funstack/root\")).default;\n const App = (await import(\"virtual:funstack/app\")).default;\n\n const rootRscStream = renderToReadableStream<RscPayload>({\n root: (\n <Root>\n <span id={marker} />\n </Root>\n ),\n });\n\n const appRscStream = renderToReadableStream<RscPayload>({\n root: <App />,\n });\n\n const ssrEntryModule = await import.meta.viteRsc.loadModule<\n typeof import(\"../ssr/entry\")\n >(\"ssr\");\n\n const ssrResult = await ssrEntryModule.renderHTML(rootRscStream, {\n appEntryMarker: marker,\n build: true,\n });\n\n return {\n html: ssrResult.stream,\n appRsc: appRscStream,\n deferRegistry,\n };\n}\n\nexport { defer } from \"./defer\";\n\nif (import.meta.hot) {\n import.meta.hot.accept();\n}\n"],"mappings":";;;;;;;;;AAYA,eAAe,mBAAmB;CAChC,MAAM,QAAQ,MAAM,OAAO,0BAA0B;CACrD,MAAM,OAAO,MAAM,OAAO,yBAAyB;AAInD,KAAI,SAAS,OACX,OAAM,IAAI,MACR,iGACD;AAEH,KAAI,QAAQ,OACV,OAAM,IAAI,MACR,gGACD;AAUH,QAPsB,uBAAmC,EACvD,MACE,oBAAC,kBACC,oBAAC,QAAM,GACF,EAEV,CAAC;;;;;AAOJ,eAAsB,YAA+B;CACnD,MAAM,SAAS,mBAAmB;CAElC,MAAM,gBAAgB,MAAM,kBAAkB;CAK9C,MAAM,YAAY,OAHK,MAAM,OAAO,KAAK,QAAQ,WAE/C,MAAM,EAC+B,WAAW,eAAe;EAC/D,gBAAgB;EAChB,OAAO;EACR,CAAC;AAGF,QAAO,IAAI,SAAS,UAAU,QAAQ;EACpC,QAAQ,UAAU;EAClB,SAAS,EACP,gBAAgB,aACjB;EACF,CAAC;;AAGJ,IAAM,gBAAN,cAA4B,MAAM;CAChC;CACA,YAAY,SAAiB,QAAmB;AAC9C,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,SAAS;;;AAIlB,SAAgB,gBAAgB,OAAwC;AACtE,QAAO,iBAAiB,SAAS,MAAM,SAAS;;;;;AAMlD,eAAsB,SAAS,SAAqC;CAElE,MAAM,WAAW,cADL,IAAI,IAAI,QAAQ,IAAI,CACG,SAAS;AAC5C,KAAI,aAAa,gBAAgB;EAE/B,MAAM,gBAAgB,MAAM,kBAAkB;AAC9C,SAAO,IAAI,SAAS,eAAe;GACjC,QAAQ;GACR,SAAS,EACP,gBAAgB,kCACjB;GACF,CAAC;;CAGJ,MAAM,WAAW,wBAAwB,SAAS;AAClD,KAAI,CAAC,SACH,OAAM,IAAI,cAAc,4BAA4B,YAAY,IAAI;CAGtE,MAAM,QAAQ,cAAc,KAAK,SAAS;AAC1C,KAAI,CAAC,MACH,OAAM,IAAI,cAAc,4BAA4B,YAAY,IAAI;CAEtE,MAAM,EAAE,UAAU;AAClB,SAAQ,MAAM,OAAd;EACE,KAAK,YACH,QAAO,IAAI,SAAS,MAAM,QAAQ;GAChC,QAAQ;GACR,SAAS,EACP,gBAAgB,kCACjB;GACF,CAAC;EAEJ,KAAK,QACH,QAAO,IAAI,SAAS,MAAM,MAAM;GAC9B,QAAQ;GACR,SAAS,EACP,gBAAgB,kCACjB;GACF,CAAC;EAEJ,KAAK,QACH,OAAM,IAAI,cACR,iCAAiC,MAAM,SACvC,IACD;;;;;;AAQP,eAAsB,QAAQ;CAC5B,MAAM,SAAS,mBAAmB;CAElC,MAAM,QAAQ,MAAM,OAAO,0BAA0B;CACrD,MAAM,OAAO,MAAM,OAAO,yBAAyB;CAEnD,MAAM,gBAAgB,uBAAmC,EACvD,MACE,oBAAC,kBACC,oBAAC,UAAK,IAAI,SAAU,GACf,EAEV,CAAC;CAEF,MAAM,eAAe,uBAAmC,EACtD,MAAM,oBAAC,QAAM,EACd,CAAC;AAWF,QAAO;EACL,OANgB,OAJK,MAAM,OAAO,KAAK,QAAQ,WAE/C,MAAM,EAE+B,WAAW,eAAe;GAC/D,gBAAgB;GAChB,OAAO;GACR,CAAC,EAGgB;EAChB,QAAQ;EACR;EACD;;AAKH,IAAI,OAAO,KAAK,IACd,QAAO,KAAK,IAAI,QAAQ"}
|
|
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\";\n\nexport type RscPayload = {\n root: React.ReactNode;\n};\n\nimport { ssr as ssrEnabled } from \"virtual:funstack/config\";\n\nasync function loadEntries() {\n const Root = (await import(\"virtual:funstack/root\")).default;\n const App = (await import(\"virtual:funstack/app\")).default;\n\n // Sanity check; this may happen when user-provided entry file\n // does not have a default export.\n if (Root === undefined) {\n throw new Error(\n \"Failed to load RSC root entry module. Check your entry file to ensure it has a default export.\",\n );\n }\n if (App === undefined) {\n throw new Error(\n \"Failed to load RSC app entry module. Check your entry file to ensure it has a default export.\",\n );\n }\n return { Root, App };\n}\n\n/**\n * Entrypoint to serve HTML response in dev environment\n */\nexport async function serveHTML(): Promise<Response> {\n const timings: string[] = [];\n const marker = generateAppMarker();\n\n const entriesStart = performance.now();\n const { Root, App } = await loadEntries();\n timings.push(`entries;dur=${performance.now() - entriesStart}`);\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: (\n <Root>\n <App />\n </Root>\n ),\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 });\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: (\n <Root>\n <App />\n </Root>\n ),\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\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 * Servers 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 const entriesStart = performance.now();\n const { Root, App } = await loadEntries();\n timings.push(`entries;dur=${performance.now() - entriesStart}`);\n\n const rscStart = performance.now();\n const rootRscStream = renderToReadableStream<RscPayload>({\n root: (\n <Root>\n <App />\n </Root>\n ),\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\n */\nexport async function build() {\n const marker = generateAppMarker();\n const { Root, App } = await loadEntries();\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: (\n <Root>\n <App />\n </Root>\n ),\n });\n appRscStream = renderToReadableStream<RscPayload>({\n root: (\n <Root>\n <App />\n </Root>\n ),\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: <App />,\n });\n }\n\n const ssrEntryModule = await import.meta.viteRsc.loadModule<\n typeof import(\"../ssr/entry\")\n >(\"ssr\");\n\n const ssrResult = await ssrEntryModule.renderHTML(rootRscStream, {\n appEntryMarker: marker,\n build: true,\n ssr: ssrEnabled,\n });\n\n return {\n html: ssrResult.stream,\n appRsc: appRscStream,\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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"}
|