一、背景与概念说明
Vue 在服务端渲染(SSR)过程中,会对组件模板进行两阶段编译:
- 阶段一(Transform) :生成用于描述结构的中间表达(IR, Intermediate Representation)。
- 阶段二(Codegen) :将中间表达转换为最终的字符串拼接指令(例如
_push、_renderSlot)。
而 <Suspense> 组件是 Vue 3 的一个特殊机制,用于异步内容加载与占位渲染。
在 SSR 环境下,Vue 需要为 <Suspense> 生成可在服务端正确处理异步与 fallback(回退内容)的渲染逻辑。
二、源码概览
1import { 2 type ComponentNode, 3 type FunctionExpression, 4 type SlotsExpression, 5 type TemplateChildNode, 6 type TransformContext, 7 buildSlots, 8 createCallExpression, 9 createFunctionExpression, 10} from '@vue/compiler-dom' 11import { 12 type SSRTransformContext, 13 processChildrenAsStatement, 14} from '../ssrCodegenTransform' 15import { SSR_RENDER_SUSPENSE } from '../runtimeHelpers' 16 17const wipMap = new WeakMap<ComponentNode, WIPEntry>() 18 19interface WIPEntry { 20 slotsExp: SlotsExpression 21 wipSlots: Array<{ 22 fn: FunctionExpression 23 children: TemplateChildNode[] 24 }> 25} 26
🔍 概念层拆解
ComponentNode:表示模板中的组件节点(如<Suspense>)。WeakMap<ComponentNode, WIPEntry>:用于临时保存「正在处理中(work in progress)」的 Suspense 组件信息。WIPEntry:存放两个关键内容:slotsExp: 代表 Suspense 的 slots 表达式(通过buildSlots生成)wipSlots: 存储每个 slot 的函数与对应的子节点列表
三、阶段一:ssrTransformSuspense
1export function ssrTransformSuspense( 2 node: ComponentNode, 3 context: TransformContext, 4) { 5 return (): void => { 6 if (node.children.length) { 7 const wipEntry: WIPEntry = { 8 slotsExp: null!, // to be immediately set 9 wipSlots: [], 10 } 11 wipMap.set(node, wipEntry) 12 wipEntry.slotsExp = buildSlots( 13 node, 14 context, 15 (_props, _vForExp, children, loc) => { 16 const fn = createFunctionExpression( 17 [], 18 undefined, // no return, assign body later 19 true, // newline 20 false, // suspense slots are not treated as normal slots 21 loc, 22 ) 23 wipEntry.wipSlots.push({ 24 fn, 25 children, 26 }) 27 return fn 28 }, 29 ).slots 30 } 31 } 32} 33
🧠 原理层说明
此函数完成「第一阶段(transform) 」任务:
- 检测该组件是否有子节点;
- 创建一个
WIPEntry存入全局的wipMap; - 调用
buildSlots构造 slots 的表达式; - 为每个 slot 生成一个函数表达式
fn; - 暂时不填充函数体(稍后在 phase 2 中完成);
💬 逐行解析
wipMap.set(node, wipEntry):标记当前<Suspense>节点正在处理中;buildSlots(...):解析模板中<template #default>与<template #fallback>之类的内容;createFunctionExpression(...):- 将生成的
fn与对应的子节点children存入wipSlots; - 返回
fn,最终形成 slots 的对象表达式。
⚖️ 对比分析
| 场景 | 普通组件 | <Suspense> 组件 |
|---|---|---|
| Slot 生成函数 | 同步生成并立即填充 | 延迟填充(分两阶段) |
| Transform 阶段 | 完成全部处理 | 仅建立 WIP 结构 |
四、阶段二:ssrProcessSuspense
1export function ssrProcessSuspense( 2 node: ComponentNode, 3 context: SSRTransformContext, 4): void { 5 const wipEntry = wipMap.get(node) 6 if (!wipEntry) { 7 return 8 } 9 const { slotsExp, wipSlots } = wipEntry 10 for (let i = 0; i < wipSlots.length; i++) { 11 const slot = wipSlots[i] 12 slot.fn.body = processChildrenAsStatement(slot, context) 13 } 14 context.pushStatement( 15 createCallExpression(context.helper(SSR_RENDER_SUSPENSE), [ 16 `_push`, 17 slotsExp, 18 ]), 19 ) 20} 21
🧩 原理层说明
这是 第二阶段(codegen) 的入口。
此时模板节点已被转换为结构化 IR(抽象表示),现在要:
- 填充每个 slot 的函数体;
- 输出最终的 SSR 渲染调用。
💬 逐步解析
wipMap.get(node):获取上阶段保存的临时状态;processChildrenAsStatement(slot, context):将 slot 子节点转换为可执行的 SSR 语句;slot.fn.body = ...:补全上阶段未填充的函数体;context.pushStatement(...):生成_push(ssrRenderSuspense(slots))调用。
最终生成的 SSR 代码结构大致如下:
1_push(ssrRenderSuspense(_push, { 2 default: () => { /* render main content */ }, 3 fallback: () => { /* render fallback */ } 4})) 5
🧭 逻辑对比
| 阶段 | 输入 | 输出 |
|---|---|---|
| Phase 1 | AST + TransformContext | WIPEntry(未完成函数) |
| Phase 2 | WIPEntry + SSRContext | 完整 SSR 代码语句 |
五、实践层:执行链分析
- 模板解析时遇到
<Suspense>; - 触发
ssrTransformSuspense; - 暂存 slot 信息;
- 所有模板节点 transform 完成后;
- 进入 codegen 阶段;
- 调用
ssrProcessSuspense; - 输出最终可执行 SSR 渲染函数。
这两阶段对应 Vue 编译流程的 “延迟处理机制” ,即在 transform 阶段只建立依赖关系,在 codegen 阶段再填充内容。
六、拓展与思考
1️⃣ 为什么使用 WeakMap
WeakMap 用于存储临时数据,不会影响垃圾回收(避免内存泄漏)。
每个节点对应的 WIPEntry 在生成代码后可被回收。
2️⃣ 为什么分两阶段
Suspense 的 children 可能包含异步或嵌套结构,无法在一次 transform 中立即处理完,因此拆成两个阶段以确保:
- Slot 函数能在后续拿到完整子节点;
- SSR 生成顺序保持一致。
3️⃣ 编译器设计哲学
这种设计体现了 Vue 编译器的「延迟求值」思想——
在 transform 阶段尽量只收集结构信息,在 codegen 阶段集中生成逻辑表达式。
七、潜在问题与调试建议
| 问题类型 | 可能原因 | 解决思路 |
|---|---|---|
| Suspense 不渲染 fallback | wipSlots 未正确填充 | 检查 transform 阶段是否被提前清理 |
| SSR 输出不正确 | context.helper 注册缺失 | 确认 SSR_RENDER_SUSPENSE 已导入 |
| 内存溢出 | 未清理 wipMap | 确认编译流程末尾自动 GC |
八、总结
ssrTransformSuspense 与 ssrProcessSuspense 共同完成了 <Suspense> 的 SSR 编译:
- 前者负责收集 slot 信息;
- 后者负责生成最终的服务端渲染调用;
- 通过两阶段机制实现异步安全的模板渲染逻辑。
这套机制充分展示了 Vue SSR 编译器中对「异步组件渲染」的精巧设计。
本文部分内容借助 AI 辅助生成,并由作者整理审核。
《Vue SSR 源码解析:ssrTransformSuspense 与 ssrProcessSuspense》 是转载文章,点击查看原文。
