版本:react 19。 vite 5.4.19 (vite 7中一些api行为不太一样)
关键步骤:
第一步:server返回html
- 一个server.js -- 使用类似模板语法,替换index.html里一些特定"模板"为React组件,组合成html dom
js
import {createServer} from 'vite';
import express from 'express';
import fs from 'node:fs'
import { fileURLToPath } from 'node:url'
import path from 'node:path'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const vite = await createServer({
server:{middlewareMode:true},
appType:'custom'
})
const app = express()
app.use(vite.middlewares)
app.use("*all", async (req,res)=>{
const template= await vite.transformIndexHtml(
req.url,
await fs.readFileSync(
path.resolve(__dirname, 'index.html'),
'utf-8',
)
)
const {getMyRender} = await vite.ssrLoadModule(path.resolve(__dirname, 'server-entry.jsx'))
//注意这里,vite7 不是这么用的,它需要.render;vite 5 vite.ssrLoadModule返回{ getMyRender: [Getter], [Symbol(Symbol.toStringTag)]: 'Module' }
const appHTML = await getMyRender(req.url)
//注意这里,vite7返回一个html对象,可以appHTMl.html ;vite 5这里就一个html字符串
const html = template
.replace(`<!--app-->`,()=>appHTML)//替换index.html里的模板为React渲染好的组件
//vite7的官方案例里给的可以分别替换head 和html body
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
})
app.listen(3000,()=>{
console.log('server running on 3000')
})
- 一个server-entry.js -- 调用
React.renderToString(<StrictMode><你的组件></StrictMode>)
返回html字符串,server.js里可以使用这个字符串来替换index.html里的"模板"
jsx
import { renderToString } from "react-dom/server"
import {App} from "./App"
import React, { StrictMode } from 'react'
export function getMyRender(){
const htmlstr= renderToString(<StrictMode><App/></StrictMode>)
return htmlstr
}
- 一个index.html -
<!--app-->
会被server.js替换成React组件,是渲染好的DOM,不是jsx。
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
</head>
<body><div id="app"><!--app--></div>
<script type="module" src="./client-entry.jsx"></script>
</body>
</html>
===
第二步: hydrate
关注一下index.html的 <script type="module" ...>
它引用了一个client-entry.jsx,这里就是水合发生的地方 1.准备client-entry. index.html里要注意挂载点<div id='app'>
要在那
jsx
import { StrictMode } from "react"
import { hydrateRoot } from "react-dom/client"
import {App} from "./App"
hydrateRoot(document.getElementById("app"),<StrictMode><App/></StrictMode>)
第三步 node server.js 运行即可
可能的报错
Hydration failed because the server rendered HTML didn't match the client.
debug:先不加水合(就是把index.html里的script先注释掉)看看渲染结果是不是正常,有可能vite版本会带来ssrLoadModule返回类型的细微差异,导致server拼的html不对,那水合时自然找不到对应的dom元素了,就会报错。server返回的dom与水合时的一定要对上哦