The work in the Transform stage
Work in the Transform Phase
The compilation process of Vue3 is divided into three main phases: Parse, Transform, and Codegen. The Transform phase occurs after parsing and before code generation, responsible for processing and optimizing the AST obtained from parsing. This phase is the most complex part of Vue's template compilation, encompassing many critical functionalities.
AST Node Transformation
The Transform phase first processes the raw AST generated during the parsing phase. Vue internally uses the transform
function to handle the AST. This function recursively traverses the AST tree, applying corresponding transformation functions to each node:
function transform(root: RootNode, options: TransformOptions) {
const context = createTransformContext(root, options)
traverseNode(root, context)
if (options.hoistStatic) {
hoistStatic(root, context)
}
if (!options.ssr) {
createRootCodegen(root, context)
}
}
Each node transformation function receives the current node and the transformation context as parameters. For example, the transformation for an element node:
const transformElement = (node, context) => {
// Process props
const props = node.props
const vnodeTag = `"${node.tag}"`
const vnodeProps = buildProps(node, context)
// Process child nodes
const children = node.children
let vnodeChildren
if (children.length) {
vnodeChildren = children[0]
}
// Create code generation node
node.codegenNode = createVNodeCall(
context,
vnodeTag,
vnodeProps,
vnodeChildren
)
}
Static Hoisting
A significant optimization in Vue3 is static hoisting, where static content is identified during compilation and hoisted outside the render function. This avoids recreating these static nodes during each re-render:
function hoistStatic(root: RootNode, context: TransformContext) {
walk(root, context,
(node, context) => {
if (node.type === NodeTypes.ELEMENT && isStaticNode(node)) {
node.static = true
context.hoists.push(node)
return true
}
}
)
}
For example, for the template <div><span>static</span><span>{{ dynamic }}</span></div>
, the first <span>
will be hoisted:
const _hoisted_1 = /*#__PURE__*/_createVNode("span", null, "static")
function render(_ctx) {
return _createVNode("div", null, [
_hoisted_1,
_createVNode("span", null, _toDisplayString(_ctx.dynamic), 1 /* TEXT */)
])
}
Patch Flags
The Transform phase also adds patch flags to dynamic nodes, helping the runtime update the DOM more efficiently. These flags indicate which parts of the node need to be compared and updated:
function markDynamicProps(node: ElementNode) {
const flagNames = []
if (hasDynamicKeys(node.props)) {
flagNames.push('PROPS')
}
if (node.children.some(isDynamic)) {
flagNames.push('CHILDREN')
}
if (flagNames.length) {
node.patchFlag = getPatchFlag(flagNames)
}
}
For example, an element with only a dynamic class will be marked as 8
(PROPS
flag), indicating that only props need to be compared:
_createVNode("div", { class: _normalizeClass(_ctx.className) }, null, 8 /* PROPS */, ["class"])
Caching Event Handlers
To avoid unnecessary re-renders, Vue3 caches event handlers. The Transform phase identifies event handlers and adds caching flags:
function transformExpression(node: Node, context: TransformContext) {
if (node.type === NodeTypes.ATTRIBUTE && node.name === 'on') {
const exp = node.value
if (exp.content.startsWith('_cache') || exp.isHandlerKey) {
return
}
node.value = {
type: NodeTypes.SIMPLE_EXPRESSION,
content: `_cache[${context.cacheIndex++}] || (_cache[${context.cacheIndex++}] = ${exp.content})`,
isStatic: false,
isConstant: false
}
}
}
The transformed code will look like this:
_createVNode("button", { onClick: _cache[0] || (_cache[0] = $event => _ctx.handleClick($event)) }, "click me")
Block Processing
Vue3 introduces the concept of blocks, dividing the template into dynamic and static regions. The Transform phase identifies these blocks and marks them accordingly:
function createStructuralDirectiveTransform(
name: string | RegExp,
fn: StructuralDirectiveTransform
): NodeTransform {
return (node, context) => {
if (node.type === NodeTypes.ELEMENT) {
const { props } = node
for (let i = 0; i < props.length; i++) {
const prop = props[i]
if (prop.type === NodeTypes.DIRECTIVE && isMatch(name, prop.name)) {
fn(node, prop, context)
}
}
}
}
}
For example, the v-if
directive is transformed into a block:
// Template: <div v-if="show">content</div>
function render(_ctx) {
return (_ctx.show)
? (_openBlock(), _createBlock("div", { key: 0 }, "content"))
: _createCommentVNode("v-if", true)
}
Custom Directive Transformation
Custom directives are also processed during the Transform phase, converting them into corresponding runtime directive objects:
function transformDirectives(node: ElementNode, context: TransformContext) {
const directives = []
for (const prop of node.props) {
if (prop.type === NodeTypes.DIRECTIVE) {
directives.push({
name: prop.name,
rawName: prop.rawName,
value: prop.exp,
arg: prop.arg,
modifiers: prop.modifiers
})
}
}
if (directives.length) {
node.directives = directives
}
}
Transformed directives are merged into the VNode's props:
_createVNode("div", {
__directives: {
myDirective: {
mounted(el, binding) { /* ... */ }
}
}
})
Slot Transformation
Slot content is specially processed during the Transform phase, converting it into slot functions:
function transformSlotOutlet(node, context) {
if (node.type === NodeTypes.ELEMENT && node.tag === 'slot') {
const slotName = node.props.find(p => p.name === 'name')?.value || '"default"'
const slotProps = node.props.filter(p => p.name !== 'name')
node.codegenNode = createSlotOutlet(
slotName,
slotProps.length ? createObjectExpression(slotProps) : undefined
)
}
}
For example, a named slot is transformed into:
_renderSlot(_ctx.$slots, "header", { item: _ctx.item })
Static Type Inference
The Transform phase also performs static analysis to infer type information for expressions, which is used during the code generation phase:
function inferExpressionType(
node: SimpleExpressionNode,
context: TransformContext
) {
if (node.isStatic) {
node.type = 'Literal'
} else if (context.identifiers.has(node.content)) {
node.type = 'Identifier'
} else if (node.content.startsWith('_ctx.')) {
node.type = 'MemberExpression'
}
}
This type information helps generate more optimized runtime code. For example, knowing that an expression is a simple property access allows generating direct property access code instead of full-featured parsing code.
Source Code Structure Analysis
The main logic of the Transform phase is located in the src/transform.ts
file of the @vue/compiler-core
package. The core transformer consists of multiple sub-transformers, each responsible for specific transformation tasks:
transform
├── transformElement // Handles element nodes
├── transformText // Handles text nodes
├── transformExpression // Handles expressions
├── transformIf // Handles v-if
├── transformFor // Handles v-for
├── transformOn // Handles events
├── transformBind // Handles v-bind
├── transformModel // Handles v-model
└── ... // Other transformations
This modular design allows each transformation function to be developed and tested independently while also facilitating on-demand combination.
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:AST节点的数据结构
下一篇:代码生成(Codegen)的过程