Skip to content

Commit d35dd85

Browse files
authored
feat(rspack): add React SSR example (#330)
1 parent 543311d commit d35dd85

File tree

12 files changed

+429
-198
lines changed

12 files changed

+429
-198
lines changed

pnpm-lock.yaml

Lines changed: 194 additions & 192 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 === '/') {

rsbuild/vue3-element-plus/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// @ts-nocheck
33
// Generated by unplugin-vue-components
44
// Read more: https://github.com/vuejs/core/pull/3399
5+
// biome-ignore lint: disable
56
export {}
67

78
/* prettier-ignore */

rsbuild/vue3-vant/components.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// @ts-nocheck
33
// Generated by unplugin-vue-components
44
// Read more: https://github.com/vuejs/core/pull/3399
5+
// biome-ignore lint: disable
56
export {}
67

78
/* prettier-ignore */
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>Rspack + React + SSR + ESM</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 rspack from "@rspack/core";
4+
import rspackConfigClient from "./rspack.config.client.js";
5+
import rspackConfigServer from "./rspack.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 = rspack([
13+
{
14+
...rspackConfigClient,
15+
mode: "development",
16+
devtool: "source-map",
17+
output: {
18+
...rspackConfigClient.output,
19+
filename: "[name].js",
20+
},
21+
},
22+
{
23+
...rspackConfigServer,
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: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "example-react-ssr-esm",
3+
"version": "1.0.0",
4+
"type": "module",
5+
"scripts": {
6+
"build:server": "rspack --config rspack.config.server.js",
7+
"build:client": "rspack --config rspack.config.client.js",
8+
"start": "node ./dist/server.js",
9+
"start:dev": "node dev.js"
10+
},
11+
"devDependencies": {
12+
"@types/cross-spawn": "^6.0.6",
13+
"@types/express": "^5.0.3",
14+
"@types/node": "^22.17.1",
15+
"@types/react": "^19.1.9",
16+
"@types/react-dom": "^19.1.7",
17+
"cross-spawn": "^7.0.6",
18+
"typescript": "^5.9.2",
19+
"rspack-manifest-plugin": "^5.0.3",
20+
"@rspack/core": "1.4.11",
21+
"@rspack/cli": "1.4.11"
22+
},
23+
"dependencies": {
24+
"ejs": "^3.1.10",
25+
"express": "^5.1.0",
26+
"react": "^19.1.1",
27+
"react-dom": "^19.1.1"
28+
}
29+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import path from "path";
2+
import { RspackManifestPlugin } from "rspack-manifest-plugin";
3+
import { fileURLToPath } from "url";
4+
5+
const __filename = fileURLToPath(import.meta.url);
6+
const __dirname = path.dirname(__filename);
7+
8+
export default {
9+
name: "client",
10+
entry: {
11+
client: path.resolve(__dirname, "client/client.tsx"),
12+
},
13+
mode: "production",
14+
output: {
15+
clean: true,
16+
path: path.resolve(__dirname + "/dist/static"),
17+
filename: "[name].[contenthash].js",
18+
publicPath: "",
19+
},
20+
resolve: {
21+
extensions: [".ts", ".tsx", ".js"],
22+
},
23+
module: {
24+
rules: [
25+
{
26+
test: /\.tsx?$/,
27+
loader: "builtin:swc-loader",
28+
},
29+
],
30+
},
31+
target: "web",
32+
plugins: [new RspackManifestPlugin()],
33+
output: {
34+
module: true,
35+
},
36+
experiments: {
37+
outputModule: true,
38+
},
39+
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import path from 'path'
2+
import rspack from '@rspack/core'
3+
import { fileURLToPath } from 'url'
4+
import { dirname } from 'path'
5+
6+
const __filename = fileURLToPath(import.meta.url)
7+
const __dirname = dirname(__filename)
8+
9+
export default {
10+
name: 'server',
11+
entry: {
12+
server: path.resolve(__dirname, 'server', 'server.ts'),
13+
},
14+
mode: 'production',
15+
experiments: {
16+
outputModule: true,
17+
},
18+
output: {
19+
module: true,
20+
path: path.resolve(__dirname, 'dist'),
21+
filename: '[name].js',
22+
},
23+
externalsType: 'node-commonjs',
24+
externals: ['react', 'express', 'react-dom/server'],
25+
resolve: {
26+
extensions: ['.ts', '.tsx'],
27+
},
28+
module: {
29+
rules: [
30+
{
31+
test: /\.tsx?$/,
32+
loader: "builtin:swc-loader",
33+
},
34+
],
35+
},
36+
target: 'node',
37+
node: {
38+
__dirname: false,
39+
__filename: false,
40+
},
41+
42+
plugins: [
43+
new rspack.BannerPlugin({
44+
banner: `
45+
import { fileURLToPath as __rspack_fileURLToPath } from 'url';
46+
import { dirname as __rspack_dirname } from 'path'
47+
const __filename = __rspack_fileURLToPath(import.meta.url);
48+
const __dirname = __rspack_dirname(__filename);
49+
`,
50+
raw: true,
51+
}),
52+
new rspack.CopyRspackPlugin({
53+
patterns: [{ context: 'server', from: 'views', to: 'views' }],
54+
}),
55+
],
56+
}

0 commit comments

Comments
 (0)