vue3项目之VUE-SSR 第一次改造方案(二)

VUE-SSR 第一次改造方案(二)

先实现客户端渲染
  • main.ts

实现导出一个 createApp 函数,再去 router、vuex 中实现类似的函数 createSSR...()

下面是全新的 mian.ts

import { createSSRApp } from 'vue'
import App from './App.vue'
import './style.css'

import { createSSRRouter } from './router'
import { createSSRStore, key } from './store'

import ElementPlus from 'element-plus'
import { ID_INJECTION_KEY } from 'element-plus'
import 'element-plus/dist/index.css'

import { createSSRI18n } from '@/language/i18n'

import '@/mock/mockServe'

export function createApp() {
    const app = createSSRApp(App)
    
    // 路由
    const router = createSSRRouter()
    app.use(router)
    
    // vuex
    const store = createSSRStore()
    app.use(store, key)
    
    // ElementPlus
    app.use(ElementPlus)
    app.provide(ID_INJECTION_KEY, {
        prefix: Math.floor(Math.random() * 10000),
        current: 0,
    })
    
    // 语言配置
    const i18n = createSSRI18n()
    app.use(i18n)

    return { app, router, store }
}

router 导出长这样

export function createSSRRouter() {

    return createRouter({
        history: import.meta.env.SSR ? createMemoryHistory() : createWebHistory(),
        routes,
    })

}
  • entry-client.ts

客户端入口文件,(之前一直是 main.ts为入口文件,现在入口文件需要区分)

这里主要用于客户端初始化

import { createApp } from "./main"
import { airbnbDB } from '@/db/index';
import stores from '@/db/index'


const { app, router, store } = createApp()

router.beforeEach(async function () {
    
    // 页面刷新时执行该回调函数
    
    // 一般用于初始化客户端的 vuex 数据
    ...
    
}) 


router.isReady().then(function() {
    app.mount('#app')
})
  • index.html

将首页的渲染方式改成 entry-client.ts 客户端渲染

并使用 <!--ssr-outlet--> 用作服务端渲染的 HTML 的占位符

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Airbnb</title>
</head>

<body>
    <div id="app"><!--ssr-outlet--></div>
    <script type="module" src="/src/entry-client.ts"></script>
</body>

</html>
5.2.3 再实现服务端渲染

这里用的 vite 服务端渲染模板!!

  • server.js

需要先下载 express 服务器框架,下面是模板

import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
import express from 'express'
import { createServer as createViteServer } from 'vite'

const __dirname = path.dirname(fileURLToPath(import.meta.url))

async function createServer() {
    const app = express()

    // 以中间件模式创建 Vite 应用,这将禁用 Vite 自身的 HTML 服务逻辑
    // 并让上级服务器接管控制
    const vite = await createViteServer({
        server: { middlewareMode: true },
        appType: 'custom'
    })

    // 使用 vite 的 Connect 实例作为中间件
    // 如果你使用了自己的 express 路由(express.Router()),你应该使用 router.use
    app.use(vite.middlewares)

    app.use('*', async (req, res, next) => {
        const url = req.originalUrl

        try {
            // 1. 读取 index.html
            let template = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf-8')

            // 2. 应用 Vite HTML 转换。这将会注入 Vite HMR 客户端,
            //    同时也会从 Vite 插件应用 HTML 转换。
            //    例如:@vitejs/plugin-react 中的 global preambles
            template = await vite.transformIndexHtml(url, template)

            // 3. 加载服务器入口。vite.ssrLoadModule 将自动转换
            //    你的 ESM 源码使之可以在 Node.js 中运行!无需打包
            //    并提供类似 HMR 的根据情况随时失效。
            const { render } = await vite.ssrLoadModule('/src/entry-server.ts')

            // 4. 渲染应用的 HTML。这假设 entry-server.js 导出的 `render`
            //    函数调用了适当的 SSR 框架 API。
            //    例如 ReactDOMServer.renderToString()
            const appHtml = await render(url)

            // 5. 注入渲染后的应用程序 HTML 到模板中。
            const html = template.replace(`<!--ssr-outlet-->`, appHtml)

            // 6. 返回渲染后的 HTML。
            res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
        } catch (e) {
            // 如果捕获到了一个错误,让 Vite 来修复该堆栈,这样它就可以映射回
            // 你的实际源码中。
            vite.ssrFixStacktrace(e)
            next(e)
        }
    })

    app.listen(5173, function() {
        console.log('服务端渲染进行中...');
    })
}

createServer()
  • entry-server.ts

服务端渲染入口!!!

import { createApp } from "./main"
import { renderToString } from 'vue/server-renderer'

export async function render(url: string) {
    const { app, router } = createApp()

    await router.push(url)
    await router.isReady()

    const appHtml = renderToString(app)
    return appHtml
}
5.2.4 最后选择启动服务

服务端渲染:node server.js

客户端渲染:npm run dev

只需要启动一次即可!vite 都会帮我们自动更新