如何快速搭建express+vite+vue3的SSR项目模板

前端 潘老师 1个月前 (03-24) 46 ℃ (0) 扫码查看

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技术。


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/front/16200.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】