阿里云主机折上折
  • 微信号
Current Site:Index > The work in the Transform stage

The work in the Transform stage

Author:Chuan Chen 阅读数:3311人阅读 分类: Vue.js

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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.