Vue3追本溯源(五)ast转化之transform

·  阅读 1019
Vue3追本溯源(五)ast转化之transform

接上篇template模版编译,上篇主要解析了template模版编译的入口以及解析模版字符串的parseChildren方法的内部实现。在生成ast对象之后,继续调用transform方法转化ast对象,主要是codegenNode属性的赋值,后续生成render方法主要依赖codegenNode属性。

transform方法调用位置

上篇解析完ast对象之后,回归到baseCompile方法中,继续执行transform方法

// baseCompile函数后续操作
const ast = isString(template) ? baseParse(template, options) : template
// ... nodeTransforms directiveTransforms 数组的组成
transform(
    ast,
    extend({}, options, {
      prefixIdentifiers,
      nodeTransforms: [
        ...nodeTransforms,
        ...(options.nodeTransforms || []) // user transforms
      ],
      directiveTransforms: extend(
        {},
        directiveTransforms,
        options.directiveTransforms || {} // user transforms
      )
    })
)
复制代码

调用transform方法时传递两个参数,第一个参数ast对象就是baseParse进行模版解析的初步结果对象,第二个参数是options对象(里面包含解析指令以及方法的函数,后续解析到具体调用时再详细分析)。接下来看下transform方法内部实现。

function transform(root, options) {
    const context = createTransformContext(root, options);
    traverseNode(root, context);
    // ...
}
复制代码

traverseNode循环解析ast对象

transform方法内部首先是调用createTransformContext方法生成一个context对象。然后调用traverseNode方法(这个方法内部就是使用options参数中的方法进一步解析指令、方法)。

export function traverseNode(
  node: RootNode | TemplateChildNode,
  context: TransformContext
) {
    context.currentNode = node
    // apply transform plugins
    const { nodeTransforms } = context
    const exitFns = []
    for (let i = 0; i < nodeTransforms.length; i++) {
        const onExit = nodeTransforms[i](node, context)
        if (onExit) {
          if (isArray(onExit)) {
            exitFns.push(...onExit)
          } else {
            exitFns.push(onExit)
          }
        }
        if (!context.currentNode) {
          // node was removed
          return
        } else {
          // node may have been replaced
          node = context.currentNo
        }
    }
    // ...
}
复制代码

nodeTransforms处理指令的函数数组

首先是for循环nodeTransforms数组,依次调用数组中的每个方法,参数是node对象(初始化是type0的ast对象) 和 context对象(调用createTransformContext方法生成的)。接下来看下nodeTransforms数组中的方法(大致看下每个函数是解析哪个指令或者方法的,然后以本例为模版详细解析调用的函数,后续再解析其它命中的函数)。

// 回归到baseCompile方法中 - nodeTransforms数组的由来
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(prefixIdentifiers)

// getBaseTransformPreset方法内部实现
export function getBaseTransformPreset(
  prefixIdentifiers?: boolean
): TransformPreset {
    return [
    [
      transformOnce,
      transformIf,
      transformFor,
      ...(!__BROWSER__ && prefixIdentifiers
        ? [
            // order is important
            trackVForSlotScopes,
            transformExpression
          ]
        : __BROWSER__ && __DEV__
          ? [transformExpression]
          : []),
      transformSlotOutlet,
      transformElement,
      trackSlotScopes,
      transformText
    ],
    // ... directiveTransforms对象
  ]
}
复制代码

transformOnce 解析v-once指令
transformIf 解析v-if指令
transformFor 解析v-for指令
trackVForSlotScopestransformSlotOutlettrackSlotScopes方法 解析slot插槽
transformExpression 处理表达式
transformElement 处理type为1的元素对象
transformText 处理type为0的root根对象、type为1的元素对象、type为11的v-for

解析root根节点对象(type=0)

初始化asttype0的根节点对象,循环调用nodeTransforms数组中的方法,最终会命中transformText方法,返回一个自定的函数() => {}赋值给onExit对象,pushexitFns数组中。接下来看下traverseNode方法for循环之后的内部实现

// traverseNode方法后续 switch-case解析ast的子节点对象
switch (node.type) {
    case NodeTypes.COMMENT: // type = 3
      if (!context.ssr) {
        context.helper(CREATE_COMMENT)
      }
      break
    case NodeTypes.INTERPOLATION: // type = 5
      if (!context.ssr) {
        context.helper(TO_DISPLAY_STRING)
      }
      break
    case NodeTypes.IF: // type = 9 v-if
      for (let i = 0; i < node.branches.length; i++) {
        traverseNode(node.branches[i], context)
      }
      break
    case NodeTypes.IF_BRANCH: // type = 0 | 1
    case NodeTypes.FOR:
    case NodeTypes.ELEMENT:
    case NodeTypes.ROOT:
      traverseChildren(node, context)
      break
}
复制代码

解析动态数据节点(type=5)

当解析完type=0root对象时,根据switch-case的选项调用traverseChildren方法解析children数组中的子节点对象。

export function traverseChildren(
  parent: ParentNode,
  context: TransformContext
) {
  let i = 0
  const nodeRemoved = () => {
    i--
  }
  for (; i < parent.children.length; i++) {
    const child = parent.children[i]
    if (isString(child)) continue
    context.parent = parent
    context.childIndex = i
    context.onNodeRemoved = nodeRemoved
    traverseNode(child, context)
  }
}
复制代码

可以看到traverseChildren函数中大致的实现是循环parentchildren数组中的元素,再次调用traverseNode方法进行解析,如果child是字符串则跳过。

命中transformExpression函数

以本例为模版,根节点的第一个子对象是type5的动态数据message的结果对象,所以在nodeTransforms数组中会调用transformExpression方法

// transformExpression 方法
export const transformExpression: NodeTransform = (node, context) => {
    if (node.type === NodeTypes.INTERPOLATION/* 5 */) {
        node.content = processExpression(
          node.content as SimpleExpressionNode,
          context
        )
    } else if () {} //...
}

// processExpression 方法
export function processExpression(
  node: SimpleExpressionNode,
  context: TransformContext,
  asParams = false,
  asRawStatements = false
): ExpressionNode {
    if (__BROWSER__) {
        if (__DEV__) {
          // simple in-browser validation (same logic in 2.x)
          validateBrowserExpression(node, context, asParams, asRawStatements)
        }
        return node
    }
    // ...
}
复制代码

这个方法调用processExpression方法处理表达式,内部实现大致是校验content属性(就是message)是否为空、校验下new Function()在当前运行环境下执行会不会报错等等,最后返回传入的node.content。在nodeTransforms数组循环完之后进入switch-case

// traverseNode方法中type为5时执行的switch-case分支
context.helper(TO_DISPLAY_STRING)

// TO_DISPLAY_STRING 是一个 Symbol对象
export const TO_DISPLAY_STRING = Symbol(__DEV__ ? `toDisplayString` : ``)


// methods
helper(name) {
    context.helpers.add(name)
    return name
}

// helpers: new Set() helpers是一个set对象
复制代码

contexthelpers属性(new Set()对象)中添加key(Symbol('toDisplayString'))、 value(1)值。switch-case结束之后会调用while循环,依次执行exitFns数组中的函数(就是nodeTransforms循环执行的结果存放在这个数组中)

// traverseNode 后续循环执行exitFns数组中的方法
context.currentNode = node
let i = exitFns.length
while (i--) {
    exitFns[i]()
}
复制代码

type5的对象循环时并没有返回数组,所以没有方法可以执行,本例后续会解析type2的文本对象,因为type2是空节点没有命中任何方法,这里直接跳过了。

解析元素节点对象(type=1)

最后执行type为1的元素节点对象

命中transformExpression函数

nodeTransforms数组循环中首先会命中transformExpressionnode.type === NodeTypes.ELEMENT的分支判断

// transformExpression方法node.type=1的分支后续
else if (node.type === NodeTypes.ELEMENT) {
    for (let i = 0; i < node.props.length; i++) {
      const dir = node.props[i]
      // do not process for v-on & v-for since they are special handled
      if (dir.type === NodeTypes.DIRECTIVE/* 7 */ && dir.name !== 'for') {
          const exp = dir.exp
          const arg = dir.arg
          if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION &&
              !(dir.name === 'on' && arg)
          ) {/* ... */}
          if (arg && arg.type === NodeTypes.SIMPLE_EXPRESSION && !arg.isStatic) {/* ... */}
      }
    }
}
复制代码

node.type===1时会循环props数组,判断props对象的type属性是否为7并且不是v-for指令,再判断exp(属性值)的type是否为4并且属性名不是on(本例中属性为on事件监听),再判断arg(属性名)的type是否为4并且不是静态的(本例中isStatic值为true),所以条件判断都不成立(若条件成立,后续还是执行processExpression方法进行一些校验)。

命中transformElement函数

nodeTransforms数组循环再次命中transformElement方法

export const transformElement: NodeTransform = (node, context) => {
  if (
    !(
      node.type === NodeTypes.ELEMENT/* 1 */ &&
      (node.tagType === ElementTypes.ELEMENT/* 0 */ ||
        node.tagType === ElementTypes.COMPONENT)
    )
  ) {
    return
  }
  return function postTransformElement() {}
}
复制代码

满足node.type===1并且node.tagType === 0,所以返回postTransformElement方法。

命中transformText函数

因为node.type === 1,执行transformText方法,所以返回() => {}自定义函数

export const transformText: NodeTransform = (node, context) => {
  if (
    node.type === NodeTypes.ROOT ||
    node.type === NodeTypes.ELEMENT ||
    node.type === NodeTypes.FOR ||
    node.type === NodeTypes.IF_BRANCH
  ) {
      return () => { /* ... */ }
  }
}
复制代码

循环结束之后执行switch-case命中case NodeTypes.ELEMENT分支,执行traverseChildren方法解析子节点(继续调用traverseNode方法解析子节点),因为本例中button元素的内部子节点是一个type2的文本节点,所以没有命中nodeTransforms中的方法(exitFns数组为空),switch-case也没有命中,因此while循环也没有执行任何方法。type1children数组解析完了,再次回到type=1的元素对象解析过程中,开始while循环执行exitFns数组中的方法。

traverseNode中执行回调函数(type=1)

执行postTransformElement方法

第一个是postTransformElement方法

function postTransformElement() {
    const { tag, props } = node
    const isComponent = node.tagType === ElementTypes.COMPONENT
    const vnodeTag = isComponent
      ? resolveComponentType(node as ComponentNode, context)
      : `"${tag}"`
    const isDynamicComponent = 
    isObject(vnodeTag) && vnodeTag.callee === RESOLVE_DYNAMIC_COMPONENT
    
    let vnodeProps: VNodeCall['props']
    let vnodeChildren: VNodeCall['children']
    let vnodePatchFlag: VNodeCall['patchFlag']
    let patchFlag: number = 0
    let vnodeDynamicProps: VNodeCall['dynamicProps']
    let dynamicPropNames: string[] | undefined
    let vnodeDirectives: VNodeCall['directives']
    // ... shouldUseBlock = false
    
    // props
    if (props.length > 0) {
    const propsBuildResult = buildProps(node, context)
    // 将返回的属性解析对象进行赋值
    vnodeProps = propsBuildResult.props // 属性对象
    patchFlag = propsBuildResult.patchFlag // 8
    dynamicPropNames = propsBuildResult.dynamicPropNames // [name]
    const directives = propsBuildResult.directives
    vnodeDirectives =
        directives && directives.length
          ? (createArrayExpression(
              directives.map(dir => buildDirectiveArgs(dir, context))
            ) as DirectiveArguments)
          : undefined  // undefined
        }
}
复制代码

该方法中判断是否是组件node.tagType === 1(本例tagType0);vnodeTag赋值,本例中为"button";是否是动态组件isDynamicComponent(本例因为vnodeTag是字符串,所以是false)

buildProps解析props属性

然后调用buildProps方法解析props属性

export function buildProps(
  node: ElementNode,
  context: TransformContext,
  props: ElementNode['props'] = node.props,
  ssr = false
): {
  props: PropsExpression | undefined
  directives: DirectiveNode[]
  patchFlag: number
  dynamicPropNames: string[]
} {
    const { tag, loc: elementLoc } = node
    const isComponent = node.tagType === ElementTypes.COMPONENT
    let properties: ObjectExpression['properties'] = []
    const mergeArgs: PropsExpression[] = []
    const runtimeDirectives: DirectiveNode[] = []
    
    let patchFlag = 0
    let hasRef = false
    let hasClassBinding = false
    let hasStyleBinding = false
    let hasHydrationEventBinding = false
    let hasDynamicKeys = false
    let hasVnodeHook = false
    const dynamicPropNames: string[] = []
    
    const analyzePatchFlag = ({ key, value }: Property) => {}
    for (let i = 0; i < props.length; i++) {
        const prop = props[i]
        if (prop.type === NodeTypes.ATTRIBUTE /* 6 */) {}
        else {
            // directives
            const { name, arg, exp, loc } = prop
            const isVBind = name === 'bind' // false
            const isVOn = name === 'on' // true
            // ...
            const directiveTransform = context.directiveTransforms[name]
            if (directiveTransform) {
                // has built-in directive transform.
                const { props, needRuntime } = directiveTransform(prop, node, context)
                // ...
            } else {
             // ...
            }
        }
    }
}
复制代码

buildProps方法中会循环props中的每个属性,调用context.directiveTransforms[name]对应的方法

命中transformOn函数

本例的属性名为on,所以调用transformOn方法(在getBaseTransformPreset方法返回数组的第二个元素中定义)

// createSimpleExpression方法
export function createSimpleExpression(
  content: SimpleExpressionNode['content'],
  isStatic: SimpleExpressionNode['isStatic'] = false,
  loc: SourceLocation = locStub,
  constType: ConstantTypes = ConstantTypes.NOT_CONSTANT
): SimpleExpressionNode {
  return {
    type: NodeTypes.SIMPLE_EXPRESSION,
    loc,
    content,
    isStatic,
    constType: isStatic ? ConstantTypes.CAN_STRINGIFY : constType
  }
}

// transformOn方法
export const transformOn: DirectiveTransform = (
  dir,
  node,
  context,
  augmentor
) => {
   const { loc, modifiers, arg } = dir as VOnDirectiveNode
   if (!dir.exp && !modifiers.length) {}
   let eventName: ExpressionNode
   if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
      if (arg.isStatic) {
          const rawName = arg.content
          eventName = createSimpleExpression(
            toHandlerKey(camelize(rawName)),
            true,
            arg.loc
          )
      }
      else {}
   } else {}
   // ...
   let shouldCache: boolean = context.cacheHandlers && !exp
   if (exp) {
       // ...
   }
   let ret: DirectiveTransformResult = {
    props: [
      createObjectProperty(
        eventName,
        exp || createSimpleExpression(`() => {}`, false, loc)
      )
    ]
   }
   // ...
   return ret
}
复制代码

transformOn方法的内部实现,首先属性名(arg对象)的type4(简单表达式)并且isStatictrue,则调用createSimpleExpression方法创建简单表达式对象{ type: 4, content: onClick, constType: 3, ... }。这里对argname(事件名称)做了处理(首先是中划线转驼峰,再是因为是on事件监听,所以在name开头加上on并且name的首字母大写,本例中nameclick -> onClick)。最后调用createObjectProperty方法。

export function createObjectProperty(
  key: Property['key'] | string,
  value: Property['value']
): Property {
  return {
    type: NodeTypes.JS_PROPERTY, // type=16
    loc: locStub,
    key: isString(key) ? createSimpleExpression(key, true) : key,
    value
  }
}
复制代码

createObjectProperty方法最终返回一个type16的对象,其中keytype=4arg对象,valuetype=4exp对象,所以transformOn方法返回{ props: [{type: 16, key, value, ...}] }。回归到buildProps方法中,调用完directiveTransform(就是transformOn函数)方法的后续实现

// buildProps函数中调用完directiveTransform对应的方法之后的内部实现
const { props, needRuntime/* undefined */ } = directiveTransform(prop, node, context)
!ssr && props.forEach(analyzePatchFlag)
properties.push(...props)
if (needRuntime) {}
复制代码

props数组中的每个元素依次调用analyzePatchFlag方法,然后将props的每个元素pushproperties数组中。看下analyzePatchFlag函数的内部实现

// buildProps方法内部定义的analyzePatchFlag函数
const analyzePatchFlag = ({ key, value }: Property) => {
    // isStaticExp判断key的type === 4 并且 key的isStatic === true
    if (isStaticExp(key)) {
        const name = key.content
        const isEventHandler = isOn(name) // 是否是on开头的事件监听
        // ...
        if (name === 'ref') {
            hasRef = true
        } else if (name === 'class') {
            hasClassBinding = true
        } else if (name === 'style') {
            hasStyleBinding = true
        } else if (name !== 'key' && !dynamicPropNames.includes(name)) {
            dynamicPropNames.push(name)
        }
        // ...
    }
    else {}
}
复制代码

以本例分析,analyzePatchFlag方法的作用是将key.content放到dynamicPropNames数组中(收集属性名称)。后续回归到buildProps方法中

// buildProps方法后续
if (mergeArgs.length) {/* ... */}
else if (properties.length) {
    propsExpression = createObjectExpression(
      dedupeProperties(properties), // 属性对象组成的数组[prop]
      elementLoc
    )
}

// createObjectExpression方法
export function createObjectExpression(
  properties: ObjectExpression['properties'],
  loc: SourceLocation = locStub
): ObjectExpression {
  return {
    type: NodeTypes.JS_OBJECT_EXPRESSION, // type = 15
    loc,
    properties
  }
}
复制代码

后续调用createObjectExpression方法返回一个type15的对象,properties属性为prop构成的数组。

// buildProps方法最后部分
if (hasDynamicKeys) {}
else {
    if (dynamicPropNames.length) {
      patchFlag |= PatchFlags.PROPS // 0|8 = 8
    }
}
// ...
return {
    props: propsExpression,
    directives: runtimeDirectives,
    patchFlag,
    dynamicPropNames
}
复制代码

最终buildProps方法返回了一个对象,表示属性的进一步解析结果{ props: {...}, patchFlag: 8, ... },之后回归到transformElement方法中,解析完属性之后开始处理children数组

// transformElement方法 处理 children 后续
if (node.children.length > 0) {
    // ...
    const shouldBuildAsSlots = isComponent && vnodeTag !== TELEPORT && vnodeTag !== KEEP_ALIVE
    if (shouldBuildAsSlots) {}
    else if (node.children.length === 1 && vnodeTag !== TELEPORT) {
        const child = node.children[0]
        const type = child.type
        const hasDynamicTextChild /* false */ =
          type === NodeTypes.INTERPOLATION ||
          type === NodeTypes.COMPOUND_EXPRESSION
        // ...
        if (hasDynamicTextChild || type === NodeTypes.TEXT) {
          vnodeChildren = child as TemplateTextChildNode
        } else {
          vnodeChildren = node.children
        }
    } else {}
}

if (patchFlag !== 0) {
    if (__DEV__) {
        if (patchFlag < 0) {/* patchFlag = 8 */} 
        else {
            const flagNames = Object.keys(PatchFlagNames)
            .map(Number)
            .filter(n => n > 0 && patchFlag & n)
            .map(n => PatchFlagNames[n])
            .join(`, `)
            // PatchFlagNames对象中 { 8: 'PROPS' }
            vnodePatchFlag = patchFlag + ` /* ${flagNames} */`
        }
    } else {
        vnodePatchFlag = String(patchFlag)
    }
    if (dynamicPropNames && dynamicPropNames.length) {
        vnodeDynamicProps = stringifyDynamicPropNames(dynamicPropNames) 
        // 在name首尾添加[] -> "["onClick"]"
    }
}
node.codegenNode = createVNodeCall(
  context,
  vnodeTag,
  vnodeProps,
  vnodeChildren,
  vnodePatchFlag,
  vnodeDynamicProps,
  vnodeDirectives,
  !!shouldUseBlock,
  false /* disableTracking */,
  node.loc
)
复制代码

children长度为1时(本例只有一个子节点),首先是给vnodeChildren赋值child=node.children[0]vnodePatchFlag="8 /* PROPS */"vnodeDynamicProps=["onClick"],最后调用createVNodeCall方法创建codegenNode属性对象

export function createVNodeCall(
  context: TransformContext | null,
  tag: VNodeCall['tag'],
  props?: VNodeCall['props'],
  children?: VNodeCall['children'],
  patchFlag?: VNodeCall['patchFlag'],
  dynamicProps?: VNodeCall['dynamicProps'],
  directives?: VNodeCall['directives'],
  isBlock: VNodeCall['isBlock'] = false,
  disableTracking: VNodeCall['disableTracking'] = false,
  loc = locStub
): VNodeCall {
  if (context) {
    if (isBlock) {
      context.helper(OPEN_BLOCK)
      context.helper(CREATE_BLOCK)
    } else {
      context.helper(CREATE_VNODE)
    }
    if (directives) {
      context.helper(WITH_DIRECTIVES)
    }
  }

  return {
    type: NodeTypes.VNODE_CALL, // type = 13
    tag,
    props,
    children,
    patchFlag,
    dynamicProps,
    directives,
    isBlock,
    disableTracking,
    loc
  }
}
// CREATE_VNODE定义
export const CREATE_VNODE = Symbol(__DEV__ ? `createVNode` : ``)
复制代码

createVNodeCall方法内部判断isBlock是否为true,本例为false,所以在contetxhelpers对象中添加Symbol('createVNode'),最终返回type13的对象

执行transformText函数中返回的自定义方法

执行完postTransformElement函数之后,继续执行自定义() => {}方法,看下这个方法的内部实现

() => {
    const children = node.children
    let currentContainer: CompoundExpressionNode | undefined = undefined
    let hasText = false
    
    for (let i = 0; i < children.length; i++) {
        const child = children[i]
        // isText: node.type === 5 || 2
        if (isText(child)) {
            hasText = true
            for (let j = i + 1; j < children.length; j++) {
                const next = children[j]
                if (isText(next)) {
                    if (!currentContainer) {
                        currentContainer = children[i] = {
                            type: NodeTypes.COMPOUND_EXPRESSION,
                            loc: child.loc,
                            children: [child]
                        }
                    }
                    // merge adjacent text node into current
                    currentContainer.children.push(` + `, next)
                    children.splice(j, 1)
                    j--
                } else {
                    currentContainer = undefined
                    break
                }
            }
        }
    }
}
复制代码

首选是for循环,循环nodechildren数组,判断child是否为文本节点(node.type等于5或者2),然后再判断child的下一个元素是否也是文本节点,本来中type1children长度是1(只有一个子节点),所以跳出for循环,执行后续代码

() => {
    // 自定义函数for循环之后的 内部实现
    if (!hasText ||
      (children.length === 1 && (node.type === NodeTypes.ROOT || (node.type === NodeTypes.ELEMENT &&
      node.tagType === ElementTypes.ELEMENT)))
      ) {
      return
    }
}
复制代码

以本例为模版来说,type1的元素满足children数组长度是1并且node.tagType === 0,所以return返回了。至此,type1节点的exitFns数组中的回调函数已经全部执行完成了,主要是生成codegenNode属性值(type13的对象),其中对props属性和children子节点都做了进一步的分析。

traverseNode中执行回调函数(type=0)

最后执行type0的根节点的回调函数,依然是上面的自定义函数() => {...}。首先是循环children数组(type0的根节点的children数组是type5,2,1三个对象)。for循环的内部实现大致是,如果childchild的下一个元素都是文本节点,则将child节点替换为type8的新对象,新对象的children属性为[child, '+', next(child的下一个元素)](包含三个元素的数组),所以现在type0的对象的children属性的值变成了[{type:8, children:[{type:5,...}, '+', {type:2}], ...}, {type:1, ...}]的新对象(原先type52的两个相邻文本对象替换为type8的对象)。最后执行自定义函数的结尾部分。

// createCallExpression方法定义
export function createCallExpression<T extends CallExpression['callee']>(
  callee: T,
  args: CallExpression['arguments'] = [],
  loc: SourceLocation = locStub
): InferCodegenNodeType<T> {
  return {
    type: NodeTypes.JS_CALL_EXPRESSION, // type = 14
    loc,
    callee,
    arguments: args
  } as any
}

// type为0的根节点对象 执行自定义函数的后续部分实现
() => {
    // ...
    for (let i = 0; i < children.length; i++) {
        const child = children[i]
        if (isText(child) || child.type === NodeTypes.COMPOUND_EXPRESSION) {
          const callArgs: CallExpression['arguments'] = []
          // createTextVNode defaults to single whitespace, so if it is a
          // single space the code could be an empty call to save bytes.
          if (child.type !== NodeTypes.TEXT || child.content !== ' ') {
            callArgs.push(child)
          }
          // mark dynamic text with flag so it gets patched inside a block
          if (
            !context.ssr &&
            getConstantType(child, context) === ConstantTypes.NOT_CONSTANT
          ) {
            callArgs.push(
              PatchFlags.TEXT +
                (__DEV__ ? ` /* ${PatchFlagNames[PatchFlags.TEXT]} */` : ``)
            )
          }
          children[i] = {
            type: NodeTypes.TEXT_CALL, // type = 12
            content: child,
            loc: child.loc,
            codegenNode: createCallExpression(
              context.helper(CREATE_TEXT), 
              // export const CREATE_TEXT = Symbol(__DEV__ ? `createTextVNode` : ``)
              callArgs
           )
         }
       }
    }
}
复制代码

最后的部分仍然是循环children数组(本例为type81两个对象),进一步转化type8的对象。以本例分析是将child对象和"1 /* TEXT */"字符串存放在callArgs数组中,然后child重新赋值为type=12的新对象,新对象的codegenNode属性赋值为createCallExpression方法的返回值{ type: 14, callee: Symbol('createTextVNode'), arguments: callArgs数组, ... }。那么type0的回调函数也执行完成了(主要是对文本节点对象做了一个合并)。回归到transform方法中,当traverseNode函数执行完成之后,看下后续的内部实现。

if (options.hoistStatic/* true */) {
    hoistStatic(root, context)
}
if (!options.ssr) {
    createRootCodegen(root, context)
}
// finalize meta information
root.helpers = [...context.helpers]
root.components = [...context.components]
root.directives = [...context.directives]
root.imports = context.imports
root.hoists = context.hoists
root.temps = context.temps
root.cached = context.cached

// hoistStatic方法定义
export function hoistStatic(root: RootNode, context: TransformContext) {
  walk(
    root,
    context,
    // Root node is unfortunately non-hoistable due to potential parent
    // fallthrough attributes.
    isSingleElementRoot(root, root.children[0]) // false(children.length !== 1)
  )
}
复制代码

首先会调用walk方法(依本例解析,walk方法中的条件判断不会命中,这里不详述了)。

createRootCodegen创建根codegenNode属性

之后调用createRootCodegen方法

function createRootCodegen(root: RootNode, context: TransformContext) {
  const { helper } = context
  const { children } = root
  if (children.length === 1) {
  } else if (children.length > 1) {
      let patchFlag = PatchFlags.STABLE_FRAGMENT // 64
      let patchFlagText = PatchFlagNames[PatchFlags.STABLE_FRAGMENT] // 'STABLE_FRAGMENT'
      root.codegenNode = createVNodeCall(
          context,
          helper(FRAGMENT), // export const FRAGMENT = Symbol(__DEV__ ? `Fragment` : ``)
          undefined,
          root.children,
          patchFlag + (__DEV__ ? ` /* ${patchFlagText} */` : ``),
          undefined,
          undefined,
          true // isBlock
      )
  }
}
复制代码

此方法内部调用createVNodeCall函数,这个函数我们上文提到过,最终返回一个type13的对象,因为isBlocktrue,所以在context对象的helpers(Set对象)中新增Symbol('openBlock')Symbol('createBlock')patchFlag64isBlocktruechildrenroot.children(type0children数组),tagSymbol('Fragment')。最后将context上的一些对象赋值到了root对象上。

总结

transform方法将baseParse函数解析的ast对象作为源,进一步转化。主要是通过traverseNode函数(当遇到根对象或者元素对象的时候会循环调用,解析子节点对象),这个方法内部首先是依次观察指令函数是否命中,收集回调函数进行循环执行,执行过程中会对元素节点的属性进行解析(以本例为模版,@click会命中transformOn处理事件监听对象,存储方法和事件回调函数);会对子节点进行解析(将两个相邻的文本节点进行合并,生成新的对象)等等。在根节点上生成codegenNode属性,属性中囊括了children子节点,属性propspatchFlag处理标志位等等。最后将context上的部分属性转移到root根对象上,为generate方法铺垫(例如helpers对象,其中存放的就是Vue暴露的创建节点的方法:创建动态数据的toDisplayString函数、创建节点的createVNode函数、创建文本节点的createTextVNode函数)。后文将解析generate函数,是如何利用codegenNode属性组装render函数字符串的。

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改