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,59 @@
1
+ import { normalizePath } from "vite";
2
+
3
+ type NormalizedRelativePathOptions = {
4
+ // will automatically remove this part
5
+ root: string;
6
+ // will automatically see this as a optional extra part of the rootDir that will be removed
7
+ outDir: string | string[];
8
+ // will ensure it always starts with this path, if it does not it will be added
9
+ moduleBase: string;
10
+ // will ensure it never starts with a leading /, which in some cases is needed (vite entry), other cases it is not for example from project root /
11
+ noLeadingSlash: boolean;
12
+ };
13
+
14
+ export const createNormalizedRelativePath = (
15
+ options: NormalizedRelativePathOptions = {
16
+ root: process.cwd(),
17
+ outDir: ["dist", "build"],
18
+ moduleBase: "src",
19
+ noLeadingSlash: false,
20
+ }
21
+ ) => {
22
+ const base =
23
+ options.noLeadingSlash && options.moduleBase.startsWith("/")
24
+ ? options.moduleBase.slice(1)
25
+ : options.moduleBase;
26
+ const removeOutDir = Array.isArray(options.outDir)
27
+ ? (path: string) =>
28
+ (options.outDir as string[])
29
+ .map((dir) => path.startsWith(dir))
30
+ .some((v) => v)
31
+ ? path.slice(options.outDir[0].length)
32
+ : path
33
+ : (path: string) =>
34
+ (options.outDir as string) === path
35
+ ? path.slice(options.outDir.length)
36
+ : path;
37
+
38
+ const removeRoot = (path: string) => {
39
+ const normalized = normalizePath(path);
40
+ const relative = normalized.startsWith(options.root)
41
+ ? normalized.slice(options.root.length)
42
+ : normalized;
43
+ return relative;
44
+ };
45
+
46
+ const ensureModuleBase = (path: string) => {
47
+ if (options.noLeadingSlash && path.startsWith("/")) {
48
+ return path.slice(1);
49
+ }
50
+ if (!path.startsWith(base)) {
51
+ throw new Error(
52
+ `Path ${path} does not start with module base ${base}, this will not work down the line.`
53
+ );
54
+ }
55
+ return path;
56
+ };
57
+
58
+ return (path: string) => ensureModuleBase(removeOutDir(removeRoot(path)));
59
+ };
@@ -0,0 +1,23 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+
4
+ type TryManifestOptions = {
5
+ root: string;
6
+ outDir: string;
7
+ ssrManifest: boolean;
8
+ };
9
+
10
+ export function tryManifest(options: TryManifestOptions) {
11
+ const manifestPath = resolve(
12
+ options.root,
13
+ options.outDir,
14
+ ".vite",
15
+ options.ssrManifest ? "ssr-manifest.json" : "manifest.json"
16
+ );
17
+ try {
18
+ return JSON.parse(readFileSync(manifestPath, "utf-8"));
19
+ } catch (e) {
20
+ console.log("No manifest found", manifestPath);
21
+ return null;
22
+ }
23
+ }
@@ -0,0 +1,99 @@
1
+ import { resolve as resolvePath } from "node:path";
2
+ import { load } from "react-server-dom-esm/node-loader";
3
+ import {
4
+ registerClientReference,
5
+ registerServerReference,
6
+ } from "react-server-dom-esm/server.node";
7
+ import { createNormalizedRelativePath } from "../helpers/normalizedRelativePath.js";
8
+
9
+ type CreatePageLoaderOptions = {
10
+ manifest: Record<string, { file: string }>;
11
+ root: string;
12
+ outDir: string;
13
+ moduleBase: string;
14
+ registerServer?: string[];
15
+ registerClient?: string[];
16
+ alwaysRegisterServer?: boolean;
17
+ alwaysRegisterClient?: boolean;
18
+ };
19
+
20
+ type CreateDefaultLoaderOptions = {
21
+ id: string;
22
+ registerServer?: string[];
23
+ registerClient?: string[];
24
+ alwaysRegisterServer?: boolean;
25
+ alwaysRegisterClient?: boolean;
26
+ };
27
+
28
+ export const createDefaultLoader = ({
29
+ id,
30
+ registerServer,
31
+ registerClient,
32
+ alwaysRegisterServer = false,
33
+ alwaysRegisterClient = false,
34
+ }: CreateDefaultLoaderOptions) => {
35
+ const mapper = ([key, value]: [string, any]) => {
36
+ try {
37
+ if (
38
+ registerClient?.includes(key) ||
39
+ (alwaysRegisterClient && typeof value === "function")
40
+ ) {
41
+ return [key, registerClientReference(value, id, key)];
42
+ }
43
+ if (
44
+ registerServer?.includes(key) ||
45
+ (alwaysRegisterServer && typeof value === "function")
46
+ ) {
47
+ return [key, registerServerReference(value, id, key)];
48
+ }
49
+ return [key, value];
50
+ } catch (e) {
51
+ console.error("[RSC] Error registering reference:", key, value, e);
52
+ return [key, value];
53
+ }
54
+ };
55
+ return async (url: string) =>
56
+ Object.fromEntries(Object.entries(await import(url)).map(mapper));
57
+ };
58
+
59
+ export const createPageLoader = ({
60
+ manifest,
61
+ root,
62
+ outDir,
63
+ moduleBase,
64
+ registerServer,
65
+ registerClient,
66
+ alwaysRegisterServer,
67
+ alwaysRegisterClient,
68
+ }: CreatePageLoaderOptions) => {
69
+ const pathNormalizer = createNormalizedRelativePath({
70
+ root,
71
+ outDir,
72
+ moduleBase,
73
+ noLeadingSlash: true,
74
+ });
75
+ return async (id: string) => {
76
+ const normalizedId = pathNormalizer(id);
77
+ const entry =
78
+ normalizedId in manifest
79
+ ? manifest[normalizedId]
80
+ : Object.values(manifest).find((entry) => entry.file === normalizedId);
81
+ if (!entry) {
82
+ throw new Error(
83
+ `Could not find manifest entry for ${id}, ${normalizedId} from ${root}`
84
+ );
85
+ }
86
+ const loaderResult = await load(
87
+ resolvePath(root, outDir, entry.file),
88
+ { format: "module" },
89
+ createDefaultLoader({
90
+ id,
91
+ registerServer,
92
+ registerClient,
93
+ alwaysRegisterServer,
94
+ alwaysRegisterClient,
95
+ })
96
+ );
97
+ return loaderResult;
98
+ };
99
+ };
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { reactStreamPlugin } from './plugin.js';
2
+
3
+ // Export types
4
+ export type * from './types.js';
@@ -0,0 +1,24 @@
1
+ import type { Manifest } from "vite";
2
+ import { normalizePath } from "vite";
3
+
4
+ export interface RscManifest {
5
+ clientComponents: Record<string, string>;
6
+ }
7
+
8
+ export function resolveManifestEntry(
9
+ id: string,
10
+ manifest: Manifest
11
+ ): string | undefined {
12
+ // Try exact match
13
+ if (manifest[id]) {
14
+ return manifest[id].file;
15
+ }
16
+
17
+ // Try normalized path
18
+ const normalizedId = normalizePath(id);
19
+ if (manifest[normalizedId]) {
20
+ return manifest[normalizedId].file;
21
+ }
22
+
23
+ return undefined;
24
+ }
@@ -0,0 +1,48 @@
1
+ import type { ModuleNode, ViteDevServer } from "vite";
2
+
3
+ export interface ModuleWithDeps {
4
+ id: string;
5
+ deps: Set<string>;
6
+ css: Set<string>;
7
+ }
8
+
9
+ export function getModuleGraph(server: ViteDevServer) {
10
+ return {
11
+ async getModuleWithDeps(id: string): Promise<ModuleWithDeps> {
12
+ // Load module first to ensure it's in the module graph
13
+ await server.ssrLoadModule(id);
14
+
15
+ const resolvedId = await server.pluginContainer.resolveId(id, undefined, {
16
+ // Add ssr and react-server conditions
17
+ ssr: true,
18
+ });
19
+
20
+ if (!resolvedId) throw new Error(`Module not found: ${id}`);
21
+
22
+ const moduleNode = server.moduleGraph.getModuleById(resolvedId.id);
23
+ if (!moduleNode) throw new Error(`Module node not found: ${id}`);
24
+
25
+ const deps = new Set<string>();
26
+ const css = new Set<string>();
27
+
28
+ // Recursively collect dependencies
29
+ const collectDeps = (node: ModuleNode) => {
30
+ // Track CSS imports
31
+ if (node.id?.endsWith(".css") && node.id) {
32
+ css.add(node.id);
33
+ }
34
+
35
+ // Track all dependencies
36
+ for (const dep of node.importedModules) {
37
+ if (dep.id && !deps.has(dep.id)) {
38
+ deps.add(dep.id);
39
+ collectDeps(dep);
40
+ }
41
+ }
42
+ };
43
+
44
+ collectDeps(moduleNode);
45
+ return { id: moduleNode.id ?? "", deps, css };
46
+ },
47
+ };
48
+ }
package/src/options.ts ADDED
@@ -0,0 +1,351 @@
1
+ import path from "node:path";
2
+ import type { ConfigEnv, ResolvedConfig, UserConfig } from "vite";
3
+ import type { ResolvedUserOptions, StreamPluginOptions } from "./types.js";
4
+ // Default configuration values
5
+ export const DEFAULT_CONFIG = {
6
+ FILE_REGEX: /\.(m|c)?(j|t)sx?$/,
7
+ CLIENT_ASSETS_DIR: "assets",
8
+ RSC_DIR: "rsc",
9
+ MODULE_BASE: "src",
10
+ MODULE_BASE_PATH: "/src",
11
+ MODULE_BASE_URL: "/src",
12
+ PAGE: "/src/page/page.tsx",
13
+ PROPS: "/src/page/props.ts",
14
+ CLIENT_ENTRY: "/src/client.tsx",
15
+ PAGE_EXPORT: "Page",
16
+ PROPS_EXPORT: "props",
17
+ // relative from plugin root
18
+ WORKER_PATH: "worker/worker.tsx",
19
+ LOADER_PATH: "worker/loader.ts",
20
+ RSC_EXTENSION: ".rsc",
21
+ HTML: ({ children }: { children: any }) => children,
22
+ COLLECT_CSS: true,
23
+ COLLECT_ASSETS: true,
24
+ PAGE_PATTERN: "/src/page/**/*.page.tsx",
25
+ PROPS_PATTERN: "/src/page/**/*.props.ts",
26
+ DEV_PORT: 5173,
27
+ PREVIEW_PORT: 4173,
28
+ DEV_HOST: "localhost",
29
+ PREVIEW_HOST: "localhost",
30
+ ENV_PREFIX: "VITE_",
31
+ BUILD: {
32
+ pages: () => ["/"],
33
+ client: "dist/client",
34
+ server: "dist/server",
35
+ },
36
+ } as const;
37
+
38
+ export const resolveConfig = <T extends ResolvedConfig>(
39
+ config: T,
40
+ resolvedUserConfig: UserConfig,
41
+ userOptions: ResolvedUserOptions
42
+ ) => {
43
+ return {
44
+ ...config,
45
+ build: {
46
+ ...config.build,
47
+ assetsDir: config.build?.assetsDir ?? DEFAULT_CONFIG.CLIENT_ASSETS_DIR,
48
+ },
49
+ };
50
+ };
51
+
52
+ export const resolveUserConfig = (
53
+ condition: "react-client" | "react-server",
54
+ input: string[],
55
+ config: UserConfig,
56
+ configEnv: ConfigEnv,
57
+ userOptions: ResolvedUserOptions
58
+ ):
59
+ | { type: "error"; error: Error }
60
+ | {
61
+ type: "success";
62
+ userConfig: Required<Pick<UserConfig, "root" | "build" | "mode">> &
63
+ Omit<UserConfig, "root" | "build" | "mode">;
64
+ } => {
65
+ const isReactServer = condition === "react-server";
66
+ const isViteServer = configEnv.command === "serve";
67
+ const isVitePreview = configEnv.isPreview;
68
+
69
+ if (isReactServer && configEnv.command === "build") {
70
+ if (!config.build?.rollupOptions?.input) {
71
+ config = {
72
+ ...config,
73
+ build: {
74
+ ...config.build,
75
+ rollupOptions: {
76
+ ...config.build?.rollupOptions,
77
+ input: input
78
+ }
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ if (typeof config.build?.assetsDir === 'string' && userOptions.assetsDir !== config.build?.assetsDir) {
85
+ return {
86
+ type: "error",
87
+ error: new Error(
88
+ `assetsDir cannot be changed after the config has been resolved, before: ${userOptions.assetsDir}, after: ${config.build?.assetsDir}`
89
+ ),
90
+ };
91
+ }
92
+
93
+ if (isReactServer) {
94
+ if (configEnv.command === "build") {
95
+ if (!configEnv.isSsrBuild) {
96
+ return {
97
+ type: "error",
98
+ error: new Error(
99
+ "ssr must be true when using the server plugin, NODE_OPTIONS='--conditions react-server' vite build --ssr"
100
+ ),
101
+ };
102
+ }
103
+ } else if (!isViteServer) {
104
+ return {
105
+ type: "error",
106
+ error: new Error(
107
+ isViteServer
108
+ ? `react-server condition was not set. Please use \`NODE_OPTIONS='--conditions react-server' vite${
109
+ isVitePreview ? " preview" : ""
110
+ }\``
111
+ : "react-server condition was not set. Please use `NODE_OPTIONS='--conditions react-server' vite build --ssr`"
112
+ ),
113
+ };
114
+ } else if (!configEnv.isSsrBuild && configEnv.command !== 'serve') {
115
+ return {
116
+ type: "error",
117
+ error: new Error(
118
+ "Vite was run with the react-server condition, but is making a client build."
119
+ ),
120
+ };
121
+ }
122
+ }
123
+
124
+ const { root: configRoot, mode: configMode, ...configRest } = config;
125
+ const { outDir: configOutDir, assetsDir: configAssetsDir, ssr: configSsr, manifest: configManifest, ssrManifest: configSsrManifest, target: configTarget, ...configBuildRest } = config.build ?? {};
126
+
127
+ return {
128
+ type: "success",
129
+ userConfig: {
130
+ ...configRest,
131
+ root: configRoot ?? userOptions.projectRoot ?? process.cwd(),
132
+ mode: configMode ?? process.env["NODE_ENV"] ?? "production",
133
+ build: {
134
+ ...configBuildRest,
135
+ ssr: configSsr ?? isReactServer,
136
+ manifest: configManifest ?? true,
137
+ ssrManifest: configSsrManifest ?? true,
138
+ target: configTarget ?? "es2020",
139
+ outDir:
140
+ typeof configOutDir === "string"
141
+ ? configOutDir
142
+ : isReactServer
143
+ ? userOptions.build.server
144
+ : userOptions.build.client,
145
+ assetsDir:
146
+ typeof configAssetsDir === "string"
147
+ ? configAssetsDir
148
+ : isReactServer
149
+ ? ''
150
+ : DEFAULT_CONFIG.CLIENT_ASSETS_DIR,
151
+ },
152
+ },
153
+ };
154
+ };
155
+
156
+ export const resolveOptions = (
157
+ options: StreamPluginOptions
158
+ ):
159
+ | { type: "error"; error: Error }
160
+ | {
161
+ type: "success";
162
+ userOptions: ResolvedUserOptions;
163
+ } => {
164
+ const projectRoot = options.projectRoot ?? process.cwd();
165
+ /** the module base can be assumed to not have a leading slash */
166
+ const moduleBase =
167
+ typeof options.moduleBase === "string"
168
+ ? options.moduleBase.startsWith(path.sep)
169
+ ? options.moduleBase.slice(path.sep.length)
170
+ : options.moduleBase
171
+ : DEFAULT_CONFIG.MODULE_BASE;
172
+
173
+ if (
174
+ typeof options.moduleBase === "string" &&
175
+ options.moduleBase !== moduleBase
176
+ ) {
177
+ return {
178
+ type: "error",
179
+ error: new Error(
180
+ `moduleBase ${options.moduleBase} is invalid, should be like ${moduleBase}`
181
+ ),
182
+ };
183
+ }
184
+
185
+ const moduleBasePath =
186
+ typeof options.moduleBasePath === "string"
187
+ ? !options.moduleBasePath.startsWith(path.sep)
188
+ ? `${path.sep}${options.moduleBasePath}`
189
+ : options.moduleBasePath
190
+ : `${path.sep}${moduleBase}`;
191
+
192
+ if (!moduleBasePath.includes(moduleBase)) {
193
+ return {
194
+ type: "error",
195
+ error: new Error(
196
+ `moduleBasePath ${moduleBasePath} is invalid, should include moduleBase ${moduleBase}`
197
+ ),
198
+ };
199
+ }
200
+
201
+ const moduleBaseURL =
202
+ typeof options.moduleBaseURL === "string"
203
+ ? !options.moduleBaseURL.endsWith(moduleBasePath)
204
+ ? path.join(options.moduleBaseURL, moduleBasePath)
205
+ : options.moduleBaseURL
206
+ : moduleBasePath;
207
+
208
+ if (!moduleBaseURL.includes(moduleBasePath)) {
209
+ return {
210
+ type: "error",
211
+ error: new Error(
212
+ `moduleBaseURL ${moduleBaseURL} is invalid, should include moduleBasePath ${moduleBasePath}`
213
+ ),
214
+ };
215
+ }
216
+
217
+ if (
218
+ typeof options.moduleBaseURL === "string" &&
219
+ options.moduleBaseURL !== moduleBaseURL
220
+ ) {
221
+ return {
222
+ type: "error",
223
+ error: new Error(
224
+ `moduleBaseURL ${options.moduleBaseURL} is invalid, should be like ${moduleBaseURL}`
225
+ ),
226
+ };
227
+ }
228
+
229
+ const build = options.build
230
+ ? {
231
+ client: options.build.client ?? DEFAULT_CONFIG.BUILD.client,
232
+ pages: options.build.pages ?? DEFAULT_CONFIG.BUILD.pages,
233
+ server: options.build.server ?? DEFAULT_CONFIG.BUILD.server,
234
+ }
235
+ : DEFAULT_CONFIG.BUILD;
236
+
237
+ return {
238
+ type: "success",
239
+ userOptions: {
240
+ moduleBase,
241
+ moduleBasePath,
242
+ moduleBaseURL,
243
+ build,
244
+ Page: options.Page ?? DEFAULT_CONFIG.PAGE,
245
+ props: options.props ?? DEFAULT_CONFIG.PROPS,
246
+ Html: options.Html ?? DEFAULT_CONFIG.HTML,
247
+ pageExportName: options.pageExportName ?? DEFAULT_CONFIG.PAGE_EXPORT,
248
+ propsExportName: options.propsExportName ?? DEFAULT_CONFIG.PROPS_EXPORT,
249
+ collectCss: options.collectCss ?? DEFAULT_CONFIG.COLLECT_CSS,
250
+ collectAssets: options.collectAssets ?? DEFAULT_CONFIG.COLLECT_ASSETS,
251
+ projectRoot: projectRoot,
252
+ assetsDir: options.assetsDir ?? DEFAULT_CONFIG.CLIENT_ASSETS_DIR,
253
+ } satisfies ResolvedUserOptions,
254
+ };
255
+ };
256
+
257
+ export async function resolvePages(
258
+ pages: ResolvedUserOptions["build"]["pages"]
259
+ ): Promise<
260
+ | {
261
+ type: "success";
262
+ pages: string[];
263
+ }
264
+ | {
265
+ type: "error";
266
+ error: Error;
267
+ }
268
+ > {
269
+ if (!pages) {
270
+ return {
271
+ type: "success",
272
+ pages: [],
273
+ };
274
+ }
275
+ if (typeof pages === "function") {
276
+ try {
277
+ return resolvePages(await Promise.resolve(pages()));
278
+ } catch (error) {
279
+ return {
280
+ type: "error",
281
+ error:
282
+ error instanceof Error
283
+ ? error
284
+ : new Error(
285
+ `build.pages must be a array of strings, or a (async) function that returns a string or an array of strings. Got "${JSON.stringify(
286
+ pages
287
+ )}"`,
288
+ {
289
+ cause: error,
290
+ }
291
+ ),
292
+ };
293
+ }
294
+ }
295
+ try {
296
+ const result = pages;
297
+ const awaited = "then" in result ? await result : result;
298
+ if (typeof awaited === "string") {
299
+ return {
300
+ type: "success",
301
+ pages: [awaited],
302
+ };
303
+ }
304
+ if (Array.isArray(awaited)) {
305
+ if (awaited.every((page) => typeof page === "string")) {
306
+ return {
307
+ type: "success",
308
+ pages: awaited,
309
+ };
310
+ } else {
311
+ return {
312
+ type: "error",
313
+ error: new Error(
314
+ `build.pages must be a array of strings, or a (async) function that returns a string or an array of strings. Got "${JSON.stringify(
315
+ awaited.find((page) => typeof page !== "string")
316
+ )}"`,
317
+ {
318
+ cause: awaited,
319
+ }
320
+ ),
321
+ };
322
+ }
323
+ }
324
+ return {
325
+ type: "error",
326
+ error: new Error(
327
+ `build.pages must be a array of strings, or a (async) function that returns a string or an array of strings. Got "${JSON.stringify(
328
+ pages
329
+ )}"`,
330
+ {
331
+ cause: pages,
332
+ }
333
+ ),
334
+ };
335
+ } catch (error) {
336
+ return {
337
+ type: "error",
338
+ error:
339
+ error instanceof Error
340
+ ? error
341
+ : new Error(
342
+ `build.pages must be a array of strings, or a (async) function that returns a string or an array of strings. Got "${JSON.stringify(
343
+ error
344
+ )}"`,
345
+ {
346
+ cause: error,
347
+ }
348
+ ),
349
+ };
350
+ }
351
+ }
package/src/plugin.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { resolveOptions } from "./options.js";
2
+ import { viteReactClientTransformPlugin } from "./transformer/index.js";
3
+ import { preserveDirectives } from "./transformer/preserveDirectives.js";
4
+ import type { StreamPluginOptions } from "./types.js";
5
+
6
+ export const getCondition = (options: { env?: typeof process.env } = {}) => {
7
+ const nodeOptions = options?.env?.['NODE_OPTIONS'] ?? process.env['NODE_OPTIONS'];
8
+ return nodeOptions?.match(/--conditions=react-server/) ? "server" : "client";
9
+ };
10
+
11
+ export const reactStreamPlugin = async (options: StreamPluginOptions) => {
12
+ const condition = getCondition();
13
+ try {
14
+ const resolvedOptions = resolveOptions(options);
15
+ if (resolvedOptions.type === "error") {
16
+ throw resolvedOptions.error;
17
+ }
18
+ options = resolvedOptions.userOptions;
19
+ } catch (error) {
20
+ throw new Error(
21
+ `[vite:react-stream:${condition}] ${(error as Error).message}`
22
+ );
23
+ }
24
+ return condition === "server"
25
+ ? [
26
+ (await import(`./react-server/plugin.js`)).reactStreamPlugin(options),
27
+ viteReactClientTransformPlugin(),
28
+ preserveDirectives(),
29
+ ]
30
+ : [(await import(`./react-client/plugin.js`)).reactStreamPlugin(options)];
31
+ };
@@ -0,0 +1,34 @@
1
+ import type { Plugin } from "vite";
2
+ import {
3
+ resolveOptions,
4
+ resolvePages,
5
+ resolveUserConfig,
6
+ } from "../options.js";
7
+ import type { StreamPluginOptions } from "../types.js";
8
+
9
+ export async function reactStreamPlugin(
10
+ options: StreamPluginOptions
11
+ ): Promise<Plugin> {
12
+ const resolvedOptions = resolveOptions(options);
13
+ if (resolvedOptions.type === "error") {
14
+ throw resolvedOptions.error;
15
+ }
16
+ const { userOptions } = resolvedOptions;
17
+
18
+ const resolvedPages = await resolvePages(userOptions.build.pages)
19
+ if(resolvedPages.type === "error") {
20
+ throw resolvedPages.error
21
+ }
22
+ const { pages } = resolvedPages
23
+ return {
24
+ name: "vite:react-stream",
25
+ config(config, configEnv) {
26
+ const resolvedConfig = resolveUserConfig("react-client", pages, config, configEnv, userOptions);
27
+ if (resolvedConfig.type === "error") {
28
+ throw resolvedConfig.error;
29
+ }
30
+ const { userConfig } = resolvedConfig;
31
+ return userConfig
32
+ },
33
+ };
34
+ }