vite-plugin-react-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +289 -0
  3. package/dist/build/createBuildConfig.d.ts +12 -0
  4. package/dist/build/createBuildConfig.d.ts.map +1 -0
  5. package/dist/build/createBuildConfig.js +55 -0
  6. package/dist/build/createBuildConfig.js.map +1 -0
  7. package/dist/checkFilesExist.d.ts +8 -0
  8. package/dist/checkFilesExist.d.ts.map +1 -0
  9. package/dist/checkFilesExist.js +61 -0
  10. package/dist/checkFilesExist.js.map +1 -0
  11. package/dist/collect-css-manifest.d.ts +4 -0
  12. package/dist/collect-css-manifest.d.ts.map +1 -0
  13. package/dist/collect-css-manifest.js +57 -0
  14. package/dist/collect-css-manifest.js.map +1 -0
  15. package/dist/components.d.ts +13 -0
  16. package/dist/components.d.ts.map +1 -0
  17. package/dist/components.js +13 -0
  18. package/dist/components.js.map +1 -0
  19. package/dist/copy-dir.d.ts +4 -0
  20. package/dist/copy-dir.d.ts.map +1 -0
  21. package/dist/getEnv.d.ts +19 -0
  22. package/dist/getEnv.d.ts.map +1 -0
  23. package/dist/getEnv.js +76 -0
  24. package/dist/getEnv.js.map +1 -0
  25. package/dist/helpers/normalizedRelativePath.d.ts +9 -0
  26. package/dist/helpers/normalizedRelativePath.d.ts.map +1 -0
  27. package/dist/helpers/normalizedRelativePath.js +31 -0
  28. package/dist/helpers/normalizedRelativePath.js.map +1 -0
  29. package/dist/helpers/tryManifest.d.ts +8 -0
  30. package/dist/helpers/tryManifest.d.ts.map +1 -0
  31. package/dist/html/createPageLoader.d.ts +26 -0
  32. package/dist/html/createPageLoader.d.ts.map +1 -0
  33. package/dist/html/createPageLoader.js +70 -0
  34. package/dist/html/createPageLoader.js.map +1 -0
  35. package/dist/index.d.ts +3 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +5 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/manifest.d.ts +6 -0
  40. package/dist/manifest.d.ts.map +1 -0
  41. package/dist/module-graph.d.ts +10 -0
  42. package/dist/module-graph.d.ts.map +1 -0
  43. package/dist/options.d.ts +86 -0
  44. package/dist/options.d.ts.map +1 -0
  45. package/dist/options.js +251 -0
  46. package/dist/options.js.map +1 -0
  47. package/dist/plugin.d.ts +8 -0
  48. package/dist/plugin.d.ts.map +1 -0
  49. package/dist/plugin.js +31 -0
  50. package/dist/plugin.js.map +1 -0
  51. package/dist/react-client/plugin.d.ts +4 -0
  52. package/dist/react-client/plugin.d.ts.map +1 -0
  53. package/dist/react-client/plugin.js +28 -0
  54. package/dist/react-client/plugin.js.map +1 -0
  55. package/dist/react-server/createDevMiddleware.d.ts +8 -0
  56. package/dist/react-server/createDevMiddleware.d.ts.map +1 -0
  57. package/dist/react-server/createDevServer.d.ts +4 -0
  58. package/dist/react-server/createDevServer.d.ts.map +1 -0
  59. package/dist/react-server/createHandler.d.ts +23 -0
  60. package/dist/react-server/createHandler.d.ts.map +1 -0
  61. package/dist/react-server/createHandler.js +110 -0
  62. package/dist/react-server/createHandler.js.map +1 -0
  63. package/dist/react-server/createReactNodeStreamer.d.ts +10 -0
  64. package/dist/react-server/createReactNodeStreamer.d.ts.map +1 -0
  65. package/dist/react-server/createRscStream.d.ts +4 -0
  66. package/dist/react-server/createRscStream.d.ts.map +1 -0
  67. package/dist/react-server/createRscStream.js +47 -0
  68. package/dist/react-server/createRscStream.js.map +1 -0
  69. package/dist/react-server/createSsrHandler.d.ts +4 -0
  70. package/dist/react-server/createSsrHandler.d.ts.map +1 -0
  71. package/dist/react-server/plugin.d.ts +8 -0
  72. package/dist/react-server/plugin.d.ts.map +1 -0
  73. package/dist/react-server/plugin.js +298 -0
  74. package/dist/react-server/plugin.js.map +1 -0
  75. package/dist/resolvePage.d.ts +19 -0
  76. package/dist/resolvePage.d.ts.map +1 -0
  77. package/dist/resolvePage.js +44 -0
  78. package/dist/resolvePage.js.map +1 -0
  79. package/dist/resolveProps.d.ts +19 -0
  80. package/dist/resolveProps.d.ts.map +1 -0
  81. package/dist/resolveProps.js +90 -0
  82. package/dist/resolveProps.js.map +1 -0
  83. package/dist/server.d.ts +2 -0
  84. package/dist/server.d.ts.map +1 -0
  85. package/dist/transformer/index.d.ts +28 -0
  86. package/dist/transformer/index.d.ts.map +1 -0
  87. package/dist/transformer/index.js +54 -0
  88. package/dist/transformer/index.js.map +1 -0
  89. package/dist/transformer/preserveDirectives.d.ts +4 -0
  90. package/dist/transformer/preserveDirectives.d.ts.map +1 -0
  91. package/dist/transformer/preserveDirectives.js +72 -0
  92. package/dist/transformer/preserveDirectives.js.map +1 -0
  93. package/dist/transformer/preserver.d.ts +2 -0
  94. package/dist/transformer/preserver.d.ts.map +1 -0
  95. package/dist/transformer/transformer.d.ts +30 -0
  96. package/dist/transformer/transformer.d.ts.map +1 -0
  97. package/dist/transformer/transformer.js +80 -0
  98. package/dist/transformer/transformer.js.map +1 -0
  99. package/dist/transformer/types.d.ts +15 -0
  100. package/dist/transformer/types.d.ts.map +1 -0
  101. package/dist/types.d.ts +197 -0
  102. package/dist/types.d.ts.map +1 -0
  103. package/dist/worker/createHtmlStream.d.ts +7 -0
  104. package/dist/worker/createHtmlStream.d.ts.map +1 -0
  105. package/dist/worker/createWorker.d.ts +3 -0
  106. package/dist/worker/createWorker.d.ts.map +1 -0
  107. package/dist/worker/createWorker.js +33 -0
  108. package/dist/worker/createWorker.js.map +1 -0
  109. package/dist/worker/loader.d.ts +15 -0
  110. package/dist/worker/loader.d.ts.map +1 -0
  111. package/dist/worker/renderPages.d.ts +18 -0
  112. package/dist/worker/renderPages.d.ts.map +1 -0
  113. package/dist/worker/renderPages.js +99 -0
  114. package/dist/worker/renderPages.js.map +1 -0
  115. package/dist/worker/types.d.ts +31 -0
  116. package/dist/worker/types.d.ts.map +1 -0
  117. package/dist/worker/worker.d.ts +7 -0
  118. package/dist/worker/worker.d.ts.map +1 -0
  119. package/package.json +116 -0
  120. package/src/build/createBuildConfig.ts +74 -0
  121. package/src/checkFilesExist.ts +67 -0
  122. package/src/collect-css-manifest.ts +76 -0
  123. package/src/components.tsx +14 -0
  124. package/src/copy-dir.ts +27 -0
  125. package/src/getEnv.ts +135 -0
  126. package/src/helpers/normalizedRelativePath.ts +59 -0
  127. package/src/helpers/tryManifest.ts +23 -0
  128. package/src/html/createPageLoader.ts +99 -0
  129. package/src/index.ts +4 -0
  130. package/src/manifest.ts +24 -0
  131. package/src/module-graph.ts +48 -0
  132. package/src/options.ts +351 -0
  133. package/src/plugin.ts +31 -0
  134. package/src/react-client/plugin.ts +34 -0
  135. package/src/react-server/createDevMiddleware.ts +75 -0
  136. package/src/react-server/createDevServer.ts +10 -0
  137. package/src/react-server/createHandler.ts +144 -0
  138. package/src/react-server/createReactNodeStreamer.ts +25 -0
  139. package/src/react-server/createRscStream.ts +52 -0
  140. package/src/react-server/createSsrHandler.ts +147 -0
  141. package/src/react-server/plugin.ts +349 -0
  142. package/src/resolvePage.ts +65 -0
  143. package/src/resolveProps.ts +122 -0
  144. package/src/server.tsx +0 -0
  145. package/src/transformer/README.md +44 -0
  146. package/src/transformer/index.ts +112 -0
  147. package/src/transformer/preserveDirectives.ts +100 -0
  148. package/src/transformer/preserver.ts +47 -0
  149. package/src/transformer/transformer.ts +123 -0
  150. package/src/transformer/types.ts +15 -0
  151. package/src/types.ts +245 -0
  152. package/src/worker/createHtmlStream.ts +76 -0
  153. package/src/worker/createWorker.ts +39 -0
  154. package/src/worker/loader.ts +16 -0
  155. package/src/worker/renderPages.ts +144 -0
  156. package/src/worker/types.ts +38 -0
  157. package/src/worker/worker.tsx +136 -0
  158. package/tsconfig.json +79 -0
@@ -0,0 +1,100 @@
1
+ import type { Node } from "estree";
2
+ import type { Plugin } from "rollup";
3
+ import { SourceMapGenerator } from "source-map";
4
+ import { DEFAULT_CONFIG } from "../options.js";
5
+ import type { Options } from "../types.js";
6
+
7
+ const REACT_DIRECTIVES = new Set(["use client", "use server", "use no-memo"]);
8
+
9
+ interface PreserveDirectiveMeta {
10
+ directives: Record<string, Set<string>>;
11
+ }
12
+
13
+ export function preserveDirectives(options?: Pick<Options, "include">): Plugin {
14
+ const meta: PreserveDirectiveMeta = {
15
+ directives: {},
16
+ };
17
+ const fileRegex = options?.include ?? DEFAULT_CONFIG.FILE_REGEX;
18
+
19
+ return {
20
+ name: "react-preserve-directives",
21
+ transform: {
22
+ order: "post",
23
+ handler(code, id) {
24
+ if (!fileRegex.test(id)) {
25
+ return null;
26
+ }
27
+
28
+ const ast = this.parse(code) as Node;
29
+ if (ast.type !== "Program") {
30
+ return null;
31
+ }
32
+
33
+ let hasDirectives = false;
34
+ const directives = new Set<string>();
35
+
36
+ // Look for directives at start of file
37
+ for (const node of ast.body) {
38
+ if (node.type !== "ExpressionStatement") {
39
+ break;
40
+ }
41
+
42
+ if (
43
+ node.expression.type === "Literal" &&
44
+ typeof node.expression.value === "string" &&
45
+ REACT_DIRECTIVES.has(node.expression.value)
46
+ ) {
47
+ directives.add(node.expression.value);
48
+ hasDirectives = true;
49
+ }
50
+ }
51
+
52
+ if (!hasDirectives) return null;
53
+
54
+ meta.directives[id] = directives;
55
+
56
+ // Generate source map
57
+ const map = new SourceMapGenerator({
58
+ file: id,
59
+ sourceRoot: "",
60
+ });
61
+ map.setSourceContent(id, code);
62
+
63
+ return {
64
+ code,
65
+ map: map.toString(),
66
+ meta: { directives: Array.from(directives) },
67
+ };
68
+ },
69
+ },
70
+
71
+ renderChunk(code, chunk) {
72
+ const moduleIds = chunk.moduleIds;
73
+ const chunkDirectives = moduleIds
74
+ .map((id) => meta.directives[id])
75
+ .filter((dirs): dirs is Set<string> => !!dirs)
76
+ .reduce((acc, dirs) => {
77
+ dirs.forEach((d) => acc.add(d));
78
+ return acc;
79
+ }, new Set<string>());
80
+
81
+ if (chunkDirectives.size) {
82
+ const directiveCode = Array.from(chunkDirectives)
83
+ .map((d) => `"${d}";`)
84
+ .join("\n");
85
+ return {
86
+ code: `${directiveCode}\n${code}`,
87
+ };
88
+ }
89
+
90
+ return null;
91
+ },
92
+
93
+ onLog(level, log) {
94
+ if (log.code === "MODULE_LEVEL_DIRECTIVE" && level === "warn") {
95
+ return false;
96
+ }
97
+ return this.warn(log);
98
+ },
99
+ };
100
+ }
@@ -0,0 +1,47 @@
1
+ interface RSCChunk {
2
+ id: number;
3
+ type?: string;
4
+ content: any;
5
+ timing?: number;
6
+ }
7
+
8
+ export function preserveRSC(chunk: string): string {
9
+ // Try to parse RSC format: <id>:<content>
10
+ const match = chunk.match(/^(\d+):(.+)$/);
11
+ if (!match) return chunk;
12
+
13
+ const [_, id, content] = match;
14
+
15
+ // Handle different chunk types
16
+ if (content.startsWith('"$S')) {
17
+ // Fragment type
18
+ return chunk;
19
+ }
20
+
21
+ if (content.startsWith("I[")) {
22
+ // Import type
23
+ return chunk;
24
+ }
25
+
26
+ try {
27
+ // Try to parse as array/object
28
+ const parsed = JSON.parse(content);
29
+
30
+ // Check if this is a component chunk
31
+ if (Array.isArray(parsed) && parsed[2] === "html") {
32
+ const componentChunk = {
33
+ name: "Page",
34
+ env: "Server",
35
+ key: "page",
36
+ owner: null,
37
+ stack: [],
38
+ props: parsed[3],
39
+ };
40
+ return `${id}:${JSON.stringify(componentChunk)}\n:N${Date.now()}`;
41
+ }
42
+
43
+ return chunk;
44
+ } catch {
45
+ return chunk;
46
+ }
47
+ }
@@ -0,0 +1,123 @@
1
+ import { SourceMapGenerator } from "source-map";
2
+ import type { TransformerOptions } from "./types.js";
3
+
4
+ export function createRscTransformer(options: TransformerOptions) {
5
+ return {
6
+ name: "vite:react-stream-transformer",
7
+ enforce: "post" as const,
8
+
9
+ async transform(
10
+ code: string,
11
+ path: string,
12
+ { ssr }: { inMap: null | string; ssr: boolean }
13
+ ) {
14
+ try {
15
+ let transformedCode = code;
16
+ const directiveMatch = code.match(/^["']use client["'];?/);
17
+ const moduleId = options.moduleId(path, ssr);
18
+
19
+ // Log the path transformation
20
+ console.log("[RSC Transform] Module path transformation:", {
21
+ original: path,
22
+ transformed: moduleId,
23
+ });
24
+
25
+ // Find all named exports
26
+ const exportMatches = Array.from(
27
+ code.matchAll(/export\s+(?:const|let|var|function|class)\s+(\w+)/g)
28
+ );
29
+
30
+ if (!exportMatches.length) {
31
+ console.warn(`[RSC] No exports found in client component: ${path}`);
32
+ return null;
33
+ }
34
+
35
+ // Transform each export
36
+ for (const [fullMatch, exportName] of exportMatches) {
37
+ if (!exportName) {
38
+ console.warn(`[RSC] Invalid export in client component: ${path}`);
39
+ continue;
40
+ }
41
+
42
+ const isClass = fullMatch.includes("class");
43
+
44
+ // Remove export keyword
45
+ transformedCode = transformedCode.replace(
46
+ fullMatch,
47
+ fullMatch.replace("export ", "")
48
+ );
49
+
50
+ if (!directiveMatch || directiveMatch.index !== 0) {
51
+ // Server component
52
+ transformedCode += `
53
+ const ${exportName}Ref = Object.defineProperties(
54
+ ${
55
+ isClass
56
+ ? `class extends ${exportName} {
57
+ constructor(...args) { super(...args); }
58
+ }`
59
+ : `function(...args) { return ${exportName}.apply(null, args); }`
60
+ },
61
+ {
62
+ $$typeof: { value: Symbol.for("react.server.reference") },
63
+ $$id: { value: ${JSON.stringify(moduleId + "#" + exportName)} },
64
+ $$filepath: { value: ${JSON.stringify(path)} },
65
+ $$async: { value: true }
66
+ }
67
+ );
68
+ export { ${exportName}Ref as ${exportName} };
69
+ `;
70
+ } else {
71
+ // Client component
72
+ transformedCode += `
73
+ const ${exportName}Ref = Object.defineProperties(
74
+ ${
75
+ isClass
76
+ ? `class extends ${exportName} {
77
+ constructor(...args) { super(...args); }
78
+ }`
79
+ : `function(...args) { return ${exportName}.apply(null, args); }`
80
+ },
81
+ {
82
+ $$typeof: { value: Symbol.for("react.client.reference") },
83
+ $$id: { value: ${JSON.stringify(moduleId + "#" + exportName)} },
84
+ $$filepath: { value: ${JSON.stringify(path)} }
85
+ }
86
+ );
87
+ export { ${exportName}Ref as ${exportName} };
88
+ `;
89
+ }
90
+ }
91
+
92
+ return {
93
+ code: transformedCode,
94
+ map: new SourceMapGenerator({ file: path }).toString(),
95
+ };
96
+ } catch (error) {
97
+ console.error(
98
+ `[RSC] Error transforming client component: ${path}`,
99
+ error
100
+ );
101
+ throw error;
102
+ }
103
+ },
104
+ };
105
+ }
106
+
107
+ /**
108
+ * transformedCode += `
109
+ const ${exportName}Ref = Object.defineProperties(
110
+ ${
111
+ isClass
112
+ ? `class extends ${exportName} {
113
+ constructor(...args) { super(...args); }
114
+ }`
115
+ : `function(...args) { return ${exportName}.apply(null, args); }`
116
+ },
117
+ {
118
+ $$typeof: { value: Symbol.for("react.client.reference") },
119
+ $$id: { value: ${JSON.stringify(moduleId + "#" + exportName)} }
120
+ }
121
+ );
122
+ export { ${exportName}Ref as ${exportName} };`;
123
+ */
@@ -0,0 +1,15 @@
1
+ export interface ViteReactClientTransformOptions {
2
+ projectRoot?: string;
3
+ moduleId?: (path: string, ssr: boolean) => string;
4
+ validateModuleId?: (moduleId: string) => boolean;
5
+ include?: string | RegExp | (string | RegExp)[];
6
+ exclude?: string | RegExp | (string | RegExp)[];
7
+ }
8
+
9
+ export interface TransformerOptions {
10
+ moduleId: (path: string, ssr: boolean) => string;
11
+ /**
12
+ * Optional validation function for module IDs
13
+ */
14
+ validateModuleId?: (moduleId: string) => boolean;
15
+ }
package/src/types.ts ADDED
@@ -0,0 +1,245 @@
1
+ import type {
2
+ AliasOptions,
3
+ Connect,
4
+ Logger,
5
+ Manifest,
6
+ ModuleGraph,
7
+ ViteDevServer,
8
+ } from "vite";
9
+
10
+ export interface StreamPluginOptionsClient {
11
+ outDir?: string;
12
+ build?: BuildConfig;
13
+ assetsDir?: string;
14
+ projectRoot?: string;
15
+ moduleBase?: string;
16
+ moduleBasePath?: string;
17
+ moduleBaseURL?: string;
18
+ clientComponents?: AliasOptions;
19
+ cssFiles?: AliasOptions;
20
+ }
21
+
22
+ export type ResolvedUserOptions = Required<
23
+ Pick<
24
+ StreamPluginOptions,
25
+ | "moduleBase"
26
+ | "moduleBasePath"
27
+ | "moduleBaseURL"
28
+ | "projectRoot"
29
+ | "build"
30
+ | "Page"
31
+ | "props"
32
+ | "Html"
33
+ | "pageExportName"
34
+ | "propsExportName"
35
+ | "collectCss"
36
+ | "collectAssets"
37
+ | "assetsDir"
38
+ >
39
+ > & { build: NonNullable<Required<StreamPluginOptions["build"]>> };
40
+
41
+ export interface StreamPluginOptions {
42
+ projectRoot?: string;
43
+ assetsDir?: string;
44
+ moduleBase?: string;
45
+ moduleBasePath?: string;
46
+ moduleBaseURL?: string;
47
+ clientEntry?: string;
48
+ serverOutDir?: string;
49
+ clientOutDir?: string;
50
+ // Auto-discovery (zero-config)
51
+ autoDiscover?: {
52
+ pagePattern?: string;
53
+ propsPattern?: string;
54
+ };
55
+ // Manual configuration
56
+ Page: string | ((url: string) => string);
57
+ props?: undefined | string | ((url: string) => string);
58
+ // Escape hatches
59
+ workerPath?: string;
60
+ loaderPath?: string;
61
+ pageExportName?: string;
62
+ propsExportName?: string;
63
+ Html?: React.FC<{
64
+ manifest: Manifest;
65
+ pageProps: any;
66
+ route: string;
67
+ url: string;
68
+ children: React.ReactNode;
69
+ }>;
70
+ collectCss?: boolean;
71
+ collectAssets?: boolean;
72
+ build?: BuildConfig;
73
+ }
74
+
75
+ export interface CreateHandlerOptions<T = any> {
76
+ loader: (id: string) => Promise<T>;
77
+ manifest?: Manifest;
78
+ moduleGraph?: ModuleGraph;
79
+ cssFiles?: string[];
80
+ onCssFile?: (path: string) => void;
81
+ logger?: Logger;
82
+ pipableStreamOptions?: any;
83
+ }
84
+
85
+ export type ModuleLoader = (
86
+ url: string,
87
+ context?: any,
88
+ defaultLoad?: any
89
+ ) => Promise<Record<string, any>>;
90
+
91
+ export interface BaseProps {
92
+ manifest: Manifest;
93
+ children?: React.ReactNode;
94
+ assets?: {
95
+ css?: string[];
96
+ };
97
+ }
98
+
99
+ export type StreamResult =
100
+ | {
101
+ type: "success";
102
+ stream: any;
103
+ assets?: {
104
+ css?: string[];
105
+ };
106
+ }
107
+ | { type: "error"; error: unknown }
108
+ | { type: "skip" };
109
+
110
+ export interface RscStreamOptions {
111
+ Page: React.ComponentType;
112
+ props: any;
113
+ Html: any;
114
+ logger?: Console | Logger;
115
+ cssFiles?: string[];
116
+ route: string;
117
+ url: string;
118
+ pipableStreamOptions?: any;
119
+ moduleBasePath: string;
120
+ }
121
+
122
+ export interface RouteConfig {
123
+ path: string;
124
+ // Define page/props paths using patterns
125
+ pattern?: {
126
+ page?: string; // e.g. "page/_theme/[route]/page"
127
+ props?: string; // e.g. "page/_theme/[route]/props"
128
+ };
129
+ // Or use explicit paths
130
+ paths?: {
131
+ page: string; // e.g. "page/home/page"
132
+ props: string; // e.g. "page/home/props"
133
+ };
134
+ }
135
+
136
+ export interface BuildOutput {
137
+ dir?: string;
138
+ rsc?: string;
139
+ ext?: string;
140
+ }
141
+
142
+ export interface BuildConfig {
143
+ pages: string[] | (() => Promise<string[]> | string[]);
144
+ client?: string; // Output directory for client files
145
+ server?: string; // Output directory for server files
146
+ }
147
+
148
+ export interface RscResolver {
149
+ /**
150
+ * Get RSC data for static generation
151
+ * @param path - Route path (e.g. "/", "/about")
152
+ */
153
+ getRscData: (path: string) => Promise<{
154
+ Page: React.ComponentType;
155
+ props: any;
156
+ }>;
157
+ }
158
+
159
+ export interface Options {
160
+ include?: RegExp;
161
+ projectRoot?: string;
162
+ moduleBase: string;
163
+ // can be inferred from moduleBase, will add / to moduleBase by default (if not already present)
164
+ moduleBasePath?: string;
165
+ Html?: React.ComponentType<React.PropsWithChildren<{ manifest: Manifest }>>;
166
+ Page: string | ((url: string) => string);
167
+ props?: string | ((url: string) => string);
168
+ pageExportName?: string;
169
+ propsExportName?: string;
170
+ collectCss?: boolean;
171
+ collectAssets?: boolean;
172
+ emitCss?: boolean;
173
+ moduleLoader?: (server: ViteDevServer) => ModuleLoader;
174
+ build?: BuildConfig;
175
+ outDir?: string; // defaults to 'dist'
176
+ /**
177
+ * Configure static asset copying
178
+ * - true: Copy all assets
179
+ * - false: Don't copy assets
180
+ * - Function: Custom filter for which files to copy
181
+ */
182
+ copyAssets?: boolean | ((file: string) => boolean);
183
+ }
184
+
185
+ export type RequestHandler = Connect.NextHandleFunction;
186
+
187
+ export interface SsrStreamOptions {
188
+ url: string;
189
+ controller: AbortController;
190
+ loader: (id: string) => Promise<any>;
191
+ Html: any;
192
+ options: StreamPluginOptions;
193
+ pageExportName: string;
194
+ propsExportName: string;
195
+ moduleGraph: any;
196
+ bootstrapModules?: string[];
197
+ importMap?: Record<string, string[]>;
198
+ clientComponents?: boolean;
199
+ onlyClientComponents?: boolean;
200
+ }
201
+
202
+ export type RscServerConfig = {
203
+ /** How to get RSC data (e.g. HTTP, direct import, etc) */
204
+ getRscComponent: (url: string) => React.Usable<React.ReactNode>;
205
+ /** Base URL for client assets */
206
+ clientBase?: string;
207
+ /** SSR stream rendering options */
208
+ ssrOptions?: SsrStreamOptions;
209
+ };
210
+
211
+ export interface RscServerModule {
212
+ /**
213
+ * Get RSC data for a route
214
+ * @param path - Route path (e.g. "/", "/about")
215
+ * @returns Page component and props
216
+ */
217
+ getRscData: (path: string) => Promise<{
218
+ /** Page component to render */
219
+ Page: React.ComponentType;
220
+ /** Props to pass to the page */
221
+ props: any;
222
+ }>;
223
+ }
224
+
225
+ export interface RegisterComponentMessage {
226
+ type: "REGISTER_COMPONENT";
227
+ id: string;
228
+ code: string;
229
+ }
230
+
231
+ export type RscBuildResult = string[];
232
+
233
+ export interface ReactStreamPluginMeta {
234
+ timing: BuildTiming;
235
+ }
236
+
237
+ export interface BuildTiming {
238
+ start: number;
239
+ configResolved?: number;
240
+ buildStart?: number;
241
+ buildEnd?: number;
242
+ renderStart?: number;
243
+ renderEnd?: number;
244
+ total?: number;
245
+ }
@@ -0,0 +1,76 @@
1
+ import { createWriteStream } from "node:fs";
2
+ import { Readable, Writable } from "node:stream";
3
+ import { parentPort } from "node:worker_threads";
4
+ import { renderToPipeableStream } from "react-dom/server";
5
+ import { createFromNodeStream } from "react-server-dom-esm/client.node";
6
+ import type { RenderState } from "./types.js";
7
+
8
+ const concatter = (chunk: string) => {
9
+ if (Array.isArray(chunk)) {
10
+ return Buffer.from(chunk);
11
+ }
12
+ return Buffer.from(chunk);
13
+ };
14
+
15
+ export function createHtmlStream(
16
+ renderState: RenderState,
17
+ writeStream: Writable
18
+ ) {
19
+ const outputPath = renderState.htmlOutputPath;
20
+
21
+ // Create readable stream from RSC content
22
+ const rscStream = Readable.from(renderState.chunks.map(concatter));
23
+
24
+ // Create RSC node stream
25
+ const reactElements = createFromNodeStream(
26
+ rscStream,
27
+ renderState.moduleBasePath,
28
+ renderState.moduleBaseURL
29
+ );
30
+ // rsc file destination follows the same path as the html file, but with a .rsc extension
31
+ const rscOutputPath = renderState.htmlOutputPath.endsWith(".html")
32
+ ? renderState.htmlOutputPath.slice(0, -5) + ".rsc"
33
+ : renderState.htmlOutputPath.endsWith("/")
34
+ ? renderState.htmlOutputPath + "index.rsc"
35
+ : renderState.htmlOutputPath.endsWith(".")
36
+ ? renderState.htmlOutputPath + "rsc"
37
+ : renderState.htmlOutputPath + ".rsc";
38
+
39
+ const writeRscEntry = createWriteStream(rscOutputPath);
40
+ rscStream.on("data", (chunk) => {
41
+ writeRscEntry.write(chunk);
42
+ });
43
+ rscStream.on("end", () => {
44
+ writeRscEntry.end();
45
+ });
46
+ const stream = renderToPipeableStream(reactElements as React.ReactNode, {
47
+ ...renderState.pipableStreamOptions,
48
+ onAllReady() {
49
+ writeStream.on("finish", () => {
50
+ parentPort?.postMessage({
51
+ type: "HTML",
52
+ outputPath,
53
+ route: renderState.id,
54
+ });
55
+ });
56
+ writeStream.on("error", (error) => {
57
+ console.error("[Worker] Write error at", error);
58
+ stream.abort();
59
+ });
60
+ },
61
+ onShellReady() {
62
+ stream.pipe(writeStream);
63
+ },
64
+ onError(error) {
65
+ console.error("[Worker] Render error at", error);
66
+ stream.abort();
67
+ writeStream.destroy();
68
+ },
69
+ onShellError(error) {
70
+ console.error("[Worker] Shell error at", error);
71
+ stream.abort();
72
+ writeStream.destroy();
73
+ },
74
+ });
75
+ return { stream, writeStream };
76
+ }
@@ -0,0 +1,39 @@
1
+ import { resolve } from "node:path";
2
+ import { Worker } from "node:worker_threads";
3
+
4
+ export async function createWorker(
5
+ projectRoot: string,
6
+ outDir: string,
7
+ fileName: string,
8
+ mode: "production" | "development"
9
+ ) {
10
+ console.log("[Worker] Creating worker...");
11
+ const workerPath = resolve(projectRoot, outDir, fileName);
12
+ console.log("[Worker] Worker path:", workerPath);
13
+
14
+ try {
15
+ const worker = new Worker(workerPath, {
16
+ env: {
17
+ NODE_OPTIONS: "",
18
+ NODE_PATH: resolve(projectRoot, "node_modules"),
19
+ NODE_ENV: mode,
20
+ },
21
+ });
22
+ worker.setMaxListeners(1000);
23
+
24
+ // Wait for worker to be ready
25
+ await new Promise<void>((resolve, reject) => {
26
+ worker.once("message", (message) => {
27
+ if (message.type === "READY") {
28
+ resolve();
29
+ }
30
+ });
31
+ worker.once("error", reject);
32
+ });
33
+
34
+ return worker;
35
+ } catch (error) {
36
+ console.error("[Worker] Startup error:", error);
37
+ throw error;
38
+ }
39
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Extension point for custom module loading in the worker thread.
3
+ * This file can be overridden via the plugin options:
4
+ *
5
+ * ```ts
6
+ * reactStreamPlugin({
7
+ * loaderPath: './my-custom-loader.ts'
8
+ * })
9
+ * ```
10
+ *
11
+ * The default loader provides basic module loading functionality.
12
+ * Override this if you need custom module resolution or transformation.
13
+ */
14
+ export function load(id: string) {
15
+ return import(id);
16
+ }