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.
- package/LICENSE +21 -0
- package/README.md +289 -0
- package/dist/build/createBuildConfig.d.ts +12 -0
- package/dist/build/createBuildConfig.d.ts.map +1 -0
- package/dist/build/createBuildConfig.js +55 -0
- package/dist/build/createBuildConfig.js.map +1 -0
- package/dist/checkFilesExist.d.ts +8 -0
- package/dist/checkFilesExist.d.ts.map +1 -0
- package/dist/checkFilesExist.js +61 -0
- package/dist/checkFilesExist.js.map +1 -0
- package/dist/collect-css-manifest.d.ts +4 -0
- package/dist/collect-css-manifest.d.ts.map +1 -0
- package/dist/collect-css-manifest.js +57 -0
- package/dist/collect-css-manifest.js.map +1 -0
- package/dist/components.d.ts +13 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +13 -0
- package/dist/components.js.map +1 -0
- package/dist/copy-dir.d.ts +4 -0
- package/dist/copy-dir.d.ts.map +1 -0
- package/dist/getEnv.d.ts +19 -0
- package/dist/getEnv.d.ts.map +1 -0
- package/dist/getEnv.js +76 -0
- package/dist/getEnv.js.map +1 -0
- package/dist/helpers/normalizedRelativePath.d.ts +9 -0
- package/dist/helpers/normalizedRelativePath.d.ts.map +1 -0
- package/dist/helpers/normalizedRelativePath.js +31 -0
- package/dist/helpers/normalizedRelativePath.js.map +1 -0
- package/dist/helpers/tryManifest.d.ts +8 -0
- package/dist/helpers/tryManifest.d.ts.map +1 -0
- package/dist/html/createPageLoader.d.ts +26 -0
- package/dist/html/createPageLoader.d.ts.map +1 -0
- package/dist/html/createPageLoader.js +70 -0
- package/dist/html/createPageLoader.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +6 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/module-graph.d.ts +10 -0
- package/dist/module-graph.d.ts.map +1 -0
- package/dist/options.d.ts +86 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +251 -0
- package/dist/options.js.map +1 -0
- package/dist/plugin.d.ts +8 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +31 -0
- package/dist/plugin.js.map +1 -0
- package/dist/react-client/plugin.d.ts +4 -0
- package/dist/react-client/plugin.d.ts.map +1 -0
- package/dist/react-client/plugin.js +28 -0
- package/dist/react-client/plugin.js.map +1 -0
- package/dist/react-server/createDevMiddleware.d.ts +8 -0
- package/dist/react-server/createDevMiddleware.d.ts.map +1 -0
- package/dist/react-server/createDevServer.d.ts +4 -0
- package/dist/react-server/createDevServer.d.ts.map +1 -0
- package/dist/react-server/createHandler.d.ts +23 -0
- package/dist/react-server/createHandler.d.ts.map +1 -0
- package/dist/react-server/createHandler.js +110 -0
- package/dist/react-server/createHandler.js.map +1 -0
- package/dist/react-server/createReactNodeStreamer.d.ts +10 -0
- package/dist/react-server/createReactNodeStreamer.d.ts.map +1 -0
- package/dist/react-server/createRscStream.d.ts +4 -0
- package/dist/react-server/createRscStream.d.ts.map +1 -0
- package/dist/react-server/createRscStream.js +47 -0
- package/dist/react-server/createRscStream.js.map +1 -0
- package/dist/react-server/createSsrHandler.d.ts +4 -0
- package/dist/react-server/createSsrHandler.d.ts.map +1 -0
- package/dist/react-server/plugin.d.ts +8 -0
- package/dist/react-server/plugin.d.ts.map +1 -0
- package/dist/react-server/plugin.js +298 -0
- package/dist/react-server/plugin.js.map +1 -0
- package/dist/resolvePage.d.ts +19 -0
- package/dist/resolvePage.d.ts.map +1 -0
- package/dist/resolvePage.js +44 -0
- package/dist/resolvePage.js.map +1 -0
- package/dist/resolveProps.d.ts +19 -0
- package/dist/resolveProps.d.ts.map +1 -0
- package/dist/resolveProps.js +90 -0
- package/dist/resolveProps.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/transformer/index.d.ts +28 -0
- package/dist/transformer/index.d.ts.map +1 -0
- package/dist/transformer/index.js +54 -0
- package/dist/transformer/index.js.map +1 -0
- package/dist/transformer/preserveDirectives.d.ts +4 -0
- package/dist/transformer/preserveDirectives.d.ts.map +1 -0
- package/dist/transformer/preserveDirectives.js +72 -0
- package/dist/transformer/preserveDirectives.js.map +1 -0
- package/dist/transformer/preserver.d.ts +2 -0
- package/dist/transformer/preserver.d.ts.map +1 -0
- package/dist/transformer/transformer.d.ts +30 -0
- package/dist/transformer/transformer.d.ts.map +1 -0
- package/dist/transformer/transformer.js +80 -0
- package/dist/transformer/transformer.js.map +1 -0
- package/dist/transformer/types.d.ts +15 -0
- package/dist/transformer/types.d.ts.map +1 -0
- package/dist/types.d.ts +197 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/worker/createHtmlStream.d.ts +7 -0
- package/dist/worker/createHtmlStream.d.ts.map +1 -0
- package/dist/worker/createWorker.d.ts +3 -0
- package/dist/worker/createWorker.d.ts.map +1 -0
- package/dist/worker/createWorker.js +33 -0
- package/dist/worker/createWorker.js.map +1 -0
- package/dist/worker/loader.d.ts +15 -0
- package/dist/worker/loader.d.ts.map +1 -0
- package/dist/worker/renderPages.d.ts +18 -0
- package/dist/worker/renderPages.d.ts.map +1 -0
- package/dist/worker/renderPages.js +99 -0
- package/dist/worker/renderPages.js.map +1 -0
- package/dist/worker/types.d.ts +31 -0
- package/dist/worker/types.d.ts.map +1 -0
- package/dist/worker/worker.d.ts +7 -0
- package/dist/worker/worker.d.ts.map +1 -0
- package/package.json +116 -0
- package/src/build/createBuildConfig.ts +74 -0
- package/src/checkFilesExist.ts +67 -0
- package/src/collect-css-manifest.ts +76 -0
- package/src/components.tsx +14 -0
- package/src/copy-dir.ts +27 -0
- package/src/getEnv.ts +135 -0
- package/src/helpers/normalizedRelativePath.ts +59 -0
- package/src/helpers/tryManifest.ts +23 -0
- package/src/html/createPageLoader.ts +99 -0
- package/src/index.ts +4 -0
- package/src/manifest.ts +24 -0
- package/src/module-graph.ts +48 -0
- package/src/options.ts +351 -0
- package/src/plugin.ts +31 -0
- package/src/react-client/plugin.ts +34 -0
- package/src/react-server/createDevMiddleware.ts +75 -0
- package/src/react-server/createDevServer.ts +10 -0
- package/src/react-server/createHandler.ts +144 -0
- package/src/react-server/createReactNodeStreamer.ts +25 -0
- package/src/react-server/createRscStream.ts +52 -0
- package/src/react-server/createSsrHandler.ts +147 -0
- package/src/react-server/plugin.ts +349 -0
- package/src/resolvePage.ts +65 -0
- package/src/resolveProps.ts +122 -0
- package/src/server.tsx +0 -0
- package/src/transformer/README.md +44 -0
- package/src/transformer/index.ts +112 -0
- package/src/transformer/preserveDirectives.ts +100 -0
- package/src/transformer/preserver.ts +47 -0
- package/src/transformer/transformer.ts +123 -0
- package/src/transformer/types.ts +15 -0
- package/src/types.ts +245 -0
- package/src/worker/createHtmlStream.ts +76 -0
- package/src/worker/createWorker.ts +39 -0
- package/src/worker/loader.ts +16 -0
- package/src/worker/renderPages.ts +144 -0
- package/src/worker/types.ts +38 -0
- package/src/worker/worker.tsx +136 -0
- 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
|
+
}
|