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 都会帮我们自动更新