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 

可以看到,项目主要分为clientservervite这几个部分,每个部分都承担着不同的职责。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技术。