我如何实现vue3 ssr热更新环境搭建?

106 阅读2分钟

相关技术整理

    1. 客户端HRM(使用vite开发环境HRM,代码更新,vite服务自动构建并通知浏览器更新相关模块):

      1. 创建vite服务(createServer。开发服务:含监听、编译、推送、自动更新功能):

        import { createServer } from 'vite’;
        const createViteServer=async ()=>{
        	await createServer({
        	  server:{
        	      middlewareMode:'ssr'
        	      // middlewareMode:true
        	  },
        	  appType:'custom'
        	})
        }
        const viteServer=createViteServer();
        
      2. 将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`);
        })
        
      3. 将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)
        })
        
    2. 服务端HRM:

      1. 使用vite编译服务端代码(ssrLoadModule):

        await viteServer.ssrLoadModule(path.resolve(__dirname,'./src/entry-server.js'))
        
      2. 使用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 []
                    }
                }
            }
          ],
        }
        
    3. node服务自动重启:nodemon server.js

vue3 ssr热更新流程图

    1. 客户端、客户端和服务端公共代码更新:代码更新 → 进入handleHotUpdate({file,server})回调函数 → 通知浏览器更新模块信息:server.ws.send({type:”update”,updates:[{// 模块更新信息}]}) → import.meta.hot.accept实现更新逻辑 → 浏览器加载更新模块,实现局部更新

    2. 服务端入口文件更新:代码更新 → 进入handleHotUpdate({file,server})回调函数 → 通知浏览器重新加载页面:server.ws.send({type:”full-reload”}) → 浏览器重新加载页面:location.reload()

    3. node服务器文件更新:代码更新 → nodemon监听并重启node服务

    4. 流程结构图:

      [文件修改][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()