最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vu3追本溯源(五)ast转化之transform

    正文概述 掘金(筱普通人)   2021-08-21   499

    接上篇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对象
      ]
    }
    

    解析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函数字符串的。


    下载网 » Vu3追本溯源(五)ast转化之transform

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元