相关技术整理
-
-
客户端HRM(使用vite开发环境HRM,代码更新,vite服务自动构建并通知浏览器更新相关模块):
-
创建vite服务(createServer。开发服务:含监听、编译、推送、自动更新功能):
import { createServer } from 'vite’; const createViteServer=async ()=>{ await createServer({ server:{ middlewareMode:'ssr' // middlewareMode:true }, appType:'custom' }) } const viteServer=createViteServer(); -
将vite服务的请求功能注入到node服务中,而非重开端口(middlewares。向http://localhost:3000访问相关资源,就能实现vite HRM功能,不用另开vite服务端口):
import express from "express" const app=express(); app.use(viteServer.middlewares) app.use(express.static(".")); app.listen(3000, () => { console.log(`SSR服务器运行在端口:3000`); }) -
将html中客户端入口文件自动转译为编译后的入口文件(transformIndexHtml):
import { fileURLToPath } from 'node:url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const templateHTML=readFileSync(path.resolve(__dirname,'index.dev.html'),'utf-8') app.get("/:path",async(req,res)=>{ viteServer.transformIndexHtml(req.url,templateHTML) })
-
-
服务端HRM:
-
使用vite编译服务端代码(ssrLoadModule):
await viteServer.ssrLoadModule(path.resolve(__dirname,'./src/entry-server.js')) -
使用vite实现服务端代码更新,自动刷新页面(handleHotUpdate):
// vite.config.js { plugins: [ vue(), { name:'ssr-hrm-update', handleHotUpdate(ctx){ const {file,server}=ctx if(file.includes('./src/')&&file.endsWith('entry-server.js')){ server.ws.send({type:'full-reload'}) // return [] } } } ], }
-
-
node服务自动重启:
nodemon server.js
-
vue3 ssr热更新流程图
-
-
客户端、客户端和服务端公共代码更新:
代码更新 → 进入handleHotUpdate({file,server})回调函数 → 通知浏览器更新模块信息:server.ws.send({type:”update”,updates:[{// 模块更新信息}]}) → import.meta.hot.accept实现更新逻辑 → 浏览器加载更新模块,实现局部更新 -
服务端入口文件更新:
代码更新 → 进入handleHotUpdate({file,server})回调函数 → 通知浏览器重新加载页面:server.ws.send({type:”full-reload”}) → 浏览器重新加载页面:location.reload() -
node服务器文件更新:
代码更新 → nodemon监听并重启node服务 -
流程结构图:
[文件修改] ↓ [Vite Watcher 检测到变更] ↓ ┌─────────────────────────────┐ │ file.includes(...) → 客户端 HMR (update) │ │ file.endsWith(entry-server.js) → full-reload │ └─────────────────────────────┘ ↓ [浏览器执行 HMR 或页面刷新]
-
代码展示
import { fileURLToPath } from 'node:url'import express from "express";import { readFileSync } from 'fs'import path from 'path'import {stringify} from 'devalue'import { createServer } from 'vite'const __dirname = path.dirname(fileURLToPath(import.meta.url))class CreateServerDev{ constructor(){ this.vite=null } isDev(){ return process.env.NODE_ENV==='development' } createViteServerDev=async()=>{ this.vite=await createServer({ server:{ middlewareMode:'ssr' // middlewareMode:true }, appType:'custom' }) } registerViteMiddleware=(app)=>{ app.use(this.vite.middlewares) } buildServerEntry=async()=>{ return await this.vite.ssrLoadModule(path.resolve(__dirname,'./src/entry-server.js')) } transformHTML=(url,html)=>{ return this.vite.transformIndexHtml(url,html) }}const serverDev= new CreateServerDev()const getPort=()=>{ return process.env.PORT||3000}async function createDevServer(){ const app = express(); const templateHTML=readFileSync(path.resolve(__dirname,'index.dev.html'),'utf-8') await serverDev.createViteServerDev() await serverDev.registerViteMiddleware(app) /* 使用命名参数代替通配符* */ app.get("/:path",async(req,res)=>{ console.log('==服务端请求URL:==',req.url) const {render}=await serverDev.buildServerEntry() const {html:appHTML,pinia} = await render(req.url); let html=templateHTML.replace('<!--vue-ssr-outlet-->',appHTML) .replace('<!-- 你的 PINIA 字符串 -->',stringify(pinia.state.value)) .replace('<!-- 你的服务端 HTML 字符串 -->',appHTML) html=await serverDev.transformHTML(req.url,html) res.status(200).set({'Content-Type':'text/html'}).end(html) }) app.use(express.static(".")); // app.use('/src', express.static(path.resolve(__dirname, 'src'))); app.listen(getPort(), () => { console.log(`SSR服务器运行在端口 ${getPort()}`); })}createDevServer()