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,75 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from "http";
|
|
2
|
+
import type { ViteDevServer } from "vite";
|
|
3
|
+
import { type RequestHandler, type StreamPluginOptions } from "../types.js";
|
|
4
|
+
import { createHandler } from "./createHandler.js";
|
|
5
|
+
|
|
6
|
+
export type DevMiddlewareOptions = Required<
|
|
7
|
+
Pick<
|
|
8
|
+
StreamPluginOptions,
|
|
9
|
+
"moduleBase" | "moduleBasePath" | "moduleBaseURL" | "projectRoot"
|
|
10
|
+
>
|
|
11
|
+
> &
|
|
12
|
+
Pick<StreamPluginOptions, "Page" | "props" | "build" | "Html" | "pageExportName" | "propsExportName">;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Creates a request handler for development
|
|
16
|
+
*/
|
|
17
|
+
export function createDevMiddleware(
|
|
18
|
+
server: ViteDevServer,
|
|
19
|
+
options: DevMiddlewareOptions
|
|
20
|
+
): RequestHandler {
|
|
21
|
+
return async (req: IncomingMessage, res: ServerResponse, next: any) => {
|
|
22
|
+
// Skip non-page requests
|
|
23
|
+
if (
|
|
24
|
+
!req.url ||
|
|
25
|
+
(req.url.includes(".") && !req.url.endsWith("/index.rsc"))
|
|
26
|
+
) {
|
|
27
|
+
return next();
|
|
28
|
+
}
|
|
29
|
+
const url = req.url.endsWith("/index.rsc")
|
|
30
|
+
? req.url.replace("/index.rsc", "/")
|
|
31
|
+
: req.url;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
console.log("[stream] Handling RSC stream");
|
|
35
|
+
|
|
36
|
+
const result = await createHandler(url, options, {
|
|
37
|
+
loader: server.ssrLoadModule,
|
|
38
|
+
moduleGraph: server.moduleGraph,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (result.type === "error") {
|
|
42
|
+
if (
|
|
43
|
+
(result.error as Error).message?.includes(
|
|
44
|
+
"module runner has been closed"
|
|
45
|
+
)
|
|
46
|
+
) {
|
|
47
|
+
console.log("[RSC] Module runner closed, returning 503");
|
|
48
|
+
res.writeHead(503, { "Content-Type": "text/x-component" });
|
|
49
|
+
res.end('{"error":"Server restarting..."}');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
console.error("[RSC] Stream error:", result.error);
|
|
53
|
+
res.writeHead(500, { "Content-Type": "text/x-component" });
|
|
54
|
+
res.end('{"error":"Internal Server Error"}');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (result.type !== "success") {
|
|
59
|
+
res.end();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
res.setHeader("Content-Type", "text/x-component");
|
|
64
|
+
if (result.stream) result.stream.pipe(res);
|
|
65
|
+
} catch (error: any) {
|
|
66
|
+
if (error.message?.includes("module runner has been closed")) {
|
|
67
|
+
console.log("[RSC] Module runner closed, returning 503");
|
|
68
|
+
res.writeHead(503, { "Content-Type": "text/x-component" });
|
|
69
|
+
res.end('{"error":"Server restarting..."}');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
next(error);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ViteDevServer } from "vite";
|
|
2
|
+
import type { StreamPluginOptions } from "../types.js";
|
|
3
|
+
import { createDevMiddleware, type DevMiddlewareOptions } from "./createDevMiddleware.js";
|
|
4
|
+
|
|
5
|
+
export function createDevServer(
|
|
6
|
+
server: ViteDevServer,
|
|
7
|
+
options: DevMiddlewareOptions
|
|
8
|
+
) {
|
|
9
|
+
server.middlewares.use(createDevMiddleware(server, options));
|
|
10
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { createLogger } from "vite";
|
|
2
|
+
import {
|
|
3
|
+
collectManifestCss,
|
|
4
|
+
collectModuleGraphCss,
|
|
5
|
+
} from "../collect-css-manifest.js";
|
|
6
|
+
import { DEFAULT_CONFIG } from "../options.js";
|
|
7
|
+
import { resolvePage } from "../resolvePage.js";
|
|
8
|
+
import { resolveProps } from "../resolveProps.js";
|
|
9
|
+
import type { CreateHandlerOptions, StreamPluginOptions } from "../types.js";
|
|
10
|
+
import { createRscStream } from "./createRscStream.js";
|
|
11
|
+
|
|
12
|
+
export async function createHandler<T>(
|
|
13
|
+
url: string,
|
|
14
|
+
pluginOptions: Pick<
|
|
15
|
+
StreamPluginOptions,
|
|
16
|
+
"Page" | "props" | "build" | "Html" | "pageExportName" | "propsExportName"
|
|
17
|
+
> &
|
|
18
|
+
Required<
|
|
19
|
+
Pick<StreamPluginOptions, "moduleBase" | "moduleBasePath" | "projectRoot">
|
|
20
|
+
>,
|
|
21
|
+
streamOptions: CreateHandlerOptions<T>
|
|
22
|
+
) {
|
|
23
|
+
const root = pluginOptions.projectRoot ?? process.cwd();
|
|
24
|
+
|
|
25
|
+
const Html = pluginOptions.Html ?? DEFAULT_CONFIG.HTML;
|
|
26
|
+
const pageExportName =
|
|
27
|
+
pluginOptions.pageExportName ?? DEFAULT_CONFIG.PAGE_EXPORT;
|
|
28
|
+
const propsExportName =
|
|
29
|
+
pluginOptions.propsExportName ?? DEFAULT_CONFIG.PROPS_EXPORT;
|
|
30
|
+
const controller = new AbortController();
|
|
31
|
+
|
|
32
|
+
const cssFiles = streamOptions.cssFiles;
|
|
33
|
+
const propsPath =
|
|
34
|
+
typeof pluginOptions.props === "function"
|
|
35
|
+
? pluginOptions.props(url)
|
|
36
|
+
: pluginOptions.props;
|
|
37
|
+
const pagePath =
|
|
38
|
+
typeof pluginOptions.Page === "function"
|
|
39
|
+
? pluginOptions.Page(url)
|
|
40
|
+
: pluginOptions.Page;
|
|
41
|
+
|
|
42
|
+
const cssModules = new Set<string>();
|
|
43
|
+
|
|
44
|
+
if (!(streamOptions.manifest || streamOptions.moduleGraph))
|
|
45
|
+
throw new Error("Missing manifest or moduleGraph, pass it to options.");
|
|
46
|
+
|
|
47
|
+
const getCss = streamOptions.manifest
|
|
48
|
+
? (id: string) =>
|
|
49
|
+
collectManifestCss(
|
|
50
|
+
streamOptions.manifest!,
|
|
51
|
+
root,
|
|
52
|
+
id,
|
|
53
|
+
streamOptions.onCssFile
|
|
54
|
+
)
|
|
55
|
+
: (id: string) => collectModuleGraphCss(streamOptions.moduleGraph!, id);
|
|
56
|
+
|
|
57
|
+
const loadWithCss = async (id: string) => {
|
|
58
|
+
if (!id) return {};
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const mod = await streamOptions.loader(id);
|
|
62
|
+
const pageCss = await Promise.resolve(getCss(id));
|
|
63
|
+
Array.from(pageCss.keys()).forEach((css) => cssModules.add(css));
|
|
64
|
+
return mod as Record<string, any>;
|
|
65
|
+
} catch (e: any) {
|
|
66
|
+
if (e.message?.includes("module runner has been closed")) {
|
|
67
|
+
return { type: "skip" } as Record<string, any>;
|
|
68
|
+
} else {
|
|
69
|
+
return { type: "error", error: e } as Record<string, any>;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const PropsModule = await resolveProps({
|
|
75
|
+
propsModule: await loadWithCss(propsPath ?? pagePath),
|
|
76
|
+
path: String(propsPath ?? pagePath),
|
|
77
|
+
exportName: propsExportName,
|
|
78
|
+
url,
|
|
79
|
+
});
|
|
80
|
+
if (PropsModule.type === "error")
|
|
81
|
+
return { type: PropsModule.type, error: PropsModule?.error };
|
|
82
|
+
if (PropsModule.type === "skip") return { type: PropsModule.type };
|
|
83
|
+
const props = PropsModule[propsExportName as keyof typeof PropsModule] as any;
|
|
84
|
+
if (props?.type === "error") return { type: props.type, error: props.error };
|
|
85
|
+
if (props?.type === "skip") return { type: props.type };
|
|
86
|
+
|
|
87
|
+
const PageModule = await resolvePage({
|
|
88
|
+
pageModule: await loadWithCss(pagePath),
|
|
89
|
+
path: pagePath,
|
|
90
|
+
exportName: pageExportName,
|
|
91
|
+
url,
|
|
92
|
+
});
|
|
93
|
+
if (PageModule.type === "error")
|
|
94
|
+
return { type: PageModule.type, error: PageModule.error };
|
|
95
|
+
if (PageModule.type === "skip") return { type: PageModule.type };
|
|
96
|
+
const Page = PageModule[pageExportName as keyof typeof PageModule] as any;
|
|
97
|
+
if (Page?.type === "error") return { type: Page.type, error: Page.error };
|
|
98
|
+
if (Page?.type === "skip") return { type: Page.type };
|
|
99
|
+
if (!(typeof Page === "function")) {
|
|
100
|
+
return {
|
|
101
|
+
type: "error",
|
|
102
|
+
error: new Error("Invalid Page component: " + pagePath, {
|
|
103
|
+
cause: Page,
|
|
104
|
+
}),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (!(typeof props === "object")) {
|
|
108
|
+
return {
|
|
109
|
+
type: "error",
|
|
110
|
+
error: new Error("Invalid props: " + propsPath, {
|
|
111
|
+
cause: props,
|
|
112
|
+
}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Add any additional CSS files
|
|
117
|
+
if (streamOptions.cssFiles) {
|
|
118
|
+
streamOptions.cssFiles.forEach((css) => cssModules.add(css));
|
|
119
|
+
}
|
|
120
|
+
const stream = createRscStream({
|
|
121
|
+
Html: Html,
|
|
122
|
+
Page: Page,
|
|
123
|
+
props: props,
|
|
124
|
+
moduleBasePath: pluginOptions.moduleBasePath, // eg /src
|
|
125
|
+
logger: streamOptions.logger ?? createLogger(),
|
|
126
|
+
cssFiles: Array.from(cssModules),
|
|
127
|
+
route: url,
|
|
128
|
+
url,
|
|
129
|
+
pipableStreamOptions: streamOptions.pipableStreamOptions,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (!stream) {
|
|
133
|
+
return { type: "skip" as const };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
type: "success" as const,
|
|
138
|
+
controller,
|
|
139
|
+
stream,
|
|
140
|
+
assets: {
|
|
141
|
+
css: cssFiles,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
createFromNodeStream,
|
|
4
|
+
type CreateFromNodeStreamOptions,
|
|
5
|
+
} from "react-server-dom-esm/client.node";
|
|
6
|
+
import type { Readable } from "stream";
|
|
7
|
+
|
|
8
|
+
export function createReactNodeStreamer({
|
|
9
|
+
stream,
|
|
10
|
+
moduleBasePath,
|
|
11
|
+
moduleBaseURL,
|
|
12
|
+
options,
|
|
13
|
+
}: {
|
|
14
|
+
stream: Readable;
|
|
15
|
+
moduleBasePath: string;
|
|
16
|
+
moduleBaseURL: string;
|
|
17
|
+
options?: CreateFromNodeStreamOptions;
|
|
18
|
+
}) {
|
|
19
|
+
return createFromNodeStream(
|
|
20
|
+
stream,
|
|
21
|
+
moduleBasePath,
|
|
22
|
+
moduleBaseURL,
|
|
23
|
+
options
|
|
24
|
+
) as React.Usable<React.ReactNode>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
renderToPipeableStream,
|
|
4
|
+
type PipeableStream,
|
|
5
|
+
} from "react-server-dom-esm/server.node";
|
|
6
|
+
import { CssCollector } from "../components.js";
|
|
7
|
+
import type { RscStreamOptions } from "../types.js";
|
|
8
|
+
|
|
9
|
+
export function createRscStream(
|
|
10
|
+
streamOptions: RscStreamOptions
|
|
11
|
+
): PipeableStream {
|
|
12
|
+
const {
|
|
13
|
+
Html,
|
|
14
|
+
Page,
|
|
15
|
+
props,
|
|
16
|
+
logger,
|
|
17
|
+
cssFiles,
|
|
18
|
+
route,
|
|
19
|
+
url,
|
|
20
|
+
moduleBasePath,
|
|
21
|
+
pipableStreamOptions,
|
|
22
|
+
} = streamOptions;
|
|
23
|
+
const css = Array.isArray(cssFiles)
|
|
24
|
+
? cssFiles.map((css, index) =>
|
|
25
|
+
React.createElement(CssCollector, {
|
|
26
|
+
key: `css-${index}`,
|
|
27
|
+
url: css,
|
|
28
|
+
})
|
|
29
|
+
)
|
|
30
|
+
: [];
|
|
31
|
+
return renderToPipeableStream(
|
|
32
|
+
React.createElement(
|
|
33
|
+
Html,
|
|
34
|
+
{
|
|
35
|
+
key: "html",
|
|
36
|
+
pageProps: props,
|
|
37
|
+
moduleBasePath: moduleBasePath,
|
|
38
|
+
route,
|
|
39
|
+
url,
|
|
40
|
+
},
|
|
41
|
+
React.createElement(Page, { key: "page", ...props }),
|
|
42
|
+
...css
|
|
43
|
+
),
|
|
44
|
+
moduleBasePath,
|
|
45
|
+
{
|
|
46
|
+
onError: logger?.error ?? console.error,
|
|
47
|
+
onPostpone: logger?.info ?? console.info,
|
|
48
|
+
environmentName: "Server",
|
|
49
|
+
...pipableStreamOptions,
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { dirname, join, resolve } from "node:path";
|
|
2
|
+
import { Writable } from "node:stream";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { Worker } from "node:worker_threads";
|
|
5
|
+
import { type ViteDevServer } from "vite";
|
|
6
|
+
import { DEFAULT_CONFIG } from "../options.js";
|
|
7
|
+
import type { RequestHandler, StreamPluginOptions } from "../types.js";
|
|
8
|
+
import type { WorkerRscChunkMessage } from "../worker/types.js";
|
|
9
|
+
import { createHandler } from "./createHandler.js";
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
export function createSsrHandler(
|
|
14
|
+
options: Required<
|
|
15
|
+
Pick<
|
|
16
|
+
StreamPluginOptions,
|
|
17
|
+
"Page" | "props" | "build" | "Html" | "pageExportName" | "propsExportName"
|
|
18
|
+
>
|
|
19
|
+
> &
|
|
20
|
+
Required<
|
|
21
|
+
Pick<
|
|
22
|
+
StreamPluginOptions,
|
|
23
|
+
"moduleBase" | "moduleBasePath" | "moduleBaseURL" | "projectRoot"
|
|
24
|
+
>
|
|
25
|
+
> &
|
|
26
|
+
Pick<StreamPluginOptions, "workerPath">,
|
|
27
|
+
server: ViteDevServer,
|
|
28
|
+
clientComponents: Map<string, string>
|
|
29
|
+
): RequestHandler {
|
|
30
|
+
const worker = new Worker(
|
|
31
|
+
options?.workerPath
|
|
32
|
+
? resolve(server.config.root, options?.workerPath)
|
|
33
|
+
: resolve(__dirname, "..", DEFAULT_CONFIG.WORKER_PATH),
|
|
34
|
+
{
|
|
35
|
+
env: {
|
|
36
|
+
NODE_OPTIONS: "--conditions ''",
|
|
37
|
+
VITE_LOADER_PATH: resolve(
|
|
38
|
+
server.config.cacheDir,
|
|
39
|
+
"react-stream/worker/loader.js"
|
|
40
|
+
),
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return async function handleSsrRequest(req, res, next) {
|
|
46
|
+
if (
|
|
47
|
+
!req.url ||
|
|
48
|
+
req.url.startsWith("/@") ||
|
|
49
|
+
(req.url.includes(".") && !req.url.endsWith(".html"))
|
|
50
|
+
) {
|
|
51
|
+
return next();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const result = await createHandler(
|
|
56
|
+
req.url ?? "",
|
|
57
|
+
{
|
|
58
|
+
Page: options.Page,
|
|
59
|
+
props: options.props,
|
|
60
|
+
build: options.build,
|
|
61
|
+
Html: options.Html,
|
|
62
|
+
pageExportName: options.pageExportName,
|
|
63
|
+
propsExportName: options.propsExportName,
|
|
64
|
+
moduleBase: options.moduleBase,
|
|
65
|
+
moduleBasePath: options.moduleBasePath,
|
|
66
|
+
projectRoot: server.config.root,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
loader: server.ssrLoadModule.bind(server),
|
|
70
|
+
moduleGraph: server.moduleGraph,
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
const moduleBasePath = join(
|
|
74
|
+
server.config.cacheDir,
|
|
75
|
+
options.moduleBasePath
|
|
76
|
+
);
|
|
77
|
+
const htmlOutputPath = join(
|
|
78
|
+
server.config.cacheDir,
|
|
79
|
+
server.config.build.outDir,
|
|
80
|
+
req.url,
|
|
81
|
+
"index.html"
|
|
82
|
+
);
|
|
83
|
+
if (result.type !== "success") {
|
|
84
|
+
throw new Error(
|
|
85
|
+
result.type === "error" ? String(result.error) : "Skipped"
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Collect RSC stream data
|
|
90
|
+
const rscData = await new Promise<string>((resolve, reject) => {
|
|
91
|
+
let data = "";
|
|
92
|
+
if (!result.stream) {
|
|
93
|
+
resolve(data);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const writable = new Writable({
|
|
97
|
+
write(chunk, _, callback) {
|
|
98
|
+
data += chunk;
|
|
99
|
+
callback();
|
|
100
|
+
},
|
|
101
|
+
final(callback) {
|
|
102
|
+
resolve(data);
|
|
103
|
+
callback();
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
result.stream.pipe(writable);
|
|
108
|
+
writable.on("error", reject);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Get HTML from worker
|
|
112
|
+
const html = await new Promise<string>((resolve, reject) => {
|
|
113
|
+
worker.postMessage({
|
|
114
|
+
type: "RSC_CHUNK",
|
|
115
|
+
id: req.url ?? "/",
|
|
116
|
+
chunk: rscData,
|
|
117
|
+
moduleBasePath,
|
|
118
|
+
moduleBaseURL: options.moduleBaseURL,
|
|
119
|
+
// Don't need file paths in dev mode
|
|
120
|
+
outDir: "",
|
|
121
|
+
htmlOutputPath: "",
|
|
122
|
+
pipableStreamOptions: {
|
|
123
|
+
bootstrapModules: ["/dist/client.js"],
|
|
124
|
+
},
|
|
125
|
+
} satisfies WorkerRscChunkMessage);
|
|
126
|
+
|
|
127
|
+
worker.once("message", (msg) => {
|
|
128
|
+
if (msg.type === "ERROR") {
|
|
129
|
+
const message =
|
|
130
|
+
msg.error instanceof Error
|
|
131
|
+
? msg.error.message
|
|
132
|
+
: String(msg.error);
|
|
133
|
+
reject(new Error(message, { cause: msg }));
|
|
134
|
+
} else if (msg.type === "HTML") {
|
|
135
|
+
// In dev, content will be the HTML string
|
|
136
|
+
resolve(msg.content);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
res.setHeader("Content-Type", "text/html");
|
|
142
|
+
res.end(html);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
next(error);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|