From 9d2f26dbe04cdfe08cbdc4c94e2f6c2a2b812760 Mon Sep 17 00:00:00 2001
From: MarvinXu <272077995@qq.com>
Date: Wed, 16 Feb 2022 01:46:00 +0800
Subject: [PATCH 1/2] review: ssr
---
src/guide/scaling-up/ssr.md | 265 ++++++++++++++++++++++++------------
1 file changed, 179 insertions(+), 86 deletions(-)
diff --git a/src/guide/scaling-up/ssr.md b/src/guide/scaling-up/ssr.md
index 33ca9be11..e2b1bb5b2 100644
--- a/src/guide/scaling-up/ssr.md
+++ b/src/guide/scaling-up/ssr.md
@@ -8,174 +8,264 @@ outline: deep
### 什么是 SSR? {#what-is-ssr}
-Vue.js 是一个用于构建客户端应用程序的框架。默认情况下,Vue 组件的功能是在浏览器中产生和操作 DOM。但是我们也可以将该组件在服务端渲染成 HTML 字符串后直接返回给浏览器,最后再并将静态标记“水化”为可交互的客户端应用。
+Vue.js 是一个用于构建客户端应用的框架。默认情况下,Vue 组件在浏览器中生成和操作 DOM 作为输出。然而,我们也可以将相同的组件在服务端渲染成 HTML 字符串,直接返回给浏览器,最后再将静态的 HTML “激活” (hydrate) 为完全交互式的客户端应用。
-一个由服务端渲染的 Vue.js 应用可以被认为是“同构”或者“通用”的,因为应用程序的大部分代码都可以在**服务端**和**客户端**上运行。
+一个由服务端渲染的 Vue.js 应用也可以被认为是“同构的”或“通用的”,因为应用的大部分代码同时运行在服务端**和**客户端。
### 为什么要用 SSR? {#why-ssr}
+
+与客户端的单页应用 (SPA) 相比,SSR 的优势主要在于:
-与传统 SPA (单页应用程序 (Single-Page Application)) 相比,服务器端渲染 (SSR) 的优势主要在于:
+- **更快的内容到达时间**:这一点在慢网速或者运行缓慢的设备上尤为重要。服务端渲染的 HTML 无需等到所有的 JavaScript 都下载并执行完成之后才显示,所以你的用户将会更快地看到完整渲染的页面。除此之外,数据获取过程在首次访问时在服务端完成,相比于从客户端获取,可能有更快的数据库连接。这通常可以带来更高的[核心 Web 指标](https://web.dev/vitals/)评分、更好的用户体验,而对于那些“内容到达时间与转化率直接相关”的应用来说,这点可能至关重要。
-- **更快的内容到达时间**:更快的内容到达时间 (time-to-content),特别是对于缓慢的网络情况或运行缓慢的设备。无需等待所有的 JavaScript 都完成下载并执行,才显示服务器渲染的标记,所以你的用户将会更快速地看到完整渲染的页面。通常可以达到更好的 [Web 应用核心指标](https://web.dev/vitals/)、产生更好的用户体验,并且对于那些“内容到达时间 (time-to-content) 与转化率直接相关”的应用程序而言,服务器端渲染 (SSR) 至关重要。
+- **统一的心智模型**:你可以使用相同的语言以及相同的声明式、面向组件的心智模型来开发整个应用,而不需要在后端模板系统和前端框架之间来回切换。
-- **更统一的心智模型**:在开发整个应用程序时,你可以使用相同的语言和相同的声明式、面向组件的心理模型,而不需要在后端模板系统和前端框架之间来回切换。
-
-- **更好的 SEO**:由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。
+- **更好的 SEO**:搜索引擎爬虫可以直接看到完全渲染的页面。
:::tip
- 请注意,截至目前,Google 和 Bing 可以很好对同步 JavaScript 应用程序进行索引。在这里,同步是关键。如果你的应用程序初始展示 loading 菊花图,然后通过 Ajax 获取内容,抓取工具并不会等待异步完成后再行抓取页面内容。也就是说,如果 SEO 对你的站点至关重要,而你的页面又是异步获取内容,则你可能需要服务器端渲染 (SSR) 解决此问题。
+ 截至目前,Google 和 Bing 可以很好地对同步 JavaScript 应用进行索引。这里的“同步”是关键词。如果你的应用以一个 loading 动画开始,然后通过 Ajax 获取内容,爬虫并不会等到内容加载完成再抓取。也就是说,如果 SEO 对你的页面至关重要,而你的内容又是异步获取的,那么 SSR 可能是必需的。
:::
-使用服务器端渲染 (SSR) 时还需要有一些权衡之处:
+使用 SSR 时还有一些权衡之处需要考量:
-- 开发时需注意限制条件。仅供浏览器使用的代码,只能在某些生命周期钩子函数中使用;一些外部扩展库可能需要特殊处理,才能在服务器渲染应用程序中运行。
+- 开发中的限制。浏览器端特定的代码只能在某些生命周期钩子中使用;一些外部库可能需要特殊处理才能在服务端渲染的应用中运行。
-- 涉及构建设置和部署的更多要求。与可以部署在任何静态文件服务器上的完全静态 SPA 不同,服务器渲染应用程序,需要处于 Node.js 服务器运行环境。
+- 更多的与构建配置和部署相关的要求。服务端渲染的应用需要一个能让 Node.js 服务器运行的环境,不像完全静态的 SPA 那样可以部署在任意的静态文件服务器上。
-- 更高的服务器端负载。在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的服务器更加占用 CPU 资源,因此如果你预期在高流量环境下使用,请准备充足的服务器负载,并采用更合理的缓存策略。
+- 更高的服务端负载。在 Node.js 中渲染一个完整的应用要比仅仅托管静态文件更加占用 CPU 资源,因此如果你预期有高流量,请为相应的服务器负载做好准备,并采用合理的缓存策略。
-在对你的应用程序使用 SSR 之前,你应该问的第一个问题是:你是否真的需要它。这主要取决于内容到达时间对应用程序的重要程度。例如,如果你正在构建一个内部系统的仪表盘,初始加载时的额外几百毫秒并不重要,这种情况下去使用 SSR 将是一个小题大作之举。然而,如果对内容到达时间的要求是你应用中最关键的指标,那么 SSR 可以帮助你实现最优的初始加载性能。
+在为你的应用使用 SSR 之前,你首先应该问自己是否真的需要它。这主要取决于内容到达时间对应用的重要程度。例如,如果你正在构建一个内部的仪表盘,初始加载时的那额外几百毫秒对你来说并不重要,这种情况下使用 SSR 就有点小题大作了。然而,在内容到达时间极其重要的场景下,SSR 可以帮你实现在可能的情况下最优的初始加载性能。
### SSR vs. SSG {#ssr-vs-ssg}
-**静态站点生成 (SSG)**,也被称为预渲染,是另一种流行的快速构建网站的技术。如果服务器渲染页面所需的数据对于每个用户来说都是相同的,那么我们可以只渲染一次,而不是每次出现请求时都呈现页面,提前在构建过程中。预构建的页面被生成并作为静态 HTML 文件提供。
+**静态站点生成 (SSG)**,也被称为预渲染,是另一种流行的构建快速网站的技术。如果用服务端渲染一个页面所需的数据对每个用户来说都是相同的,那么我们可以只渲染一次,提前在构建过程中完成,而不是每次请求进来就重新渲染页面。预渲染的页面生成后作为静态 HTML 文件被服务器托管。
-SSG 保持了和 SSR 应用相同的性能表现:它提供了更短的内容到达耗时。同时它也更容易比一般的 SSR 应用更容易部署,因为其输出的都是静态资源与 HTML。关键是这个**静态**:SSG 仅可以用于页面为静态数据的场合,即数据在构建期间就已经完全获取到了,并在部署时不会再变化。每次数据变化时,都需要重新部署。
+SSG 保留了和 SSR 应用相同的性能表现:它带来了优秀的内容到达耗时性能。同时,它比 SSR 应用的花销更小,也更容易部署,因为它输出的是静态 HTML 和资源文件。这里的关键词是**静态**:SSG 仅可以用于消费静态数据的页面,即数据在构建期间就是已知的,并且在多次部署期间不会改变。每当数据变化时,都需要重新部署。
-如果你只是想要使用 SSR 来提升一些销售页面的 SEO (例如 `/`、`/about` 和 `/contact` 等),那么你应该考虑采用 SSG 代替 SSR。SSG 也非常适合构建基于内容的网站,比如文档或者博客。事实上,你现在正在阅读的这篇文档就是使用 [VitePress](https://vitepress.vuejs.org/) 所生成的,这是一个由 Vue 驱动的静态站点生成器。
+如果你调研 SSR 只是为了优化为数不多的营销页面的 SEO (例如 `/`、`/about` 和 `/contact` 等),那么你可能需要 SSG 而不是 SSR。SSG 也非常适合构建基于内容的网站,比如文档站点或者博客。事实上,你现在正在阅读的这个网站就是使用 [VitePress](https://vitepress.vuejs.org/) 静态生成的,它是一个由 Vue 驱动的静态站点生成器。
-## 基本使用 {#basic-usage}
+## 基础教程 {#basic-tutorial}
### 渲染一个应用 {#rendering-an-app}
-Vue 的服务端渲染 API 都被暴露在 `vue/server-renderer` 之下。
+让我们来看一个 Vue SSR 最基础的实战示例。
-让我们看看一个 Vue SSR 最基本骨架的实战示例。首先,创建一个新的文件夹,并在其中运行 `npm install vue`。接着,创建一个 `example.mjs` 文件:
+1. 创建一个新的文件夹,`cd` 进入
+2. 执行 `npm init -y`
+3. 在 `package.json` 中添加 `"type":"module"` 使 Node.js 以 [ES modules mode](https://nodejs.org/api/esm.html#modules-ecmascript-modules) 运行
+4. 执行 `npm install vue`
+5. 创建一个 `example.js` 文件:
```js
-// example.mjs
-// 这会用 Node.js 运行在服务器上
+// 此文件运行在 Node.js 服务器上
import { createSSRApp } from 'vue'
+// Vue 的服务端渲染 API 位于 `vue/server-renderer` 路径下
import { renderToString } from 'vue/server-renderer'
+
const app = createSSRApp({
- data: () => ({ msg: 'hello' }),
- template: `
{{ msg }}
`
+ data: () => ({ count: 1 }),
+ template: ``
})
-;(async () => {
- const html = await renderToString(app)
+
+renderToString(app).then((html) => {
console.log(html)
-})()
+})
```
接着运行:
```sh
-> node example.mjs
+> node example.js
```
-...将会打印出下面的内容:
+它应该会在命令行中打印出如下内容:
-```html
-hello
+```
+
```
-[`renderToString()`](/api/ssr.html#rendertostring) 接收一个 Vue 应用实例为参数,会返回一个 Promise,完成时得到该应用渲染完成的 HTML。当然你也可以使用 [Node.js Stream API](https://nodejs.org/api/stream.html) 或者 [Web Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) 来执行流式渲染。查看 [SSR API 参考](/api/ssr.html)获取完整的相关细节。
+[`renderToString()`](/api/ssr.html#rendertostring) 接收一个 Vue 应用实例作为参数,返回一个 Promise,当 Promise resolve 时得到应用渲染的 HTML。当然你也可以使用 [Node.js Stream API](https://nodejs.org/api/stream.html) 或者 [Web Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) 来执行流式渲染。查看 [SSR API 参考](/api/ssr.html)获取完整的相关细节。
-### 客户端水合 {#client-hydration}
+然后我们可以把 Vue SSR 的代码移动到一个服务器请求处理函数里,它将应用的 HTML 片段包裹为完整的页面 HTML。接下来的几步我们将会使用 [`express`](https://expressjs.com/):
-在真实的 SSR 应用中,服务端渲染出的标记基本上都会是以下面这样的方式内嵌于 HTML 页面中:
+- 执行 `npm install express`
+- 创建下面的 `server.js` 文件:
+
+```js
+import express from 'express'
+import { createSSRApp } from 'vue'
+import { renderToString } from 'vue/server-renderer'
+
+const server = express()
+
+server.get('/', (req, res) => {
+ const app = createSSRApp({
+ data: () => ({ count: 1 }),
+ template: ``
+ })
+
+ renderToString(app).then((html) => {
+ res.send(`
+
+
+
+ Vue SSR Example
+
+
+ ${html}
+
+
+ `)
+ })
+})
-```html{6}
-
-
- ...
-
-
-
-
+server.listen(3000, () => {
+ console.log('ready')
+})
```
-在客户端侧,Vue 需要执行**水合**这一步骤。它会创建与服务端相同的 Vue 应用,将每个组件匹配到它应该控制的 DOM 节点,并附加事件监听器,使应用变得可以交互。
+最后,执行 `node server.js`,访问 `http://localhost:3000`。你应该可以看到页面中的按钮了。
+
+[在 StackBlitz 上试试](https://stackblitz.com/fork/vue-ssr-example-basic?file=index.js)
+
+### 客户端激活 {#client-hydration}
-和只有客户端的应用唯一不同的地方就是我们需要使用 [`createSSRApp()`](/api/application.html#createssrapp) 而不是 `createApp()`:
+如果你点击该按钮,你会发现数字并没有改变。这段 HTML 在客户端是完全静态的,因为我们没有在浏览器中加载 Vue。
+
+为了使客户端的应用可交互,Vue 需要执行一个**激活**步骤。在激活过程中,Vue 会创建一个与服务端完全相同的应用实例,然后将每个组件与它应该控制的 DOM 节点相匹配,并添加 DOM 事件监听器。
+
+为了在激活模式下挂载应用,我们应该使用 [`createSSRApp()`](/api/application.html#createssrapp) 而不是 `createApp()`:
```js{2}
-// 这运行在浏览器中
+// 该文件运行在浏览器中
import { createSSRApp } from 'vue'
+
const app = createSSRApp({
- // ...和服务端完全一致的组件实例
+ // ...和服务端完全一致的应用实例
})
-// 挂载一个 SSR 应用在客户端,假设
-// HTML 已经被预渲染,并会执行
-// 水合过程,而不是挂载新的 DOM 节点
+
+// 在客户端挂载一个 SSR 应用时会假定
+// HTML 是预渲染的,然后执行激活过程,
+// 而不是挂载新的 DOM 节点
app.mount('#app')
```
-## 高阶解决方案 {#higher-level-solutions}
+### 代码结构
-虽然到目前为止的例子都比较简单,但满足生产环境需求的 SSR 应用程序是全栈项目,涉及到的东西远不止 Vue 的 API。我们将需要:
+想想我们该如何在客户端复用服务端的应用实现。这时我们就需要开始考虑 SSR 应用中的代码结构了——我们如何在服务器和客户端之间共享相同的应用代码呢?
-- 构建两次应用程序:一次用于客户端,另一次用于服务器。
+这里我们将演示最基础的设置。首先,让我们将应用的创建逻辑拆分到一个单独的文件 `app.js` 中:
+
+```js
+// app.js (在服务器和客户端之间共享)
+import { createSSRApp } from 'vue'
+
+export function createApp() {
+ return createSSRApp({
+ data: () => ({ count: 1 }),
+ template: `{{ count }}
`
+ })
+}
+```
+
+该文件及其依赖项在服务器和客户端之间共享——我们称它们为**通用代码**。 编写通用代码时需要注意许多事项,我们将[在下面讨论](#writing-ssr-friendly-code)。
+
+我们在客户端入口导入通用代码,创建应用程序并执行挂载:
+
+```js
+// client.js
+import { createApp } from './app.js'
+
+createApp().mount('#app')
+```
+
+服务器在请求处理函数中使用相同的应用创建逻辑:
+
+```js{2,5}
+// server.js (不相关的代码省略)
+import { createApp } from './app.js'
+
+server.get('/', (req, res) => {
+ const app = createApp()
+ renderToString(app).then(html => {
+ // ...
+ })
+})
+```
+
+此外,为了在浏览器中加载客户端文件,我们还需要:
+
+1. 在 `server.js` 中添加 `server.use(express.static('.'))` 来托管客户端文件。
+2. 将 `` 添加到 HTML 外壳以加载客户端入口文件。
+3. 通过在 HTML 外壳中添加 [Import Map](https://github.com/WICG/import-maps) 以支持在浏览器中使用 `import * from 'vue'`。
+
+[在 StackBlitz 上尝试完整的示例](https://stackblitz.com/fork/vue-ssr-example?file=index.js)。按钮现在可以交互了!
+
+## 更通用的解决方案 {#higher-level-solutions}
+
+从上面的例子到一个生产就绪的 SSR 应用还需要很多工作。我们将需要:
+
+- 支持 Vue SFC 且满足其他构建步骤要求。事实上,我们需要为同一个应用协调两个构建过程:一个用于客户端,一个用于服务器。
:::tip
- Vue 组件在用于 SSR 时的编译方式是不同的,模板被编译成字符串,而不是虚拟 DOM 渲染函数,以获得更高效的渲染性能。
+ Vue 组件用在 SSR 时的编译产物不同——模板被编译为字符串而不是 render 函数,以此提高渲染性能。
:::
-- 在服务器的请求处理程序中,需要用正确的外壳标记来渲染 HTML 页面,包括客户端资源的 `` 和资源标记。我们可能还需要在 SSR 和 SSG 模式之间作选择,甚至在同一个应用中混合使用这两种模式。
+- 在服务器请求处理函数中,确保返回的 HTML 包含正确的客户端资源链接和最优的资源加载提示 (如 prefetch 和 preload)。我们可能还需要在 SSR 和 SSG 模式之间切换,甚至在同一个应用中混合使用这两种模式。
-- 以通用方式管理路由、数据获取和状态管理存储。
+- 以一种通用的方式管理路由、数据获取和状态存储。
-这是相当高级别的,且高度依赖于你所选择的内置工具链的工作。因此,我们强烈建议采用更高阶的、相对独立的解决方案,为你抽象出复杂的东西。下面我们将介绍 Vue 生态系统中几个推荐的 SSR 解决方案。
+完整的实现会非常复杂,并且取决于你选择使用的构建工具链。因此,我们强烈建议你使用一种更通用的、偏好明显的 (opinionated) 解决方案,帮你抽象掉那些复杂的东西。下面推荐几个 Vue 生态中的 SSR 解决方案。
### Nuxt {#nuxt}
-[Nuxt](https://v3.nuxtjs.org/) 是一个高阶框架,构建于 Vue 生态系统之上,这为编写通用 Vue 应用程序提供了一种更简洁高效的开发体验。此外更好的是,你也可以用它来做静态站点生成!我们强烈建议你试一试它。
+[Nuxt](https://v3.nuxtjs.org/) 是一个构建于 Vue 生态系统之上的通用型框架,它为编写通用 Vue 应用提供了一种流线型的开发体验。更棒的是,你还可以把它当作一个静态站点生成器来用!我们强烈建议你试一试。
### Quasar {#quasar}
-[Quasar](https://quasar.dev) 是一个完全基于 Vue 的解决方案,使你可以构建目标为 SPA、SSR、PWA、移动端、桌面端和浏览器插件的应用,而只需要一套代码。它不仅只提供了一套构建步骤,还提供了一整套 Material Design 的组件库。
+[Quasar](https://quasar.dev) 是一个基于 Vue 的完整解决方案,它可以让你用同一套代码库构建不同目标的应用,如 SPA、SSR、PWA、移动端应用、桌面端应用以及浏览器插件。除此之外,它还提供了一整套 Material Design 风格的组件库。
### Vite SSR {#vite-ssr}
-Vite 提供了内置的[对 Vue 服务端渲染](https://vitejs.dev/guide/ssr.html)的支持,但更加偏底层。如果你想要直接使用 Vite,请查看 [vite-plugin-ssr](https://vite-plugin-ssr.com/),这是一个社区插件,为你抽象出了许多具有复杂的细节。
+Vite 提供了内置的[ Vue 服务端渲染支持](https://vitejs.dev/guide/ssr.html),但它在设计上是偏底层的。如果你想要直接使用 Vite,可以看看 [vite-plugin-ssr](https://vite-plugin-ssr.com/),一个帮你抽象掉许多复杂细节的社区插件。
-你也可以在[这里](https://github.com/vitejs/vite/tree/main/packages/playground/ssr-vue)查看一个 Vue + Vite SSR 项目示例,这可以作为一个项目的起点。请注意,只有当您有使用过 SSR / 构建工具的经验,并且真正想要对更高级别的体系结构进行完全控制时,才建议您这样做。
+你也可以在[这里](https://github.com/vitejs/vite/tree/main/packages/playground/ssr-vue)查看一个使用手动配置的 Vue + Vite SSR 的示例项目,以它作为基础来构建。请注意,这种方式只有在你有丰富的 SSR 和构建工具经验,且相比于更顶层的架构,你更倾向于拥有完整控制权时,才推荐使用。
## 书写 SSR 友好的代码 {#writing-ssr-friendly-code}
-无论你的构建设置或高层框架选择如何,有一些原则适用于所有 Vue SSR 应用程序。
+无论你的构建配置或顶层框架的选择如何,下面的原则在所有 Vue SSR 应用中都适用。
### 服务端的响应性 {#reactivity-on-the-server}
-在 SSR 期间,每一个请求 URL 都会映射到我们应用中的一个期望状态。没有用户交互和 DOM 更新,因此响应性在服务端是不需要的。为了更好的性能,默认情况下响应性在 SSR 期间是禁用的。
+在 SSR 期间,每一个请求 URL 都会映射到我们应用中的一个期望状态。因为没有用户交互和 DOM 更新,所以响应性在服务端是不必要的。为了更好的性能,默认情况下响应性在 SSR 期间是禁用的。
### 组件生命周期钩子 {#component-lifecycle-hooks}
-因为没有任何动态更新,像 `mounted``onMounted` 或者 `updated``onUpdated` 这样的生命周期钩子**不会**被在 SSR 期间调用,并只会在客户端侧运行。只有 `beforeCreate` 和 `created` 这两个钩子会在 SSR 期间被调用。
+因为没有任何动态更新,所以像 `mounted``onMounted` 或者 `updated``onUpdated` 这样的生命周期钩子**不会**在 SSR 期间被调用,并且只会在客户端运行。只有 `beforeCreate` 和 `created` 这两个钩子会在 SSR 期间被调用。
-你应该避免在代码中产生需要在 `beforeCreate` 和 `created``setup()` 或 `