SSR入口点调整: Vite构建Server.ts全攻略
2025-01-18 17:20:38
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
作为服务器端的入口点。
-
修改
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") } } })
-
创建 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 ') }) })
-
修改
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"
}
}
```
- 配置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"]
}
- 执行构建
执行命令 pnpm run build
, 将客户端代码和 server.ts
构建至 dist
文件夹。 使用 pnpm preview
可以运行 server 端代码。
该方法避免了多次构建,并保证了服务器端代码和客户端 SSR 代码的一致性。 构建命令变得稍微复杂一点,但是简化了项目的架构。
解决方案二:分离构建步骤
第二种方法,相对直接,可以采用分别构建 server.ts
和客户端 SSR 代码。虽然相对稍微繁琐,但可以更容易地对每部分的构建过程进行更细粒度的控制。
-
构建
server.ts
: 使用 TypeScript 编译器直接构建, 使用tsc -p ./tsconfig.server.json
, 配置文件复用上面的tsconfig.server.json
. 这个命令需要安装TypeScript(pnpm add typescript --dev
)。 这个步骤可以独立于 Vite 完成,生成dist/server/server.js
。 -
构建客户端SSR 代码: Vite 会按照原来的方式构建客户端代码和
entry-server.ts
, 构建输出会到dist/client
,以及dist/server/entry-server.js
。 构建命令可以继续使用vite build
指令。 -
修改
server.ts
: 需要修改server.ts
中的导入方式,由于是两步构建,entry-server.js
位置是在dist/server
目录下,则对应的 import 为import { render } from './dist/server/entry-server.js'
。 -
调整
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"
}
}
- 执行构建 : 使用
pnpm run build
进行构建,可以使用pnpm preview
命令预览服务器端代码。
这种方法分离了 server.ts
构建过程,使得每个步骤可以更清晰地理解。 但是增加了一个额外的构建步骤,在调整编译细节方面会有更多的控制权。
安全提示
无论是采用哪种方式,都要注意:
- 确保服务器端的依赖包正确安装,可以通过使用
npm install express
来添加 express依赖。 - 对
server.ts
中的依赖模块做更严苛的版本控制,降低发生版本冲突的可能性。 - 使用
cross-env
这样的工具统一开发环境和生成环境的命令。
通过以上两种方式,可以在项目中使用 server.ts
作为 SSR 构建的入口,并能根据实际的需求调整构建的细节。 选择哪种方式,取决于开发者对项目构建的熟悉程度,以及对构建过程控制的具体需求。