返回

SSR入口点调整: Vite构建Server.ts全攻略

vue.js

SSR构建入口点调整

项目使用服务端渲染(SSR)时,通常会配置两个入口点:一个用于客户端打包,另一个用于服务器端渲染。一个常见的结构是将服务器端代码放在独立的 server.js 文件中,并配合 Vite 构建生成的 entry-server.js 进行渲染。问题在于,如果想将 server.js 重命名为 server.ts 并让其自身成为服务器端入口点,该如何处理?尤其是在保持现有 Vite 配置和 SSR 功能的前提下。

问题分析

默认的 Vite SSR 项目设置,服务器端 server.js 文件负责启动服务器和处理客户端的渲染请求,entry-server.js 是由 Vite 构建并用于实际 SSR 渲染的代码。 当你把 server.js 重命名为 server.ts 时,就需要承担构建它的责任。 之前的配置将 Vite 定位到 src/entry-server.ts,现在需要告诉 Vite,不仅需要构建 src 里的文件,还需要处理 server.ts 文件。

import './dist/server/entry-server.js' 这行代码现在失效是因为 Vite 构建时不再生成 entry-server.js,Vite构建 entry-server.ts。如果想要把 server.ts 设置为 SSR 构建的入口,需要在 Vite 配置中进行一些更改,让 Vite 正确处理所有依赖关系,并确保 server.ts 能够在构建后找到并使用 entry-server.js

解决方案一:Vite 构建服务器端代码

这个方法的核心思路是使用 Vite 的 build 命令来同时构建 server.ts 和客户端的 SSR 代码。这需要配置 Vite 才能正确地识别 server.ts 作为服务器端的入口点。

  1. 修改 vite.config.ts
    需要在 vite.config.ts 文件中添加对服务器端构建的支持,同时保留现有的 SSR 构建配置。 这涉及到调整 build.ssr 配置以处理 server.ts 并确保其正确地构建和输出。

    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import { resolve } from 'path'
    
    export default defineConfig({
      plugins: [vue()],
      build: {
        ssr: true,
      },
        resolve: {
           alias: {
             "@": resolve(__dirname, "src")
          }
        }
    })
    
    
  2. 创建 server.ts

      import express from 'express'
      import { createServer as createViteServer } from 'vite';
       import { render } from './dist/server/entry-server.js'  // 这里import 的内容将会由vite负责build生成。
    
    
      async function createServer() {
         const app = express()
        const vite = await createViteServer({
             server: { middlewareMode: true },
             appType: 'custom'
            });
    
         app.use(vite.middlewares);
    
    
       app.use('*', async (req, res,next) => {
           const url = req.originalUrl;
    
          try {
    
                let template=  await  vite.transformIndexHtml(url,   `<html><head></head><body><div id="app"></div></body></html>`)
    
    
                 const appHtml = await render(url)
    
    
    
             const html=  template.replace('<div id="app"></div>', appHtml )
    
               res.statusCode = 200;
                res.setHeader('Content-Type', 'text/html')
               res.write(html)
    
               res.end()
    
    
         }catch(e){
               if(e instanceof Error){
                    vite.ssrFixStacktrace(e);
                }
    
                next(e)
          }
    
    
    
      });
    
    
        return app;
       }
    
    
    
      createServer().then((app)=>{
    
             app.listen(5173, () => {
            console.log('App server is listen 5173 ')
    
              })
    
        })
    
  3. 修改 package.json 构建脚本
    修改 package.json 中的构建脚本,使用 Vite 的 vite build --ssr 命令同时构建客户端和服务端的代码。 添加一个针对服务端构建的 server 指令。

{
"scripts": {
"dev": "vite",
"build:client": "vite build",
"build:server": "vite build --ssr src/entry-server.ts && tsc -p ./tsconfig.server.json", // 这里build了client entry和 server.ts
"build": "npm run build:client && npm run build:server",
"preview": "node dist/server/server.js", // 直接执行server构建的结果
"start": "pnpm run build && pnpm run preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
},
"dependencies": {
"vue": "^3.4.19",
"express": "^4.18.2"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@vitejs/plugin-vue": "^5.0.4",
"prettier": "^3.2.5",
"vite": "^5.0.11",
"@vue/tsconfig": "^0.5.1" ,
"eslint": "^8.57.0",
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"typescript": "^5.2.2",
"esbuild": "^0.21.0"

 }

}
```

  1. 配置tsconfig.server.json:
    为了保证 server.ts的编译成功,还需要为服务器端编译创建一个配置文件 tsconfig.server.json, 声明使用commonjs编译输出
 {
    "extends": "./tsconfig.json",
    "compilerOptions": {
       "target": "es2020",
      "module": "commonjs",
       "outDir": "dist/server",
       "rootDir": ".",
      "declaration": false,
      "moduleResolution":"node",
      "esModuleInterop": true

     },
  "include":[ "server.ts" ],
    "exclude": ["src"]


}

  1. 执行构建

执行命令 pnpm run build, 将客户端代码和 server.ts 构建至 dist 文件夹。 使用 pnpm preview 可以运行 server 端代码。

该方法避免了多次构建,并保证了服务器端代码和客户端 SSR 代码的一致性。 构建命令变得稍微复杂一点,但是简化了项目的架构。

解决方案二:分离构建步骤

第二种方法,相对直接,可以采用分别构建 server.ts 和客户端 SSR 代码。虽然相对稍微繁琐,但可以更容易地对每部分的构建过程进行更细粒度的控制。

  1. 构建server.ts: 使用 TypeScript 编译器直接构建, 使用 tsc -p ./tsconfig.server.json, 配置文件复用上面的 tsconfig.server.json. 这个命令需要安装TypeScript(pnpm add typescript --dev)。 这个步骤可以独立于 Vite 完成,生成 dist/server/server.js

  2. 构建客户端SSR 代码: Vite 会按照原来的方式构建客户端代码和 entry-server.ts, 构建输出会到 dist/client,以及 dist/server/entry-server.js。 构建命令可以继续使用vite build指令。

  3. 修改 server.ts : 需要修改 server.ts 中的导入方式,由于是两步构建,entry-server.js 位置是在 dist/server目录下,则对应的 import 为 import { render } from './dist/server/entry-server.js'

  4. 调整package.json:

{
  "scripts": {
      "dev": "vite",
     "build:client": "vite build",
      "build:server": "tsc -p ./tsconfig.server.json",
      "build": "npm run build:client && npm run build:server",
      "preview": "node dist/server/server.js",
    "start": "pnpm run build && pnpm run preview",
   "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
      "format": "prettier --write src/"
    },
   "dependencies": {
       "vue": "^3.4.19",
        "express": "^4.18.2"
      },
   "devDependencies": {
     "@types/express": "^4.17.21",
    "@vitejs/plugin-vue": "^5.0.4",
      "prettier": "^3.2.5",
        "vite": "^5.0.11",
     "@vue/tsconfig": "^0.5.1" ,
     "eslint": "^8.57.0",
     "@typescript-eslint/eslint-plugin": "^7.1.0",
       "@typescript-eslint/parser": "^7.1.0",
     "typescript": "^5.2.2",
     "esbuild": "^0.21.0"

  }
 }
  1. 执行构建 : 使用 pnpm run build 进行构建,可以使用 pnpm preview 命令预览服务器端代码。

这种方法分离了 server.ts 构建过程,使得每个步骤可以更清晰地理解。 但是增加了一个额外的构建步骤,在调整编译细节方面会有更多的控制权。

安全提示

无论是采用哪种方式,都要注意:

  • 确保服务器端的依赖包正确安装,可以通过使用npm install express 来添加 express依赖。
  • server.ts 中的依赖模块做更严苛的版本控制,降低发生版本冲突的可能性。
  • 使用 cross-env 这样的工具统一开发环境和生成环境的命令。

通过以上两种方式,可以在项目中使用 server.ts 作为 SSR 构建的入口,并能根据实际的需求调整构建的细节。 选择哪种方式,取决于开发者对项目构建的熟悉程度,以及对构建过程控制的具体需求。