Vue SSR 源码解析:ssrTransformSuspense 与 ssrProcessSuspense

作者:excel日期:2025/11/9

一、背景与概念说明

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) 」任务:

  1. 检测该组件是否有子节点;
  2. 创建一个 WIPEntry 存入全局的 wipMap
  3. 调用 buildSlots 构造 slots 的表达式;
  4. 为每个 slot 生成一个函数表达式 fn
  5. 暂时不填充函数体(稍后在 phase 2 中完成);

💬 逐行解析

  • wipMap.set(node, wipEntry):标记当前 <Suspense> 节点正在处理中;
  • buildSlots(...):解析模板中 <template #default><template #fallback> 之类的内容;
  • createFunctionExpression(...)
    • 参数为空;
    • undefined 表示暂不生成函数体;
    • true 表示函数体换行;
    • false 表示这是特殊 slot(Suspense 专用);
  • 将生成的 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(抽象表示),现在要:

  1. 填充每个 slot 的函数体;
  2. 输出最终的 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 1AST + TransformContextWIPEntry(未完成函数)
Phase 2WIPEntry + SSRContext完整 SSR 代码语句

五、实践层:执行链分析

  1. 模板解析时遇到 <Suspense>
  2. 触发 ssrTransformSuspense
  3. 暂存 slot 信息;
  4. 所有模板节点 transform 完成后;
  5. 进入 codegen 阶段;
  6. 调用 ssrProcessSuspense
  7. 输出最终可执行 SSR 渲染函数。

这两阶段对应 Vue 编译流程的 “延迟处理机制” ,即在 transform 阶段只建立依赖关系,在 codegen 阶段再填充内容。


六、拓展与思考

1️⃣ 为什么使用 WeakMap

WeakMap 用于存储临时数据,不会影响垃圾回收(避免内存泄漏)。
每个节点对应的 WIPEntry 在生成代码后可被回收。

2️⃣ 为什么分两阶段

Suspense 的 children 可能包含异步或嵌套结构,无法在一次 transform 中立即处理完,因此拆成两个阶段以确保:

  • Slot 函数能在后续拿到完整子节点;
  • SSR 生成顺序保持一致。

3️⃣ 编译器设计哲学

这种设计体现了 Vue 编译器的「延迟求值」思想——
在 transform 阶段尽量只收集结构信息,在 codegen 阶段集中生成逻辑表达式。


七、潜在问题与调试建议

问题类型可能原因解决思路
Suspense 不渲染 fallbackwipSlots 未正确填充检查 transform 阶段是否被提前清理
SSR 输出不正确context.helper 注册缺失确认 SSR_RENDER_SUSPENSE 已导入
内存溢出未清理 wipMap确认编译流程末尾自动 GC

八、总结

ssrTransformSuspensessrProcessSuspense 共同完成了 <Suspense> 的 SSR 编译:

  • 前者负责收集 slot 信息;
  • 后者负责生成最终的服务端渲染调用;
  • 通过两阶段机制实现异步安全的模板渲染逻辑。

这套机制充分展示了 Vue SSR 编译器中对「异步组件渲染」的精巧设计。


本文部分内容借助 AI 辅助生成,并由作者整理审核。


Vue SSR 源码解析:ssrTransformSuspense 与 ssrProcessSuspense》 是转载文章,点击查看原文


相关推荐


C++中的多态:动态多态与静态多态详解
oioihoii2025/11/6

多态是面向对象编程的三大特性之一,C++提供了两种主要的多态形式:动态多态和静态多态。本文将详细解释它们的区别,并通过代码示例进行说明。 什么是多态? 多态(Polymorphism)指同一个接口可以表现出不同的行为。在C++中,这允许我们使用统一的接口来处理不同类型的对象。 动态多态(运行时多态) 动态多态在程序运行时确定调用哪个函数,主要通过虚函数和继承机制实现。 实现机制 使用虚函数(virtual function) 通过继承关系 运行时通过虚函数表(vtable)决定调用哪个函数


图的寻路算法详解:基于深度优先搜索(DFS)的实现
Seal^_^2025/11/2

图的寻路算法详解:基于深度优先搜索DFS的实现 一、寻路算法概述DFS寻路示例 二、算法核心思想数据结构设计 三、算法实现详解1. 核心数据结构2. 构造函数初始化3. DFS实现4. 路径查询方法 四、完整代码实现五、算法测试与应用测试代码输出结果 六、算法分析与优化时间复杂度分析空间复杂度优化方向 七、DFS寻路与BFS寻路对比八、实际应用场景九、总结 🌺The Begin🌺点点关注,收藏不迷路🌺


高并发压力测试:Llama-2-7b 在昇腾 NPU 的六大场景表现
2501_938774292025/10/30

以下是关于 Llama-2-7b 在昇腾 NPU 上进行高并发压力测试的六大场景表现分析,结合网络公开信息和技术逻辑整理而成: 场景一:文本生成吞吐量测试 在批量文本生成任务中(如问答、摘要),昇腾 NPU 通过异构计算架构优化模型并行度。实测数据显示,当并发请求数从 100 提升至 1000 时,吞吐量增长约 3.8 倍,但单请求响应时间增加 15%-20%,显存占用峰值达 80%。 关键指标: 吞吐量:1200 tokens/s(batch_size=32)延迟:50ms/toke


Swift 官方发布 Android SDK | 肘子的 Swift 周报 #0108
东坡肘子2025/10/28

📮 想持续关注 Swift 技术前沿? 每周一期《肘子的 Swift 周报》,为你精选本周最值得关注的 Swift、SwiftUI 技术文章、开源项目和社区动态。 📬 在 weekly.fatbobman.com 免费订阅 💬 加入 Discord 与中文 Swift 开发者深入交流 📚 访问 fatbobman.com 查看数百篇深度原创教程  一起构建更好的 Swift 应用!🚀 Swift 官方发布 Android SDK 10 月 24 日,Swift Android 工


大模型时代的广告营销变革与实践
京东零售技术2025/10/25

大模型时代的广告营销变革与实践 互联网领域,广告营销是一种核心业态,也是先进技术和研究成果的商业化进程最快的一种渠道。伴随生成式大模型的浪潮汹涌袭来,京东广告结合自身业务特性和电商零售的新业态,推出了自主研发的广告营销商业化场景大模型,并据此带来了一场深刻的技术和业务变革。 在2025年9月25日,京东JDD(京东全球科技探索者)大会的Oxygen 智能零售论坛上,京东广告团队做了题为《大模型时代的广告营销变革与实践》的报告。 核心观点 1. 通用大模型想解决营销领域问题需向垂类模型转型。 “全


【Java】基于 Tabula 的 PDF 合并单元格内容提取
Kida的躺平小屋2025/10/22

坑还是要填的,但是填得是否平整就有待商榷了(狗头保命...)。 本人技术有限,只能帮各位实现的这个地步了。各路大神如果还有更好的实现也可以发出来跟小弟共勉一下哈。 首先需要说一下的是以下提供的代码仅作研究参考使用,各位在使用之前务必自检,因为并不是所有 pdf 的表格格式都适合。 本次实现的难点在于 PDF 是一种视觉格式,而不是语义格式。 它只记录了“在 (x, y) 坐标绘制文本 'ABC'”和“从 (x1, y1) 到 (x2, y2) 绘制一条线”。它根本不“知道”什么是“表格”、“


猿辅导Java面试真实经历与深度总结(二)
360_go_php2025/10/22

​ 在面试中,掌握Java的基础知识和深入的理解是非常重要的。今天,我们来解析几个常见的Java面试问题,包括线程状态、线程池、深拷贝与浅拷贝、线程安全、Lock与Synchronized的区别,以及逃逸分析等话题。 1. 线程状态 Java中,线程有七种状态,它们是由 Thread.State 枚举类定义的。线程的状态随着程序的执行而发生变化,下面是七种状态的描述:​编辑 NEW:线程被创建,但尚未启动。 RUNNABLE:线程可以运行,或者已经正在运行。线程调度器选择合适的线程让它执行


Docker 通信核心:docker.sock 完全指南
做运维的阿瑞2025/10/20

阅读时长: 15min | 难度: 中级 | 作者: 做运维的阿瑞 | 更新时间: 2025-10 文章目录 前言一、Docker 通信原理总览1.1 技术架构解析1.2 核心技术对比 二、核心用法与技巧2.1 容器内访问宿主机 Docker2.2 使用 Docker SDK2.3 直接与 API 交互 三、安全风险与最佳实践Q1: 有多危险?为什么说拿到 `docker.sock` 就等于 `root`?Q2: 如何安全地授权用户使用 Docker?Q3: 有没有比挂


自定义Spring Boot Starter项目并且在其他项目中通过pom引入使用
劝导小子2025/10/19

1、创建starter项目 我电脑只有JDK 8,但是创建项目的时候最低只能选择17,后续创建完后去修改即可 2、项目结构 删除主启动类Application:Starter不需要启动类删除配置文件application.properties:Starter不需要自己的配置文件删除test里面的测试启动类 在resources下创建META-INF文件夹 3、修改JDK 修改成JDK8,如果你有更高的版本请切换 4、配置pom.xml <?xml version="1


RabbitMQ消息传输中Protostuff序列化数据异常的深度解析与解决方案
Mr.45672025/10/18

目录 问题背景 环境配置 使用的依赖 测试对象 初始代码(有问题的版本) 问题分析 1. 初步排查 2. 关键发现 3. RabbitTemplate的默认行为分析 4. SimpleMessageConverter的处理机制 深入理解消息转换 消息转换器的层次结构: 而直接发送 Message: 解决方案 方案1:直接使用Message对象(推荐) 方案2:配置自定义MessageConverter 问题根因总结 经验教训 结论 最后最后附上序列化工具:

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0