vite-plugin-ssr-config 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -20
- package/dist/index.cjs +19 -9
- package/dist/index.js +19 -9
- package/package.json +7 -3
- package/ssr/entryClient.jsx +25 -25
- package/ssr/entryRender.jsx +40 -13
- package/ssr/pageBrowser.jsx +1 -1
- package/ssr/pageServer.jsx +1 -1
- package/ssr/root.jsx +1 -1
- package/ssr/rootRoutes.jsx +2 -2
package/README.md
CHANGED
|
@@ -16,18 +16,19 @@ For more detailed information and resources related to `vite-plugin-ssr-config`,
|
|
|
16
16
|
To add this plugin to your project, run the following commands:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
yarn add vite-plugin-ssr-config vite-plugin-
|
|
19
|
+
yarn add vite-plugin-ssr-config vite-plugin-web-routes -D
|
|
20
|
+
```
|
|
21
|
+
```bash
|
|
22
|
+
yarn add react-query react-router express
|
|
20
23
|
```
|
|
21
24
|
|
|
22
25
|
This will install:
|
|
23
26
|
|
|
24
27
|
- vite-plugin-ssr-config: The plugin for server-side rendering (SSR) with Vite.
|
|
25
|
-
- vite-plugin-
|
|
26
|
-
- react-
|
|
28
|
+
- vite-plugin-web-routes: Automatically generate route files for your pages.
|
|
29
|
+
- react-query: Delegate Hydrated State
|
|
30
|
+
- react-router: The routing library for React, used to manage navigation within the app.
|
|
27
31
|
|
|
28
|
-
```bash
|
|
29
|
-
yarn add react-router-dom
|
|
30
|
-
```
|
|
31
32
|
|
|
32
33
|
## Basic Configuration Example
|
|
33
34
|
|
|
@@ -36,11 +37,19 @@ To use the plugin, you need to integrate it with Vite’s `defineConfig` method
|
|
|
36
37
|
```typescript
|
|
37
38
|
import { defineConfig } from "vite";
|
|
38
39
|
import react from "@vitejs/plugin-react";
|
|
39
|
-
import
|
|
40
|
+
import web from "vite-plugin-web-routes";
|
|
40
41
|
import ssr from "vite-plugin-ssr-config";
|
|
41
42
|
|
|
42
43
|
export default defineConfig({
|
|
43
|
-
plugins: [
|
|
44
|
+
plugins: [
|
|
45
|
+
react(),
|
|
46
|
+
web({
|
|
47
|
+
moduleFile: '.ssr/routes.tsx'
|
|
48
|
+
}),
|
|
49
|
+
ssr({
|
|
50
|
+
chacheDir: '.ssr'
|
|
51
|
+
})
|
|
52
|
+
],
|
|
44
53
|
});
|
|
45
54
|
```
|
|
46
55
|
|
|
@@ -49,15 +58,6 @@ export default defineConfig({
|
|
|
49
58
|
The following default values are provided for each configurable attribute in the plugin:
|
|
50
59
|
|
|
51
60
|
```typescript
|
|
52
|
-
import { defineConfig } from "vite";
|
|
53
|
-
import react from "@vitejs/plugin-react";
|
|
54
|
-
import pages from "vite-plugin-pages";
|
|
55
|
-
import ssr from "vite-plugin-ssr-config";
|
|
56
|
-
|
|
57
|
-
export default defineConfig({
|
|
58
|
-
plugins: [
|
|
59
|
-
react(),
|
|
60
|
-
pages(),
|
|
61
61
|
ssr({
|
|
62
62
|
root: process.cwd(), // Root directory, typically the project root.
|
|
63
63
|
|
|
@@ -92,9 +92,7 @@ export default defineConfig({
|
|
|
92
92
|
// Config callbacks
|
|
93
93
|
clientBuild: (config: UserConfig) => config, // Client-side Vite configuration.
|
|
94
94
|
serverBuild: (config: UserConfig) => config, // Server-side Vite configuration.
|
|
95
|
-
})
|
|
96
|
-
],
|
|
97
|
-
});
|
|
95
|
+
})
|
|
98
96
|
```
|
|
99
97
|
|
|
100
98
|
> Important Note: PageServer uses `suspense: true` in all requests to ensure proper SSR rendering. On the other hand, PageBrowser uses `suspense: false` to allow smooth client-side navigation. This setup guarantees correct SSR rendering while preventing flickering and inconsistencies between the server-rendered content and the client-side state during hydration.
|
package/dist/index.cjs
CHANGED
|
@@ -61,7 +61,7 @@ var assertSSRConfig = (ssrOpts = {}) => {
|
|
|
61
61
|
serverOutDir = "dist/",
|
|
62
62
|
serverMinify = false,
|
|
63
63
|
serverBuild = (config) => config,
|
|
64
|
-
clientOutDir = "dist/
|
|
64
|
+
clientOutDir = "dist/public",
|
|
65
65
|
clientMinify = true,
|
|
66
66
|
clientBuild = (config) => config
|
|
67
67
|
} = ssrOpts;
|
|
@@ -140,9 +140,22 @@ var copyFilesDirectory = (origin, target, {
|
|
|
140
140
|
}
|
|
141
141
|
});
|
|
142
142
|
};
|
|
143
|
+
var readManifest = (dir) => {
|
|
144
|
+
const candidates = [
|
|
145
|
+
import_path.default.resolve(dir, ".vite/manifest.json"),
|
|
146
|
+
import_path.default.resolve(dir, "manifest.json")
|
|
147
|
+
];
|
|
148
|
+
const manifestFile = candidates.find((file) => import_fs_extra.default.existsSync(file));
|
|
149
|
+
if (!manifestFile) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`manifest.json not found in:
|
|
152
|
+
${candidates.join("\n")}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return import_fs_extra.default.readJsonSync(manifestFile);
|
|
156
|
+
};
|
|
143
157
|
|
|
144
158
|
// src/plugin-build/build.ts
|
|
145
|
-
var import_fs_extra2 = __toESM(require("fs-extra"), 1);
|
|
146
159
|
var import_path2 = __toESM(require("path"), 1);
|
|
147
160
|
var import_vite = require("vite");
|
|
148
161
|
var doBuildServer = async (ssrConfig2, viteConfig) => {
|
|
@@ -167,9 +180,7 @@ var doBuildServer = async (ssrConfig2, viteConfig) => {
|
|
|
167
180
|
const { base = "/" } = viteConfig;
|
|
168
181
|
const ssrServerFile = import_path2.default.resolve(root, server);
|
|
169
182
|
const ssrPublicDir = import_path2.default.relative(serverOutDir, clientOutDir);
|
|
170
|
-
const
|
|
171
|
-
const manifestContent = import_fs_extra2.default.readFileSync(manifestFile, "utf-8");
|
|
172
|
-
const manifest = JSON.parse(manifestContent);
|
|
183
|
+
const manifest = readManifest(clientOutDir);
|
|
173
184
|
const manifestOut = manifest[entryClient].file;
|
|
174
185
|
const ssrEntryClientURL = finalUrl(base, manifestOut);
|
|
175
186
|
const ssrFiles = [
|
|
@@ -258,7 +269,6 @@ var doBuildClient = async (ssrConfig2, viteConfig) => {
|
|
|
258
269
|
write: true,
|
|
259
270
|
manifest: true,
|
|
260
271
|
minify: clientMinify,
|
|
261
|
-
target: "modules",
|
|
262
272
|
emptyOutDir: false,
|
|
263
273
|
outDir: clientOutDir,
|
|
264
274
|
rollupOptions: {
|
|
@@ -402,7 +412,7 @@ var pluginResolve = (ssrConfig2) => {
|
|
|
402
412
|
};
|
|
403
413
|
|
|
404
414
|
// src/plugin-serve/index.ts
|
|
405
|
-
var
|
|
415
|
+
var import_fs_extra2 = __toESM(require("fs-extra"), 1);
|
|
406
416
|
var import_path4 = __toESM(require("path"), 1);
|
|
407
417
|
var pluginServe = (ssrConfig2) => {
|
|
408
418
|
const { entryClient, root } = ssrConfig2;
|
|
@@ -424,10 +434,10 @@ var pluginServe = (ssrConfig2) => {
|
|
|
424
434
|
return async () => {
|
|
425
435
|
devServer.middlewares.use(async (req, res, next) => {
|
|
426
436
|
const indexHtmlPath = import_path4.default.join(root, req.url, "index.html");
|
|
427
|
-
if (
|
|
437
|
+
if (import_fs_extra2.default.existsSync(indexHtmlPath)) {
|
|
428
438
|
return devServer.transformIndexHtml(
|
|
429
439
|
req.url,
|
|
430
|
-
|
|
440
|
+
import_fs_extra2.default.readFileSync(indexHtmlPath, "utf-8")
|
|
431
441
|
).then((html) => {
|
|
432
442
|
res.setHeader("Content-Type", "text/html");
|
|
433
443
|
res.setHeader("Pragma", "no-cache");
|
package/dist/index.js
CHANGED
|
@@ -25,7 +25,7 @@ var assertSSRConfig = (ssrOpts = {}) => {
|
|
|
25
25
|
serverOutDir = "dist/",
|
|
26
26
|
serverMinify = false,
|
|
27
27
|
serverBuild = (config) => config,
|
|
28
|
-
clientOutDir = "dist/
|
|
28
|
+
clientOutDir = "dist/public",
|
|
29
29
|
clientMinify = true,
|
|
30
30
|
clientBuild = (config) => config
|
|
31
31
|
} = ssrOpts;
|
|
@@ -103,9 +103,22 @@ var copyFilesDirectory = (origin, target, {
|
|
|
103
103
|
}
|
|
104
104
|
});
|
|
105
105
|
};
|
|
106
|
+
var readManifest = (dir) => {
|
|
107
|
+
const candidates = [
|
|
108
|
+
path.resolve(dir, ".vite/manifest.json"),
|
|
109
|
+
path.resolve(dir, "manifest.json")
|
|
110
|
+
];
|
|
111
|
+
const manifestFile = candidates.find((file) => fs.existsSync(file));
|
|
112
|
+
if (!manifestFile) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`manifest.json not found in:
|
|
115
|
+
${candidates.join("\n")}`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return fs.readJsonSync(manifestFile);
|
|
119
|
+
};
|
|
106
120
|
|
|
107
121
|
// src/plugin-build/build.ts
|
|
108
|
-
import fs2 from "fs-extra";
|
|
109
122
|
import path2 from "path";
|
|
110
123
|
import { build, mergeConfig } from "vite";
|
|
111
124
|
var doBuildServer = async (ssrConfig2, viteConfig) => {
|
|
@@ -130,9 +143,7 @@ var doBuildServer = async (ssrConfig2, viteConfig) => {
|
|
|
130
143
|
const { base = "/" } = viteConfig;
|
|
131
144
|
const ssrServerFile = path2.resolve(root, server);
|
|
132
145
|
const ssrPublicDir = path2.relative(serverOutDir, clientOutDir);
|
|
133
|
-
const
|
|
134
|
-
const manifestContent = fs2.readFileSync(manifestFile, "utf-8");
|
|
135
|
-
const manifest = JSON.parse(manifestContent);
|
|
146
|
+
const manifest = readManifest(clientOutDir);
|
|
136
147
|
const manifestOut = manifest[entryClient].file;
|
|
137
148
|
const ssrEntryClientURL = finalUrl(base, manifestOut);
|
|
138
149
|
const ssrFiles = [
|
|
@@ -221,7 +232,6 @@ var doBuildClient = async (ssrConfig2, viteConfig) => {
|
|
|
221
232
|
write: true,
|
|
222
233
|
manifest: true,
|
|
223
234
|
minify: clientMinify,
|
|
224
|
-
target: "modules",
|
|
225
235
|
emptyOutDir: false,
|
|
226
236
|
outDir: clientOutDir,
|
|
227
237
|
rollupOptions: {
|
|
@@ -365,7 +375,7 @@ var pluginResolve = (ssrConfig2) => {
|
|
|
365
375
|
};
|
|
366
376
|
|
|
367
377
|
// src/plugin-serve/index.ts
|
|
368
|
-
import
|
|
378
|
+
import fs2 from "fs-extra";
|
|
369
379
|
import path4 from "path";
|
|
370
380
|
var pluginServe = (ssrConfig2) => {
|
|
371
381
|
const { entryClient, root } = ssrConfig2;
|
|
@@ -387,10 +397,10 @@ var pluginServe = (ssrConfig2) => {
|
|
|
387
397
|
return async () => {
|
|
388
398
|
devServer.middlewares.use(async (req, res, next) => {
|
|
389
399
|
const indexHtmlPath = path4.join(root, req.url, "index.html");
|
|
390
|
-
if (
|
|
400
|
+
if (fs2.existsSync(indexHtmlPath)) {
|
|
391
401
|
return devServer.transformIndexHtml(
|
|
392
402
|
req.url,
|
|
393
|
-
|
|
403
|
+
fs2.readFileSync(indexHtmlPath, "utf-8")
|
|
394
404
|
).then((html) => {
|
|
395
405
|
res.setHeader("Content-Type", "text/html");
|
|
396
406
|
res.setHeader("Pragma", "no-cache");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-ssr-config",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "A powerful Vite plugin designed to enable Server-Side Rendering (SSR) for React applications. It provides a comprehensive solution for bundling both SSR and CSR, with built-in support for React Router and React Query for efficient API handling and page rendering.",
|
|
6
6
|
"keywords": [
|
|
@@ -40,10 +40,14 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"dotenv-local": "^1.0.2",
|
|
43
|
-
"fs-extra": "^11.2.0"
|
|
43
|
+
"fs-extra": "^11.2.0",
|
|
44
|
+
"react": "^19.2.7",
|
|
45
|
+
"react-dom": "^19.2.7",
|
|
46
|
+
"react-router": "^7.17.0",
|
|
47
|
+
"react-router-dom": "^7.17.0"
|
|
44
48
|
},
|
|
45
49
|
"peerDependencies": {
|
|
46
|
-
"vite": "
|
|
50
|
+
"vite": ">=4.0.0"
|
|
47
51
|
},
|
|
48
52
|
"devDependencies": {
|
|
49
53
|
"@types/express": "^5.0.0",
|
package/ssr/entryClient.jsx
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import { PageBrowser } from "@ssr/pageBrowser.jsx";
|
|
2
|
-
import { startTransition, StrictMode } from "react";
|
|
3
|
-
import { hydrateRoot } from "react-dom/client";
|
|
4
|
-
|
|
5
|
-
startTransition(() => {
|
|
6
|
-
const hydratedState = JSON.parse(atob(window.__HYDRATED_STATE__));
|
|
7
|
-
const setHydratedState = () => {
|
|
8
|
-
throw Error("Changes Not Allowed");
|
|
9
|
-
};
|
|
10
|
-
hydrateRoot(
|
|
11
|
-
document,
|
|
12
|
-
<StrictMode>
|
|
13
|
-
<PageBrowser
|
|
14
|
-
hydratedState={hydratedState}
|
|
15
|
-
setHydratedState={setHydratedState}
|
|
16
|
-
/>
|
|
17
|
-
</StrictMode>,
|
|
18
|
-
{
|
|
19
|
-
onRecoverableError: (error, { componentStack }) => {
|
|
20
|
-
const logs = componentStack?.split("\n");
|
|
21
|
-
console.log("Error:", error, logs);
|
|
22
|
-
},
|
|
23
|
-
}
|
|
24
|
-
);
|
|
25
|
-
});
|
|
1
|
+
import { PageBrowser } from "@ssr/pageBrowser.jsx";
|
|
2
|
+
import { startTransition, StrictMode } from "react";
|
|
3
|
+
import { hydrateRoot } from "react-dom/client";
|
|
4
|
+
|
|
5
|
+
startTransition(() => {
|
|
6
|
+
const hydratedState = JSON.parse(atob(window.__HYDRATED_STATE__));
|
|
7
|
+
const setHydratedState = () => {
|
|
8
|
+
throw Error("Changes Not Allowed");
|
|
9
|
+
};
|
|
10
|
+
hydrateRoot(
|
|
11
|
+
document,
|
|
12
|
+
<StrictMode>
|
|
13
|
+
<PageBrowser
|
|
14
|
+
hydratedState={hydratedState}
|
|
15
|
+
setHydratedState={setHydratedState}
|
|
16
|
+
/>
|
|
17
|
+
</StrictMode>,
|
|
18
|
+
{
|
|
19
|
+
onRecoverableError: (error, { componentStack }) => {
|
|
20
|
+
const logs = componentStack?.split("\n");
|
|
21
|
+
console.log("Error:", error, logs);
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
});
|
package/ssr/entryRender.jsx
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
import { PageServer } from "@ssr/pageServer.jsx";
|
|
2
2
|
import { StrictMode } from "react";
|
|
3
|
+
import { Transform } from "stream";
|
|
3
4
|
import { renderToPipeableStream } from "react-dom/server";
|
|
4
5
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const createRequestContext = () => {
|
|
7
|
+
return {
|
|
8
|
+
state: {},
|
|
9
|
+
setHydratedState(partial) {
|
|
10
|
+
this.state = {
|
|
11
|
+
...this.state,
|
|
12
|
+
...partial,
|
|
13
|
+
};
|
|
14
|
+
},
|
|
9
15
|
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const renderDefault = async (request, response, next) => {
|
|
19
|
+
const context = createRequestContext();
|
|
10
20
|
const { pipe } = renderToPipeableStream(
|
|
11
21
|
<StrictMode>
|
|
12
22
|
<PageServer
|
|
13
23
|
path={request.originalUrl}
|
|
14
24
|
hydratedState={""} // Access is Not Allowed
|
|
15
|
-
setHydratedState={setHydratedState}
|
|
25
|
+
setHydratedState={context.setHydratedState.bind(context)}
|
|
16
26
|
/>
|
|
17
27
|
</StrictMode>,
|
|
18
28
|
{
|
|
@@ -22,13 +32,30 @@ const renderDefault = async (request, response, next) => {
|
|
|
22
32
|
},
|
|
23
33
|
onAllReady: () => {
|
|
24
34
|
// console.log(request.originalUrl, "onAllReady");
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
let injected = false;
|
|
36
|
+
const transform = new Transform({
|
|
37
|
+
transform(chunk, encoding, callback) {
|
|
38
|
+
if (!injected) {
|
|
39
|
+
const stateScript = `<script>window.__HYDRATED_STATE__ = "${btoa(JSON.stringify(context.state))}";</script>`;
|
|
40
|
+
const str = chunk.toString();
|
|
41
|
+
const idx = str.lastIndexOf("</head>");
|
|
42
|
+
if (idx !== -1) {
|
|
43
|
+
injected = true;
|
|
44
|
+
const out = str.slice(0, idx) + stateScript + str.slice(idx);
|
|
45
|
+
callback(null, out);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
callback(null, chunk);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
try {
|
|
53
|
+
response.setHeader("content-type", "text/html");
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error("Set-Header Context-Type text/html Error:", error);
|
|
56
|
+
}
|
|
57
|
+
transform.pipe(response);
|
|
58
|
+
pipe(transform);
|
|
32
59
|
},
|
|
33
60
|
onShellError: (error) => {
|
|
34
61
|
// console.log(request.originalUrl, "onShellError", error);
|
|
@@ -37,7 +64,7 @@ const renderDefault = async (request, response, next) => {
|
|
|
37
64
|
// console.log(request.originalUrl, "onError", error, errorInfo);
|
|
38
65
|
next(error);
|
|
39
66
|
},
|
|
40
|
-
}
|
|
67
|
+
},
|
|
41
68
|
);
|
|
42
69
|
};
|
|
43
70
|
|
package/ssr/pageBrowser.jsx
CHANGED
|
@@ -2,7 +2,7 @@ import { ErrorBoundary } from "@ssr/errorBoundary.jsx";
|
|
|
2
2
|
import { RootRoutes } from "@ssr/rootRoutes.jsx";
|
|
3
3
|
import { StrictMode, Suspense } from "react";
|
|
4
4
|
import { QueryClient, QueryClientProvider, hydrate } from "react-query";
|
|
5
|
-
import { BrowserRouter } from "react-router
|
|
5
|
+
import { BrowserRouter } from "react-router";
|
|
6
6
|
|
|
7
7
|
const queryClient = new QueryClient({
|
|
8
8
|
defaultOptions: {
|
package/ssr/pageServer.jsx
CHANGED
|
@@ -2,7 +2,7 @@ import { ErrorBoundary } from "@ssr/errorBoundary.jsx";
|
|
|
2
2
|
import { RootRoutes } from "@ssr/rootRoutes.jsx";
|
|
3
3
|
import { StrictMode, Suspense } from "react";
|
|
4
4
|
import { dehydrate, QueryClient, QueryClientProvider } from "react-query";
|
|
5
|
-
import { StaticRouter } from "react-router
|
|
5
|
+
import { StaticRouter } from "react-router";
|
|
6
6
|
|
|
7
7
|
export const PageServer = ({ path, hydratedState, setHydratedState }) => {
|
|
8
8
|
const queryServer = new QueryClient({
|
package/ssr/root.jsx
CHANGED
package/ssr/rootRoutes.jsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RootDocument } from "@ssr/root.jsx";
|
|
2
2
|
import React from "react";
|
|
3
|
-
import { useRoutes } from "react-router
|
|
4
|
-
import routes from "
|
|
3
|
+
import { useRoutes } from "react-router";
|
|
4
|
+
import routes from "./routes";
|
|
5
5
|
|
|
6
6
|
export const RootRoutes = (props) => {
|
|
7
7
|
const newRoutes = [
|