Deep_dive_into_Nuxt_Server_Components.pdf 5708427
自我介绍
wattanx - Overview
wattanx has 96 repositories available. Follow their code on GitHub.
- STORES 株式会社软件工程师
- 喜欢猫,居住在大阪
- Nuxt 项目贡献者,Nuxt Bridge 维护者
目录/大纲
- Nuxt 的渲染模式和挑战
- Nuxt Server Components 简介
- Nuxt Server Components 的应用方法
- 动手学习 Nuxt Server Components
- 与其他类似技术的区别
开场
本次演讲目标
让大家了解 Nuxt Server Components:
- 能做什么
- 解决什么问题
- 工作原理是什么
- 与其他技术有何不同
Nuxt 的渲染模式
支持的渲染模式
- 客户端渲染(Client-Side Rendering)
- 服务端渲染(Server-Side Rendering)
客户端渲染的特点
- 动态获取数据
- 使用 JavaScript 将获取的数据显示在页面上
- 使用 JavaScript 实现页面跳转
客户端渲染流程
- 浏览器请求页面
- 服务器返回空 HTML
- 浏览器下载 JavaScript 代码
- 执行 JavaScript 进行渲染
graph TD A[HTML] --> B[GET /bundle.js] B --> C[JS] C --> D[Render]
graph LR A[空的 HTML 被下载] --> B[JS 被下载] B --> C[渲染]
客户端渲染的问题
- 性能问题
- 需要等待浏览器下载、解析和执行 JavaScript
- 内容显示时间较慢
- 搜索引擎优化问题
- 对爬虫不友好
- 虽然有些爬虫可以解析 JavaScript,但仍有局限
服务端渲染(SSR)
- 解决了客户端渲染的问题
- 服务器返回完整渲染好的 HTML 给浏览器
服务端渲染流程
- 浏览器请求页面
- 服务器返回渲染好的 HTML
- 浏览器下载 JavaScript
- 进行水合(Hydration)
sequenceDiagram participant Browser participant Server Browser->>Server: GET / Server-->>Browser: HTML Browser->>Server: GET /bundle.js Server-->>Browser: JS Browser->>Browser: Hydration Note over Browser: 绑定事件监听器<br/>建立响应式数据 Browser->>Browser: 交互事件 Browser->>Browser: 重新渲染 Note over Browser: 客户端渲染
Hydration 解释
- 服务器返回的 HTML 没有事件监听器
- Hydration 是为 HTML 添加事件监听器的过程
sequenceDiagram participant Server participant Browser participant Vue.js Server->>Browser: 发送服务端渲染的 HTML Browser->>Browser: 显示静态 HTML Browser->>Server: 请求 JavaScript 包 Server->>Browser: 发送 JavaScript 包 Browser->>Vue.js: 初始化 Vue.js 应用 Vue.js->>Browser: 查找根 Vue 组件 Vue.js->>Browser: 附加事件监听器和数据绑定 Note over Browser,Vue.js: Hydration 完成 Browser->>Vue.js: 交互事件 Vue.js->>Browser: 更新 DOM Note over Browser,Vue.js: 客户端渲染
服务端渲染的遗留问题
- bundle 大小增长问题
举例:使用 markdown 转 HTML 显示内容的场景
- 代码在服务端和客户端都要执行(假设使用 SSR)
- 原因:两边要执行得到相同的 DOM 结构才能进行 Hydration
- 导致客户端 bundle 包含了大型库:
- sanitize-html: 194.8KB (80.9KB gzipped)
- marked: 35.9KB (11.2KB gzipped)
Q:为什么需要在服务器和客户端同时执行
A:因为如果两者不执行相同的 DOM 结构,就无法进行 Hydration
Nuxt Server Components 介绍
如果有只在服务器上渲染的组件就能解决。
有那么方便的机制吗?
概述
• 从Nuxt 3引入的功能
• 仍然是实验性的
• 可以创建仅在服务器上渲染的组件
主要特点
- 只在服务器端执行,无需 Hydration
- 不必要的 JS 代码不会打包到客户端
- 不强制要求服务器
- 可以在构建时预渲染所有 Server Components
- 适用于完全静态的网站
性能提升
- 使用 Server Components 可减少约 66.4% 的 bundle 大小
- 详细代码示例: https://github.com/wattanx/mini-nuxt-sc/pull/1
使用方法
- 文件扩展名改为
.server.vue
- 使用 NuxtIsland 组件
- 启用 experimental.componentIslands 配置
NuxtIsland 组件
将扩展名更改为 .server.vue 时,会变成服务器组件。
- Server Component 的基础设施
- .server.vue 会被转换为使用 NuxtIsland 组件的代码
- components/islands 目录下的组件会只在服务器端渲染
使用者可以不用 import 使用
或者从 #components 导入
(从实际路径导入尚未支持)
• 服务器组件的基础
.server.vue
将被转换为使用 Nuxtlsland 组件的代码
• 在 components/islands 中创建组件时,将成为只能在服务器上渲染的组件
静态部分称为岛屿(Island)
NuxtIsland
是在动态部分中嵌入静态内容的架构。
功能特性
Nuxt Server Components 可以实现的功能(部分摘录)
• 可以使用异步组件
• 可以嵌套服务器组件和客户端组件
• 服务器组件中,只有一部分可以进行 Hydration
可以编写 Async 组件
通过使用 Slot 嵌套 Server Component 和 Client Component。
通过使用 nuxt-client 指令,可以对 Server Component 的一部分进行 Hydration。
应用场景
适合使用的场景
- 博客(大多数情况下不需要交互)
- 定时显示的组件
- 特定日期显示的组件
- 避免敏感信息提前包含在客户端 bundle 中
不适合使用的场景
- 频繁更新 props 的组件
- 每次 props 变化都需要服务器请求
注意事项
- Server Component 嵌套
- 每个 Server Component 渲染都需要服务器请求
- 嵌套会增加开销
工作原理详解
制作 Nuxt 服务器组件并解释其机制
最终完成的东西
- 作为服务器组件基础的 NuxtIsland
- 仅在服务器上渲染组件的机制
npm create vite-extra@latest app -- --template
ssr-vue-ts
Nuxt Server Components 的流(在服务器端渲染的情况下)
与传统渲染的差异
主要构成
Nuxt Server Components 的构成要素
• 渲染 Vue 组件的端点
• 在服务器上渲染 Vue 组件的处理
• 向渲染 Vue 组件的端点发送请求的组件
渲染 Vue 组件的端点
端点处理流程
从 URL 中提取组件名和 props 的处理
实际的 URL
/_nuxt_island/CodeExample_XdPPUtbPQW.json?props={"count":4}
从 URL 中提取组件名: :::highlight-text --- text: CodeExample --- :::
从查询参数中提取 :::highlight-text --- text: props: { count: 4 } --- :::
创建端点
- 提取组件名称和 props 并填入 context
- render 函数稍后说明
SSR 上下文
- 可以添加在 Server-Side Rendering 中想要使用的数据
- 在 Nuxt 中,也会使用请求事件
- 通过使用 useSSRContext 可以从组件内部访问
在服务器上渲染 Vue 组件的处理
创建组件名称与组件的映射。
创建动态渲染组件的组件
- 获取要渲染的组件
- 可以动态渲染组件
要在服务器上渲染 Vue 组件
使用 createSSRApp 和 renderToString
将动态组件渲染处理结合起来。
- 动态渲染组件
- 在服务器上渲染 Vue 组件
此render函数在/_nuxt_island执行
相当简化的代码
- /_nuxt_island 发起请求
- 由于在服务器上渲染在客户端,如果props发生变化则发起请求
- 渲染HTML字符串 不进行Hydration
Nuxtlsland 组件
Nuxtlsland 会发送 /_nuxt_island 请求并渲染响应的 HTML 字符串。
组件级别的渲染机制已完成
这样下去,Server Component 的子组件就不能拥有交互式的组件。
通过使用插槽,可以将服务器组件和客户端组件嵌套在一起。
问:包含插槽进行渲染会怎样?
答:会变成普通的HTML字符串,因此不会进行水合(没有事件处理程序等信息)。
完全渲染的HTML字符串中如何插入Slot
Teleport
目标是在槽被内容替换的状态下进行渲染。
在构建时转换为自定义占位符。
嵌套 Client Component 所需的事项
• 在构建时将
• 用 Teleport 包围 Slot 的内容进行渲染→接下来是这里
• 在占位符部分插入 Slot 的内容
像这样改写
为了使Teleport部分更容易理解,代码如下所示。
Teleport组件
•一个内置组件,可以将组件模板的一部分"传送"到位于该组件 DOM 层次结构之外的 DOM 节点
v-if 为 true 时插入到 body 元素中。
在服务器端渲染时,SSR Context 的 teleports 中会公开 Teleport 内的内容
(SSR Context 是传递给 renderToString 的第二个参数的对象)https://ja.vuejs.org/guide/scaling-up/ssr.htm|#teleports
当包含 Teleport 的 NuxtIsland 组件进行服务器端渲染时,可以获得如下的 Teleport 内的内容。
为了嵌套 Client Component 所需的事项
• 在构建时将
• 用 Teleport 包围并渲染 Slot 的内容
• 将 Slot 的内容插入到占位符部分
在构建时转换为自定义占位符。
当 Nuxtlsland 被渲染时,data-island-uid 将被设置。
查看 uid=xXX,slot=default 可以知道应该插入到哪里。
因为找到了插入的位置,所以插入了 Teleport 的内容(在服务器渲染时用正则表达式替换)。
在服务器上完成的 HTML
指定的属性一致
Teleport之后
问:为什么需要在 Server 和 Client 都执行?
答:因为如果不在两者上执行并达到相同的 DOM 结构,就无法进行 Hydration。
与在服务器上完成的 HTML 相比
组件渲染机制
- 使用 createSSRApp 和 renderToString
- 动态组件渲染
- Teleport 处理插槽内容
具体实现
详细的技术实现代码请参考: https://github.com/wattanx/mini-nuxt-sc
• 最小化 Nuxt 服务器组件
• 独立于 Nuxt 制作
• 使用 Vue 可以实现
与其他技术的对比
React Server Components
- 在打包前预渲染
- 不需要服务器也能运行
- 渲染为特殊的 RSC Payload 格式(而不是 HTML)
Island Architecture
- 在静态部分中嵌入动态(JS交互)部分的方法
- 与 Nuxt Server Components 相反(在动态部分嵌入静态内容)
总结
主要优点
- 在动态内容中嵌入静态内容的创新方案
- 显著减少客户端 bundle 大小
- 巧妙运用 Teleport 技术
未来展望
实验性功能尚在发展中:
- 支持远程源渲染
- 支持创建 Server Page
- 支持 Lazy Server Component
因为还在实验阶段,所以请使用并反馈。