1
1
# 服务端渲染
2
2
3
+ :::warning 实验性
4
+ SSR 支持还处于试验阶段,您可能会遇到 bug 和不受支持的用例。请考虑您可能承担的风险。
5
+ :::
6
+
3
7
Vite 为服务端渲染(SSR)提供了内建支持。
4
8
5
9
:::tip 注意
@@ -10,7 +14,7 @@ SSR 特别指支持在 Node.js 中运行相同应用程序的前端框架(例
10
14
11
15
## 示例项目
12
16
13
- 这里的 Vite 范例包含了 Vue 3 和 React 的 SSR 设置示例,可以作为本指南的参考:
17
+ Vite 为服务端渲染(SSR)提供了内建支持。 这里的 Vite 范例包含了 Vue 3 和 React 的 SSR 设置示例,可以作为本指南的参考:
14
18
15
19
- [ Vue 3] ( https://github.com/vitejs/vite/tree/main/packages/playground/ssr-vue )
16
20
- [ React] ( https://github.com/vitejs/vite/tree/main/packages/playground/ssr-react )
@@ -24,29 +28,37 @@ SSR 特别指支持在 Node.js 中运行相同应用程序的前端框架(例
24
28
- src/
25
29
- main.js # 导出环境无关的(通用的)应用代码
26
30
- entry-client.js # 将应用挂载到一个 DOM 元素上
27
- - entry-server.js # 使用框架的 SSR API 渲染该应用
31
+ - entry-server.js # 使用某框架的 SSR API 渲染该应用
28
32
```
29
33
30
34
` index.html ` 将需要引用 ` entry-client.js ` 并包含一个占位标记供给服务端渲染时注入:
31
35
32
36
``` html
33
- <div id =" app" ><!-- app-html --> </div >
37
+ <div id =" app" ><!-- ssr-outlet --> </div >
34
38
<script type =" module" src =" /src/entry-client.js" ></script >
35
39
```
36
40
37
- 你可以使用任何你喜欢的占位标记来替代 ` <!--app-html --> ` ,只要它能够被正确替换。
41
+ 你可以使用任何你喜欢的占位标记来替代 ` <!--ssr-outlet --> ` ,只要它能够被正确替换。
38
42
39
- ::: tip
40
- 如果需要基于 SSR 和 client 执行条件逻辑,可以使用 ` import.meta.env.SSR ` 。这是在构建过程中被静态替换的,因此它将允许对未使用的分支进行摇树优化。
41
- :::
43
+ ## 情景逻辑
44
+
45
+ 如果需要基于 SSR 和 client 执行情景逻辑,可以使用:
46
+
47
+ ``` js
48
+ if (import .meta.env.SSR) {
49
+ // ... 仅在服务端的逻辑
50
+ }
51
+ ` ` `
52
+
53
+ 这是在构建过程中被静态替换的,因此它将允许对未使用的条件分支进行摇树优化。
42
54
43
55
## 设置开发服务器
44
56
45
- 在构建 SSR 应用程序时,您可能希望完全控制主服务器,并将 Vite 与生产环境解耦。因此,建议在中间件模式下使用 Vite。下面是一个关于 [ express] ( https://expressjs.com/ ) 的例子:
57
+ 在构建 SSR 应用程序时,您可能希望完全控制主服务器,并将 Vite 与生产环境解耦。因此,建议以中间件模式使用 Vite。下面是一个关于 [express](https://expressjs.com/) 的例子:
46
58
47
59
**server.js**
48
60
49
- ``` js{18-20 }
61
+ ` ` ` js{17 - 19 }
50
62
const fs = require (' fs' )
51
63
const path = require (' path' )
52
64
const express = require (' express' )
@@ -78,44 +90,44 @@ createServer()
78
90
下一步是实现 ` * ` 处理程序供给服务端渲染的 HTML:
79
91
80
92
` ` ` js
81
- app .use (' * ' , async (req , res ) => {
82
- const url = req .originalUrl
93
+ app .use (" * " , async (req , res ) => {
94
+ const url = req .originalUrl ;
83
95
84
96
try {
85
97
// 1. 读取 index.html
86
98
let template = fs .readFileSync (
87
- path .resolve (__dirname , ' index.html' ),
88
- ' utf-8'
89
- )
99
+ path .resolve (__dirname , " index.html" ),
100
+ " utf-8"
101
+ );
90
102
91
103
// 2. 应用 vite HTML 转换。这将会注入 vite HMR 客户端,and
92
104
// 同时也会从 Vite 插件应用 HTML 转换。
93
105
// 例如:@vitejs/plugin-react-refresh 中的 global preambles
94
- template = await vite .transformIndexHtml (url, template)
106
+ template = await vite .transformIndexHtml (url, template);
95
107
96
108
// 3. 加载服务器入口。vite.ssrLoadModule 将自动转换
97
109
// 你的 ESM 源代码将在 Node.js 也可用了!无需打包
98
110
// 并提供类似 HMR 的根据情况随时失效。
99
- const { render } = await vite .ssrLoadModule (' /src/entry-server.js' )
111
+ const { render } = await vite .ssrLoadModule (" /src/entry-server.js" );
100
112
101
113
// 4. 渲染应用的 HTML。这架设 entry-server.js 的导出 `render`
102
114
// 函数调用了相应 framework 的 SSR API。
103
115
// 例如 ReacDOMServer.renderToString()
104
- const appHtml = await render (url)
116
+ const appHtml = await render (url);
105
117
106
118
// 5. 注入应用渲染的 HTML 到模板中。
107
- const html = template .replace (` <!--ssr-outlet-->` , appHtml)
119
+ const html = template .replace (` <!--ssr-outlet-->` , appHtml);
108
120
109
121
// 6. 将渲染完成的 HTML 返回
110
- res .status (200 ).set ({ ' Content-Type' : ' text/html' }).end (html)
122
+ res .status (200 ).set ({ " Content-Type" : " text/html" }).end (html);
111
123
} catch (e) {
112
124
// 如果捕获到了一个错误,让 vite 来修复该堆栈,这样它就可以映射回
113
125
// 你的实际源代码中。
114
- vite .ssrFixStacktrace (e)
115
- console .error (e)
116
- res .status (500 ).end (e .message )
126
+ vite .ssrFixStacktrace (e);
127
+ console .error (e);
128
+ res .status (500 ).end (e .message );
117
129
}
118
- })
130
+ });
119
131
` ` `
120
132
121
133
` package .json ` 中的 ` dev` 脚本也应该相应地改变,使用服务器脚本:
@@ -150,44 +162,43 @@ app.use('*', async (req, res) => {
150
162
151
163
接着,在 ` server .js ` 中,通过检出 ` process .env .NODE_ENV ` 我们需要添加一些生产环境特定的逻辑:
152
164
153
- - 取而代之的是使用 ` dist/client/index.html ` 作为模板而不是读取根目录的 ` index.html ` ,因为它包含了到客户端构建的正确资源链接。
165
+ - 使用 ` dist/ client/ index .html ` 作为模板,而不是读取根目录的 ` index .html ` ,因为它包含了到客户端构建的正确资源链接。
154
166
155
- - 取而代之的是使用 ` require('./dist/server/entry-server.js') ` 而不是 ` await vite.ssrLoadModule('/src/entry-server.js') ` (该文件是 SSR 构建的最终结果)。
167
+ - 使用 ` require (' ./dist/server/entry-server.js' )` , 而不是 ` await vite .ssrLoadModule (' /src/entry-server.js' )` (该文件是 SSR 构建的最终结果)。
156
168
157
169
- 将 ` vite` 开发服务器的创建和所有使用都移到 dev-only 条件分支后面,然后添加静态文件服务中间件来服务 ` dist/ client` 中的文件。
158
170
159
171
可以在此参考 [Vue](https://github.com/vitejs/vite/tree/main/packages/playground/ssr-vue) 和 [React](https://github.com/vitejs/vite/tree/main/packages/playground/ssr-react) 的启动范例。
160
172
161
- ### 生成预加载指令
162
-
163
- > 此章节仅对 Vue 适用。
173
+ ## 生成预加载指令
164
174
165
- ` @vitejs/plugin-vue ` 将自动注册在向关联的 Vue SSR 上下文呈现请求期间实例化的组件模块 ID。这个信息可以用来推断异步 chunk 和资源,应该为给定的路由预加载。
166
-
167
- 为了利用这一点,添加 ` --ssrManifest ` 标志到客户端构建脚本(是的,从客户端构建生成 SSR 清单,因为我们希望将模块 ID 映射到客户端文件):
175
+ ` vite build` 支持使用 ` -- ssrManifest` 标志,这将会在构建输出目录中生成一份 ` ssr- manifest .json ` :
168
176
169
177
` ` ` diff
170
178
- " build:client" : " vite build --outDir dist/client" ,
171
179
+ " build:client" : " vite build --outDir dist/client --ssrManifest" ,
172
180
` ` `
173
181
174
- 这将生成一个 ` dist/client/ssr-manifest.json ` 文件,它包含了模块 ID 到它们关联的 chunk 和资源文件的映射。
182
+ 上面的脚本现在将会为客户端构建生成 ` dist/ client/ ssr- manifest .json ` (是的,该 SSR 清单是从客户端构建生成而来,因为我们想要将模块 ID 映射到客户端文件上)。清单包含模块 ID 到它们关联的 chunk 和资源文件的映射。
183
+
184
+ 为了利该清单,框架需要提供一种方法来收集在服务器渲染调用期间使用到的组件模块 ID。
175
185
176
- 接下来,在 ` src/entry-server.js ` 中 :
186
+ ` @vitejs / plugin - vue ` 支持该功能,开箱即用,并会自动注册使用的组件模块 ID 到相关的 Vue SSR 上下文 :
177
187
178
188
` ` ` js
179
- const ctx = {}
180
- const html = await renderToString (app, ctx)
189
+ // src/entry-server.js
190
+ const ctx = {};
191
+ const html = await vueServerRenderer .renderToString (app, ctx);
181
192
// ctx.modules 现在是一个渲染期间使用的模块 ID 的 Set
182
193
` ` `
183
194
184
- 我们现在需要读取该清单并将其传递到 ` src/entry-server.js ` 导出的 ` render ` 函数中,如此我们就有了足够的信息,来为被异步路由使用的文件渲染预加载指令 !查看 [ 示例代码] ( https://github.com/vitejs/vite/blob/main/packages/playground/ssr-vue/src/entry-server.js ) 查看完整示例 。
195
+ 我们现在需要在 ` server . js ` 的生产情景分支下读取该清单,并将其传递到 ` src/ entry- server .js ` 导出的 ` render` 函数中,这将为我们提供足够的信息,来为异步路由相应的文件渲染预加载指令 !查看 [示例代码](https://github.com/vitejs/vite/blob/main/packages/playground/ssr-vue/src/entry-server.js) 获取完整示例 。
185
196
186
197
## 预渲染 / SSG
187
198
188
- 如果预先知道某些路由所需的路由和数据,我们可以使用与生产环境 SSR 相同的逻辑将这些路由预先渲染到静态 HTML 中。这也被称为静态站点生成 (SSG)。查看 [ 示例渲染代码] ( https://github.com/vitejs/vite/blob/main/packages/playground/ssr-vue/prerender.js ) 查看有效示例 。
199
+ 如果预先知道某些路由所需的路由和数据,我们可以使用与生产环境 SSR 相同的逻辑将这些路由预先渲染到静态 HTML 中。这也被视为一种静态站点生成 (SSG)的形式 。查看 [示例渲染代码](https://github.com/vitejs/vite/blob/main/packages/playground/ssr-vue/prerender.js) 获取有效示例 。
189
200
190
- ## 启发式外部化
201
+ ## SSR 外部化
191
202
192
203
许多依赖都附带 ESM 和 CommonJS 文件。当运行 SSR 时,提供 CommonJS 构建的依赖关系可以从 Vite 的 SSR 转换/模块系统进行 “外部化”,从而加速开发和构建。例如,并非去拉取 React 的预构建的 ESM 版本然后将其转换回 Node.js 兼容版本,用 ` require (' react' )` 代替会更有效。它还大大提高了 SSR 包构建的速度。
193
204
@@ -199,7 +210,11 @@ Vite 基于以下启发式执行自动化的 SSR 外部化:
199
210
200
211
如果这个启发式导致了错误,你可以通过 ` ssr .external ` 和 ` ssr .noExternal ` 配置项手动调整。
201
212
202
- 在未来,这个启发式将可能得到改进,使其也能够外部化兼容 Node 的 ESM 构建依赖。(并在 SSR 模块加载时使用 ` import() ` 引入它们)。
213
+ 在未来,这个启发式将可能得到改进,将去探测该项目是否有启用 ` type: " module" ` ,因而 Vite 也可以外部化兼容 Node 的 ESM 构建依赖。(并在服务端渲染时使用动态 ` import ()` 引入它们)。
214
+
215
+ :::warning 使用别名
216
+ 如果你为某个包配置了一个别名,为了能使 SSR 外部化依赖功能正常工作,你可能想要使用的别名应该指的是实际的 ` node_modules` 中的包。[Yarn](https://classic.yarnpkg.com/en/docs/cli/add/#toc-yarn-add-alias) 和 [pnpm](https://pnpm.js.org/en/aliases) 都支持通过 ` npm: ` 前缀来设置别名。
217
+ :::
203
218
204
219
## SSR 专有插件逻辑
205
220
@@ -214,12 +229,12 @@ Vite 基于以下启发式执行自动化的 SSR 外部化:
214
229
` ` ` js
215
230
export function mySSRPlugin () {
216
231
return {
217
- name: ' my-ssr' ,
232
+ name: " my-ssr" ,
218
233
transform (code , id , ssr ) {
219
234
if (ssr) {
220
235
// 执行 ssr 专有转换...
221
236
}
222
- }
223
- }
237
+ },
238
+ };
224
239
}
225
240
` ` `
0 commit comments