在 Vue 3 的服务端渲染(SSR)体系中,Transition 组件虽然在客户端负责动画过渡,但在服务端它并不执行动画,而是仅作为一种逻辑容器。本文将深入分析 Vue SSR 编译阶段如何处理 <transition> 节点,重点讲解 ssrTransformTransition 与 ssrProcessTransition 两个核心函数的作用和设计。
一、概念
1. 背景
当我们在模板中使用:
css
<transition appear>
<div>Hello</div>
</transition>
客户端渲染时,Vue 会为其绑定动画钩子。但 SSR 中没有动画执行环境,因此需要一种方式"识别并简化" <transition> 的渲染逻辑,以便最终输出稳定的 HTML 字符串。
2. 目标
SSR 的编译阶段要做到两点:
- 保留
<transition>的语义结构; - 过滤掉与动画无关的运行时逻辑,只保留必要的内容。
二、原理解析
Vue 的 SSR 编译管线中,针对组件会调用一系列 transform 与 process 阶段函数。
ssrTransform*系列函数:负责"分析"组件节点的属性与结构;ssrProcess*系列函数:负责"生成"最终的 SSR 输出。
对于 <transition>,Vue 分别定义了:
ssrTransformTransition:在 AST 转换阶段执行,用于标记appear属性;ssrProcessTransition:在生成 SSR 代码阶段执行,用于输出最终 HTML。
三、源码拆解与逐行注释
源码整体
typescript
import {
type ComponentNode,
NodeTypes,
type TransformContext,
findProp,
} from '@vue/compiler-dom'
import {
type SSRTransformContext,
processChildren,
} from '../ssrCodegenTransform'
const wipMap = new WeakMap<ComponentNode, Boolean>()
export function ssrTransformTransition(
node: ComponentNode,
context: TransformContext,
) {
return (): void => {
const appear = findProp(node, 'appear', false, true)
wipMap.set(node, !!appear)
}
}
export function ssrProcessTransition(
node: ComponentNode,
context: SSRTransformContext,
): void {
// #5351: filter out comment children inside transition
node.children = node.children.filter(c => c.type !== NodeTypes.COMMENT)
const appear = wipMap.get(node)
if (appear) {
context.pushStringPart(`<template>`)
processChildren(node, context, false, true)
context.pushStringPart(`</template>`)
} else {
processChildren(node, context, false, true)
}
}
(1)wipMap:工作中间态存储
javascript
const wipMap = new WeakMap<ComponentNode, Boolean>()
解释:
这是一个"弱映射表",用来在 transform 与 process 阶段之间共享节点状态。
在编译管线中,同一个节点会多次被访问,
WeakMap能安全地存储它的中间状态,不影响垃圾回收。
(2)ssrTransformTransition:标记阶段
javascript
export function ssrTransformTransition(
node: ComponentNode,
context: TransformContext,
) {
return (): void => {
const appear = findProp(node, 'appear', false, true)
wipMap.set(node, !!appear)
}
}
逐行解释:
findProp(node, 'appear', false, true):在当前组件节点中查找appear属性;- 若存在,则表示该
<transition>有初次动画逻辑;wipMap.set(node, !!appear):将布尔结果记录到wipMap中,供后续处理阶段使用。
核心作用:这一步不生成任何输出,而是为节点打上"是否含 appear" 的标记。
(3)ssrProcessTransition:生成阶段
javascript
export function ssrProcessTransition(
node: ComponentNode,
context: SSRTransformContext,
): void {
node.children = node.children.filter(c => c.type !== NodeTypes.COMMENT)
const appear = wipMap.get(node)
if (appear) {
context.pushStringPart(`<template>`)
processChildren(node, context, false, true)
context.pushStringPart(`</template>`)
} else {
processChildren(node, context, false, true)
}
}
逐行解释:
过滤注释节点:
ininode.children = node.children.filter(c => c.type !== NodeTypes.COMMENT)避免无意义的注释干扰 SSR 输出。
读取 transform 阶段标记:
csharpconst appear = wipMap.get(node)获取
<transition>是否含有appear属性。按条件输出:
- 如果存在
appear:包裹一层<template>标签(表示这是一个初始渲染的容器);- 否则,直接输出子节点。
arduinoif (appear) { context.pushStringPart(`<template>`) processChildren(node, context, false, true) context.pushStringPart(`</template>`) } else { processChildren(node, context, false, true) }
总结逻辑:
SSR 版本的
<transition>实际不会生成<transition>标签,而是根据是否"初始出现动画"来选择是否包裹<template>。
四、对比:客户端 vs 服务端
| 环境 | Transition 行为 | 输出结构 |
|---|---|---|
| 客户端 (DOM) | 生成真实 <transition> 节点;控制过渡动画 |
<transition><div>...</div></transition> |
| 服务端 (SSR) | 仅输出子内容,不涉及动画逻辑 | <div>...</div> 或 <template>...</template> |
SSR 的 Transition 更像是"语义占位符",保证编译一致性,而非真实 DOM 控制器。
五、实践案例
示例模板
css
<transition appear>
<p>Hello SSR</p>
</transition>
SSR 编译输出(简化后)
xml
<template><p>Hello SSR</p></template>
若去掉 appear 属性:
css
<transition>
<p>Hello SSR</p>
</transition>
输出则变为:
css
<p>Hello SSR</p>
可见,
<template>的出现与appear属性一一对应。
六、拓展:WeakMap 的作用与替代方案
使用 WeakMap 而非普通 Map 的原因:
- 避免内存泄漏(节点销毁后自动释放);
- 符合"编译阶段数据暂存"的设计原则;
- 多阶段共享同一引用对象。
若改用普通 Map,节点对象可能被持久引用,导致无法被垃圾回收。
七、潜在问题与思考
- SSR 与 Hydration 差异
SSR 输出<template>可能与客户端虚拟 DOM 结构不同,需确保 hydration 阶段 DOM 匹配。 - TransitionGroup 的特殊性
目前逻辑仅覆盖<transition>,而<transition-group>会有更复杂的子节点管理逻辑。 - 进一步优化
若未来引入v-show、v-if动画 SSR 兼容,可考虑在 transform 阶段做更细粒度 AST 分析。
八、总结
本文从编译管线视角,完整分析了 Vue SSR 如何处理 <transition> 节点:
ssrTransformTransition负责标记;ssrProcessTransition负责输出;- WeakMap 作为中间态桥梁;
- 最终目标是输出干净、语义一致的 HTML。
这种分阶段设计兼顾了 编译阶段清晰性 与 运行时性能,体现了 Vue SSR 的工程化思路。
本文部分内容借助 AI 辅助生成,并由作者整理审核。