章
目
录
Vue的服务端渲染(SSR)技术能有效提升应用的性能和用户体验。以往,大家常用Nuxt来实现Vue的SSR功能。不过在实际项目开发里,会碰到各种各样的情况。比如说,有些项目架构复杂,除了SSR功能外,还得兼顾后端接口聚合、AB实验以及多语言支持等业务需求,这时候Nuxt就有点力不从心了。还有一些老项目,最初不是基于Nuxt搭建的,而是使用webpack来实现SSR。另外,有时候团队为了遵循自定义的规范,不想受Nuxt设计规范的限制。基于这些原因,本文就来详细讲讲如何快速集成一个express+vite+vue3的SSR项目模板。
一、项目结构概览
在开始搭建之前,先了解一下这个项目的整体结构,如下:
project
- client
- pages # 存放每个页面的代码
- about
- home
- routes # 路由
- App.vue
- entry-client.ts # 客户端入口
- entry-server.ts # 服务端入口
- main.ts
- server
- app.vite.ts
- vite
- vite-ssr.ts
- vite.config.ts
可以看到,项目主要分为client
、server
和vite
这几个部分,每个部分都承担着不同的职责。client
目录存放与客户端相关的代码,server
目录用于服务器端的配置,vite
目录则包含Vite相关的配置文件。
二、Vite配置详解
(一)server/app.vite.ts
server/app.vite.ts
这个文件很关键,它主要负责启动Vite开发服务器,并将其集成到Express服务中。具体代码如下:
import express from 'express'
import { createServer } from 'vite'
import { viteSsrRender } from '../vite/vite-ssr'
const app = express()
const root = process.cwd()
async function startServer() {
// 通过createServer方法以代码方式启动Vite开发服务器,而不是用命令行启动,这样能更方便地集成到node服务端代码里
const vite = await await createServer({
configFile: `${root}/vite/vite.config.ts`,
})
//vite.middlewares用于处理静态资源请求,比如.ts、.vue文件,同时支持热模块替换(HMR)功能
app.use(vite.middlewares)
// 处理所有的HTTP请求
app.get('*', async (req, res) => {
// 进行SSR渲染
const html = await viteSsrRender(req, res, { vite })
// 将渲染后的HTML内容返回给客户端
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
})
// 启动服务器,监听3000端口
app.listen(3000, () => {
console.log('Server is running at http://localhost:3000')
})
}
startServer()
(二)vite.config.ts
vite.config.ts
是Vite的配置文件,在这里可以对Vite的各种行为进行设置。代码如下:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
// 启用Vue插件,让Vite支持Vue项目的构建
plugins: [vue()],
server: {
// 开启中间件模式,便于将Vite集成到自定义的Node服务器中,而不是让Vite单独运行
middlewareMode: true,
},
// 不自动处理HTML文件,开发者需要手动控制HTML的生成和渲染
appType: 'custom',
})
三、客户端与服务端入口文件及公共逻辑
(一)entry-client.ts
entry-client.ts
是客户端渲染或激活的入口文件,代码如下:
import { createApp } from './main'
async function main() {
// 创建Vue应用实例和路由实例
const { app, router } = createApp()
// 等待路由准备就绪
await router.isReady()
// 将Vue应用挂载到id为app的DOM元素上
app.mount('#app')
}
main()
(二)entry-server.ts
entry-server.ts
作为服务端渲染的入口文件,代码如下:
import { createApp as _createApp } from './main'
export async function createApp(context: { url: string }) {
// 创建Vue应用实例和路由实例
const { app, router } = _createApp()
// 检查上下文对象中是否包含url,若没有则报错
if (!context.url) {
console.error('context.url is required')
}
// 将当前请求的URL手动添加到路由中
router.push(context.url)
// 等待路由准备就绪
await router.isReady()
return { app }
}
(三)main.ts
main.ts
文件包含了客户端和服务端共享的公共逻辑,代码如下:
import { createSSRApp } from 'vue'
import App from './App.vue'
import { createRouterInstance } from './routes'
export function createApp() {
// 创建一个用于SSR的Vue应用实例
const app = createSSRApp(App)
// 创建路由实例
const router = createRouterInstance()
// 将路由挂载到Vue应用上
app.use(router)
return { app, router }
}
(四)路由配置
在路由配置中,由于服务端没有浏览器的history api,所以需要根据运行环境选择不同的history模式。代码如下:
import { createRouter, createMemoryHistory, createWebHistory } from 'vue-router'
export function createRouterInstance() {
// 判断当前代码是在服务端还是客户端执行
const isServer = typeof window === 'undefined'
return createRouter({
// 根据环境选择不同的history模式
history: isServer ? createMemoryHistory() : createWebHistory(),
routes: [
{
path: '/about',
// 动态导入about页面的组件
component: () => import('../pages/about/App.vue'),
name: 'about'
},
{
path: '/',
// 动态导入home页面的组件
component: () => import('../pages/home/App.vue'),
name: 'home',
},
],
})
}
四、SSR渲染实现
最后,来看一下SSR渲染的具体实现代码。viteSsrRender
函数负责将Vue应用渲染为HTML字符串,并返回给客户端。
import path from 'node:path'
import { renderToString } from 'vue/server-renderer'
const root = process.cwd()
export async function viteSsrRender (req, res, { vite }) {
try {
// 获取服务端渲染入口文件的路径
const entryServerPath = path.join(root, 'client/entry-server.ts')
// 动态加载服务端渲染的入口文件
const createApp = (await vite.ssrLoadModule(entryServerPath)).createApp
// 注入所需上下文数据并执行
const { app } = await createApp({ url: req.url })
// 将Vue应用渲染为HTML字符串
const renderedHtml = await renderToString(app)
// 构建完整的HTML页面
const html = `
<!DOCTYPE html>
<html>
<head>
<title>SSR App</title>
</head>
<body>
<div id="app">${renderedHtml}</div>
<script type="module" src="/client/entry-client.ts"></script>
</body>
</html>
`
return html
} catch (e) {
// 若渲染过程中出现错误,打印错误信息并返回空字符串
console.error(e)
return ''
}
}
通过以上步骤,我们就完成了一个express+vite+vue3的SSR项目模板的搭建。希望这篇文章能帮助大家在实际项目开发中顺利运用SSR技术。