Skip to content

Commit 402d95c

Browse files
committed
feat(rspack): add React SSR example
1 parent d8e3f3c commit 402d95c

12 files changed

Lines changed: 1224 additions & 317 deletions

File tree

pnpm-lock.yaml

Lines changed: 951 additions & 311 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rsbuild/ssr/rsbuild.config.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import { type RequestHandler, type ServerAPIs, defineConfig, logger } from '@rsbuild/core';
1+
import { type RequestHandler, type SetupMiddlewaresContext, defineConfig, logger } from '@rsbuild/core';
22
import { pluginReact } from '@rsbuild/plugin-react';
33

44
export const serverRender =
5-
(serverAPI: ServerAPIs): RequestHandler =>
5+
(serverContext: SetupMiddlewaresContext): RequestHandler =>
66
async (_req, res, _next) => {
7-
const indexModule = await serverAPI.environments.ssr.loadBundle<{
7+
const indexModule = await serverContext.environments.ssr.loadBundle<{
88
render: () => string;
99
}>('index');
1010

1111
const markup = indexModule.render();
1212

13-
const template = await serverAPI.environments.web.getTransformedHtml('index');
13+
const template = await serverContext.environments.web.getTransformedHtml('index');
1414

1515
const html = template.replace('<!--app-content-->', markup);
1616

@@ -24,8 +24,8 @@ export default defineConfig({
2424
plugins: [pluginReact()],
2525
dev: {
2626
setupMiddlewares: [
27-
({ unshift }, serverAPI) => {
28-
const serverRenderMiddleware = serverRender(serverAPI);
27+
({ unshift }, serverContext) => {
28+
const serverRenderMiddleware = serverRender(serverContext);
2929

3030
unshift(async (req, res, next) => {
3131
if (req.method === 'GET' && req.url === '/') {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import React from "react";
2+
import ReactDOM from "react-dom";
3+
import { App } from "./components/app";
4+
5+
ReactDOM.hydrate(<App />, document.getElementById("root"));
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import React from "react";
2+
3+
export const App: React.FC = () => <p>Hello from Client</p>;

rspack/react-ssr-esm/dev.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { spawn } from "cross-spawn";
2+
import path from "path";
3+
import webpack from "webpack";
4+
import webpackConfigClient from "./webpack.config.client.js";
5+
import webpackConfigServer from "./webpack.config.server.js";
6+
import { fileURLToPath } from 'url'
7+
import { dirname } from 'path'
8+
9+
const __filename = fileURLToPath(import.meta.url)
10+
const __dirname = dirname(__filename)
11+
12+
const compiler = webpack([
13+
{
14+
...webpackConfigClient,
15+
mode: "development",
16+
devtool: "source-map",
17+
output: {
18+
...webpackConfigClient.output,
19+
filename: "[name].js",
20+
},
21+
},
22+
{
23+
...webpackConfigServer,
24+
mode: "development",
25+
devtool: "source-map",
26+
},
27+
]);
28+
29+
let node;
30+
31+
compiler.hooks.watchRun.tap("Dev", (compiler) => {
32+
console.log(`Compiling ${compiler.name} ...`);
33+
if (compiler.name === "server" && node) {
34+
node.kill();
35+
node = undefined;
36+
}
37+
});
38+
39+
compiler.watch({}, (err, stats) => {
40+
if (err) {
41+
console.error(err);
42+
process.exit(1);
43+
}
44+
console.log(stats?.toString("minimal"));
45+
const compiledSuccessfully = !stats?.hasErrors();
46+
if (compiledSuccessfully && !node) {
47+
console.log("Starting Node.js ...");
48+
node = spawn(
49+
"node",
50+
["--inspect", path.join(__dirname, "dist/server.js")],
51+
{
52+
stdio: "inherit",
53+
}
54+
);
55+
}
56+
});

rspack/react-ssr-esm/package.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "example-react-ssr-esm",
3+
"version": "1.0.0",
4+
"type": "module",
5+
"scripts": {
6+
"build:server": "webpack --config webpack.config.server.js",
7+
"build:client": "webpack --config webpack.config.client.js",
8+
"start": "node ./dist/server.js",
9+
"start:dev": "node dev.js"
10+
},
11+
"devDependencies": {
12+
"@types/cross-spawn": "^6.0.2",
13+
"@types/express": "^4.17.11",
14+
"@types/node": "^14.14.35",
15+
"@types/react": "^17.0.3",
16+
"@types/react-dom": "^17.0.3",
17+
"clean-webpack-plugin": "^3.0.0",
18+
"copy-webpack-plugin": "^8.1.0",
19+
"cross-spawn": "^7.0.3",
20+
"ts-loader": "^8.0.18",
21+
"typescript": "^4.2.3",
22+
"webpack": "^5.101.1",
23+
"webpack-cli": "^4.5.0",
24+
"webpack-manifest-plugin": "^3.1.0",
25+
"webpack-node-externals": "^2.5.2"
26+
},
27+
"dependencies": {
28+
"ejs": "^3.1.6",
29+
"express": "^4.17.1",
30+
"react": "^17.0.2",
31+
"react-dom": "^17.0.2"
32+
}
33+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import express from "express";
2+
import fs from "fs";
3+
import path from "path";
4+
import React from "react";
5+
import ReactDOMServer from "react-dom/server";
6+
import { App } from "../client/components/app";
7+
8+
const server = express();
9+
10+
server.set("view engine", "ejs");
11+
server.set("views", path.join(__dirname, "views"));
12+
13+
server.use("/", express.static(path.join(__dirname, "static")));
14+
15+
const manifest = fs.readFileSync(
16+
path.join(__dirname, "static/manifest.json"),
17+
"utf-8"
18+
);
19+
const assets = JSON.parse(manifest);
20+
21+
server.get("/", (req, res) => {
22+
const component = ReactDOMServer.renderToString(React.createElement(App));
23+
res.render("client", { assets, component });
24+
});
25+
26+
server.listen(3000, () => {
27+
console.log(`Server running on http://localhost:3000`);
28+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>React + Node.js App</title>
6+
</head>
7+
<body>
8+
<div id="root"><%- component %></div>
9+
<script defer="defer" src="<%= assets["client.js"] %>"></script>
10+
</body>
11+
</html>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"include": ["client"],
3+
"compilerOptions": {
4+
"module": "es6",
5+
"target": "es5",
6+
"moduleResolution": "node",
7+
"jsx": "react",
8+
"allowSyntheticDefaultImports": true,
9+
"forceConsistentCasingInFileNames": true,
10+
"skipLibCheck": true,
11+
"resolveJsonModule": true,
12+
"strict": true,
13+
"strictNullChecks": true,
14+
"noImplicitAny": true
15+
}
16+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"include": ["server"],
3+
"compilerOptions": {
4+
"module": "commonjs", // classic format that Node.js understands
5+
"esModuleInterop": true, // allow imports of modules in ES format
6+
"skipLibCheck": true, // only check types we refer to from our code
7+
"forceConsistentCasingInFileNames": true, // prevents cross-OS problems
8+
"resolveJsonModule": true, // enable import of JSON files
9+
"lib": ["es6", "dom"], // use JavaScript ES6 & DOM API
10+
"target": "es6", // compile to ES6
11+
"jsx": "react", // compile JSX to React.createElement statements for SSR
12+
"allowJs": true, // allow import of JS modules
13+
// enable strict type checking
14+
"strict": true,
15+
"strictNullChecks": true,
16+
"noImplicitAny": true,
17+
}
18+
}

0 commit comments

Comments
 (0)