携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第24天,点击查看活动详情
Nodejs 集成 Vite 开发时的 SSR
- 初始化一个 vite-react 项目
在 index.html
中添加一行注释,供替换模版字符串使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="root">
<!-- APP_HTML -->
</div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
在 src 下添加 server-entry.jsx
文件
import ReactDOMServer from "react-dom/server";
import { App } from "./App";
export function render(url, context) {
return ReactDOMServer.renderToString(<App />);
}
- 安装 express
yarn add express
- server.js
const express = require("express");
const fs = require("fs");
const app = express();
const { createServer: createViteServer } = require("vite");
createViteServer({
server: {
middlewareMode: "ssr", // 启动 ssr
},
}).then((vite) => {
app.use(vite.middlewares); // vite 中间件
app.get("*", async (req, res) => {
// 读取文件
let template = fs.readFileSync("index.html", "utf-8");
template = await vite.transformIndexHtml(req.url, template);
// 获取渲染函数
const { render } = await vite.ssrLoadModule("/src/server-entry.jsx");
// 渲染路由对应的 html
const html = await render(req.url);
// 替换字符串为 html 模版
const responseHtml = template.replace("<!-- APP_HTML -->", html);
res.set("content-type", "text/html").send(responseHtml);
});
app.listen(4000);
});
- 启动 node 服务
Node 集成正式 build 的 Vite 应用的 SSR
归档.zip 修改 package.json 脚本
"scripts": {
"dev": "vite",
"build": "npm run build:client && npm run build:server",
"build:client": "vite build --outDir dist/client",
"build:server": "vite build --outDir dist/server --ssr src/server-entry.jsx",
"serve": "vite preview"
},
执行 npm run buid
, 打包出两份文件
修改
server.js
, 作为生产环境使用
const express = require("express");
const fs = require("fs");
const app = express();
app.use(express.static("dist/client")); // 静态资源目录映射
// 获取 html 模版
const template = fs.readFileSync("dist/client/index.html", "utf-8");
app.get("*", async (req, res) => {
// 获取渲染函数
const { render } = require("./dist/server/server-entry");
const context = {};
// 渲染路由对应的 html
const html = await render(req.url, context);
if (context.url) {
// 如果有此路由,重定向此路由即可
res.redirect(301, context.url);
return;
}
// 替换字符串为 html 模版
const responseHtml = template.replace("<!-- APP_HTML -->", html);
res.set("content-type", "text/html").send(responseHtml);
});
app.listen(4000);
- 启动
node server.js
通过 SSR 功能实现静态站点导出
将源码解析,生成路由对应的 html 页面
const path = require("path");
const fs = require("fs");
const template = fs.readFileSync("dist/client/index.html", "utf-8");
const { render } = require("./dist/server/server-entry");
const routesToRender = fs.readdirSync("src/pages").map((file) => {
return file.replace(".jsx", "").toLowerCase();
});
for (const route of routesToRender) {
const context = {};
// 渲染路由对应的 html
const html = render(route, context);
// 替换字符串为 html 模版
const responseHtml = template.replace("<!-- APP_HTML -->", html);
const filePath = `dist/static/${route}.html`;
fs.writeFileSync(filePath, responseHtml);
console.log(`prerender ${filePath}`);
}