一、概念概述
在 Vue 3 的 SSR(Server-Side Rendering)编译阶段,ssrTransformElement 是负责将模板中的 DOM 元素(<div>, <input>, <textarea> 等)转化为可在服务器端渲染的 字符串构建代码 的核心逻辑之一。
它的主要职责是:
- 分析模板 AST 节点(Abstract Syntax Tree)。
- 处理静态与动态属性(
v-bind、v-model、v-text、v-html等)。 - 构建最终可执行的 SSR 渲染代码(即字符串拼接逻辑)。
- 为
ssrProcessElement阶段提供结构化的输出数据(node.ssrCodegenNode)。
而 ssrProcessElement 则是将 ssrTransformElement 阶段准备好的指令结构 具体输出为最终 HTML 字符串 的阶段。
二、原理剖析
1. SSR 编译器的双阶段机制
Vue 的 SSR 编译分为两个主要阶段:
| 阶段 | 作用 |
|---|---|
Transform 阶段(如 ssrTransformElement) |
分析 AST,提取动态属性与指令,生成中间结构 |
Codegen 阶段(如 ssrProcessElement) |
根据 Transform 的结果拼接最终 HTML 字符串 |
这两个阶段分别运行在 编译期(构建时) 和 运行期(请求时) ,确保性能与灵活性并存。
2. ssrTransformElement 核心结构解析
关键流程
javascript
export const ssrTransformElement: NodeTransform = (node, context) => {
if (node.type !== NodeTypes.ELEMENT || node.tagType !== ElementTypes.ELEMENT) {
return
}
return function ssrPostTransformElement() {
// Step 1: 初始化模板字面量
const openTag: TemplateLiteral['elements'] = [`<${node.tag}`]
// Step 2: 检查动态绑定与自定义指令
const hasDynamicVBind = hasDynamicKeyVBind(node)
const hasCustomDir = node.props.some(p => p.type === NodeTypes.DIRECTIVE && !isBuiltInDirective(p.name))
// Step 3: 若存在动态绑定或自定义指令,则生成动态属性表达式
if (hasDynamicVBind || hasCustomDir) {
const { props, directives } = buildProps(node, context, node.props, false, false, true)
const mergedProps = buildSSRProps(props, directives, context)
const propsExp = createCallExpression(context.helper(SSR_RENDER_ATTRS), [mergedProps])
openTag.push(propsExp)
}
// Step 4: 生成最终 SSR 模板字符串节点
node.ssrCodegenNode = createTemplateLiteral(openTag)
}
}
代码逐段注释
| 片段 | 功能说明 |
|---|---|
openTag |
构建 HTML 起始标签的模板片段,如 <div 或 <input。 |
hasDynamicVBind |
判断是否存在动态属性(v-bind:[key])。 |
buildProps |
将所有属性与指令汇总为统一的 props 表达式。 |
buildSSRProps |
合并 v-bind 与指令生成的属性集合。 |
createCallExpression(SSR_RENDER_ATTRS, ...) |
生成运行时调用,实际执行时会调用 ssrRenderAttrs() 来输出字符串属性。 |
createTemplateLiteral |
将所有部分拼接成最终可序列化的模板字符串结构。 |
3. buildSSRProps 的合并逻辑
less
export function buildSSRProps(
props: PropsExpression | undefined,
directives: DirectiveNode[],
context: TransformContext,
): JSChildNode {
let mergePropsArgs: JSChildNode[] = []
if (props) {
mergePropsArgs.push(props)
}
for (const dir of directives) {
mergePropsArgs.push(
createCallExpression(context.helper(SSR_GET_DIRECTIVE_PROPS), [
`_ctx`,
...buildDirectiveArgs(dir, context).elements,
])
)
}
return mergePropsArgs.length > 1
? createCallExpression(context.helper(MERGE_PROPS), mergePropsArgs)
: mergePropsArgs[0]
}
解释:
MERGE_PROPS实际对应运行时的Object.assign()或合并函数;- 若存在多个指令或动态绑定,则调用
mergeProps; - 若只有一个,则直接返回单一表达式;
- 最终结果会交给
SSR_RENDER_ATTRS负责输出 HTML 属性字符串。
4. ssrProcessElement 输出阶段
scss
export function ssrProcessElement(
node: PlainElementNode,
context: SSRTransformContext,
): void {
const elementsToAdd = node.ssrCodegenNode!.elements
for (let j = 0; j < elementsToAdd.length; j++) {
context.pushStringPart(elementsToAdd[j])
}
context.pushStringPart(`>`) // 关闭起始标签
const rawChildren = rawChildrenMap.get(node)
if (rawChildren) {
context.pushStringPart(rawChildren)
} else if (node.children.length) {
processChildren(node, context)
}
if (!isVoidTag(node.tag)) {
context.pushStringPart(`</${node.tag}>`)
}
}
注释说明:
| 步骤 | 动作 | 说明 |
|---|---|---|
| ① | 输出 node.ssrCodegenNode 中保存的所有片段 |
包括 <div, 属性字符串等 |
| ② | 输出 > |
标记标签起始结束 |
| ③ | 处理 rawChildren 或递归处理子节点 |
支持 v-html、v-text 等特殊情况 |
| ④ | 若不是空标签(如 <br>、<img>),输出闭合标签 |
输出 </div> |
三、对比分析:与普通编译器的差异
| 对比项 | 客户端编译器 (@vue/compiler-dom) |
SSR 编译器 (@vue/compiler-ssr) |
|---|---|---|
| 输出目标 | 渲染函数 (VNode) | HTML 字符串 |
| 动态绑定 | 转为响应式表达式 | 转为字符串拼接调用 |
| v-model | 运行时处理 | 编译期静态插入 |
| 输出性能 | 依赖运行时 diff | 一次性生成完整 HTML |
四、实践:手动调试 SSR Element 转换
假设模板如下:
ini
<textarea v-bind="obj" v-model="content"></textarea>
经过 ssrTransformElement 处理后,将生成如下伪代码:
less
_ssrRenderAttrs(_mergeProps(obj, _ssrGetDynamicModelProps(obj, content)))
解释:
obj提供动态绑定属性;v-model在 SSR 阶段转为_ssrGetDynamicModelProps();MERGE_PROPS合并两者;- 运行时会自动渲染出:
css
<textarea value="content-value"></textarea>
五、拓展与高级机制
-
rawChildrenMap 的作用
用于记录需要在 SSR 输出阶段特殊替换的节点内容,如:
v-htmlv-text<textarea>的动态value
-
动态属性渲染函数
SSR_RENDER_ATTR:渲染静态安全属性。SSR_RENDER_DYNAMIC_ATTR:处理动态 key 属性。SSR_RENDER_STYLE/SSR_RENDER_CLASS:合并 style 与 class。
-
安全与兼容性
- 通过
isSSRSafeAttrName过滤潜在 XSS 风险。 - 对自定义元素(
<my-element>)保留原始属性名。
- 通过
六、潜在问题与注意事项
| 问题类型 | 描述 | 解决思路 |
|---|---|---|
| 动态属性覆盖 | 多个 v-bind 可能覆盖静态属性 |
通过 MERGE_PROPS 顺序合并 |
| Textarea 内容冲突 | 同时存在 v-bind 与静态文本 |
通过临时变量 _temp 判断是否含有 .value |
| 指令优先级混乱 | 如 v-text 与 v-html 并存 |
通过 rawChildrenMap 后置处理 |
| 性能损耗 | 多层 mergeProps 嵌套可能降低性能 |
编译阶段尽量合并静态属性 |
结语
ssrTransformElement 与 ssrProcessElement 共同构成了 Vue SSR 编译管线的「结构生成 → 渲染输出」闭环。它们的设计体现了 Vue 在模板编译层面对 动态性、性能与安全性 的三重平衡。
通过这一机制,Vue 能够在服务端生成与客户端几乎一致的 DOM 树,从而实现无缝 hydration。
本文部分内容借助 AI 辅助生成,并由作者整理审核。