Skip to content

Commit

Permalink
feat: add Rsbuild SSR express with manifest example (#178)
Browse files Browse the repository at this point in the history
  • Loading branch information
9aoy authored Nov 27, 2024
1 parent a43c868 commit 5b6cee3
Show file tree
Hide file tree
Showing 15 changed files with 373 additions and 2 deletions.
63 changes: 62 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions rsbuild/ssr-express-with-manifest/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Local
.DS_Store
*.local
*.log*

# Dist
node_modules
dist/

# IDE
.vscode/*
!.vscode/extensions.json
.idea
29 changes: 29 additions & 0 deletions rsbuild/ssr-express-with-manifest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Rsbuild Project

## Setup

Install the dependencies:

```bash
pnpm install
```

## Get Started

Start the dev server:

```bash
pnpm dev
```

Build the app for production:

```bash
pnpm build
```

Preview the production build locally:

```bash
pnpm preview
```
23 changes: 23 additions & 0 deletions rsbuild/ssr-express-with-manifest/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "rsbuild-ssr-express-with-manifest",
"version": "1.0.0",
"private": true,
"scripts": {
"build": "rsbuild build",
"dev": "node ./server.mjs",
"preview": "node ./prod-server.mjs"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@rsbuild/core": "1.1.0",
"@rsbuild/plugin-react": "1.0.6",
"@types/express": "^4.17.21",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"express": "^4.19.2",
"typescript": "^5.3.0"
}
}
50 changes: 50 additions & 0 deletions rsbuild/ssr-express-with-manifest/prod-server.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import express from "express";
import { createRequire } from "node:module";
import fs from "node:fs";
import path from "node:path";

const require = createRequire(import.meta.url);

const templateHtml = fs.readFileSync('./template.html', 'utf-8');

const serverRender = (_req, res) => {
const remotesPath = path.join(process.cwd(), `./dist/server/index.js`);

const importedApp = require(remotesPath);

const markup = importedApp.render();

const { entries } = JSON.parse(fs.readFileSync('./dist/manifest.json', 'utf-8'));

const { js, css } = entries['index'].initial;

const scriptTags = js.map(file => `<script src="${file}" defer></script>`).join('\n');
const styleTags = css.map(file => `<link rel="stylesheet" href="${file}">`).join('\n');

const html = templateHtml.replace("<!--app-content-->", markup).replace('<!--app-head-->', `${scriptTags}\n${styleTags}`);

res.status(200).set({ "Content-Type": "text/html" }).send(html);
};

const port = process.env.PORT || 3000;

export async function preview() {
const app = express();

app.get("/", (req, res, next) => {
try {
serverRender(req, res, next);
} catch (err) {
console.error("SSR render error, downgrade to CSR...\n", err);
next();
}
});

app.use(express.static("dist"));

app.listen(port, () => {
console.log(`Server started at http://localhost:${port}`);
});
}

preview(process.cwd());
40 changes: 40 additions & 0 deletions rsbuild/ssr-express-with-manifest/rsbuild.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { defineConfig } from "@rsbuild/core";
import { pluginReact } from "@rsbuild/plugin-react";

export default defineConfig({
plugins: [pluginReact()],
dev: {
writeToDisk: true
},
environments: {
web: {
output: {
target: "web",
},
source: {
entry: {
index: "./src/index",
},
},
output: {
manifest: true,
},
},
ssr: {
output: {
target: "node",
distPath: {
root: "dist/server",
},
},
source: {
entry: {
index: "./src/index.server",
},
},
},
},
tools: {
htmlPlugin: false
}
});
69 changes: 69 additions & 0 deletions rsbuild/ssr-express-with-manifest/server.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import express from "express";
import fs from 'node:fs';
import { createRsbuild, loadConfig, logger } from "@rsbuild/core";

const templateHtml = fs.readFileSync('./template.html', 'utf-8');

const serverRender = (serverAPI) => async (_req, res) => {
const indexModule = await serverAPI.environments.ssr.loadBundle("index");

const markup = indexModule.render();

const { entries } = JSON.parse(fs.readFileSync('./dist/manifest.json', 'utf-8'));

const { js, css } = entries['index'].initial;

const scriptTags = js.map(file => `<script src="${file}" defer></script>`).join('\n');
const styleTags = css.map(file => `<link rel="stylesheet" href="${file}">`).join('\n');

const html = templateHtml.replace("<!--app-content-->", markup).replace('<!--app-head-->', `${scriptTags}\n${styleTags}`);

res.writeHead(200, {
"Content-Type": "text/html",
});
res.end(html);
};

export async function startDevServer() {
const { content } = await loadConfig({});

// Init Rsbuild
const rsbuild = await createRsbuild({
rsbuildConfig: content,
});

const app = express();

// Create Rsbuild DevServer instance
const rsbuildServer = await rsbuild.createDevServer();

const serverRenderMiddleware = serverRender(rsbuildServer);

app.get("/", async (req, res, next) => {
try {
await serverRenderMiddleware(req, res, next);
} catch (err) {
logger.error("SSR render error, downgrade to CSR...\n", err);
next();
}
});

// Apply Rsbuild’s built-in middlewares
app.use(rsbuildServer.middlewares);

const httpServer = app.listen(rsbuildServer.port, () => {
// Notify Rsbuild that the custom server has started
rsbuildServer.afterListen();
});

rsbuildServer.connectWebSocket({ server: httpServer });

return {
close: async () => {
await rsbuildServer.close();
httpServer.close();
},
};
}

startDevServer();
26 changes: 26 additions & 0 deletions rsbuild/ssr-express-with-manifest/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
body {
margin: 0;
color: #fff;
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
background-image: linear-gradient(to bottom, #020917, #101725);
}

.content {
display: flex;
min-height: 100vh;
line-height: 1.1;
text-align: center;
flex-direction: column;
justify-content: center;
}

.content h1 {
font-size: 3.6rem;
font-weight: 700;
}

.content p {
font-size: 1.2rem;
font-weight: 400;
opacity: 0.5;
}
12 changes: 12 additions & 0 deletions rsbuild/ssr-express-with-manifest/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import './App.css';

const App = () => {
return (
<div className="content">
<h1>Rsbuild with React</h1>
<p>Start building amazing things with Rsbuild.</p>
</div>
);
};

export default App;
1 change: 1 addition & 0 deletions rsbuild/ssr-express-with-manifest/src/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="@rsbuild/core/types" />
Loading

0 comments on commit 5b6cee3

Please sign in to comment.