美文网首页
2. vue3.0将template转化为render的过程

2. vue3.0将template转化为render的过程

作者: Doter | 来源:发表于2020-03-24 13:58 被阅读0次

上一文vue-loader如何实现.vue的处理,

我们了解了在vue-loader对.vue文件进行处理的过程中
在vue-loader中:

  1. 使用了compiler-sfcparse对.vue转换成import形式。
  2. 使用compileTemplate转换template为render函数。
  3. 使用compileStyle处理style中socpe。

所以下来我们来了解下compileTemplate处理的大致流程,以便我们后期深入到vue运行机制。

compileTemplate如何转化

我们跟踪调层层调用
最终是
compileTemplate -->
@vue/compiler-domcompile-->
compiler-corebaseCompile
所以我们直接来看baseCompile

export function baseCompile(
  template: string | RootNode,
  options: CompilerOptions = {}
): CodegenResult {
  const onError = options.onError || defaultOnError
  const isModuleMode = options.mode === 'module'
  ...
  // 对tempalet转化成ast。
  const ast = isString(template) ? baseParse(template, options) : template
  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(
    prefixIdentifiers
  )
  // 对ast进行node转换,此处预感比较重要待补充。
  transform(ast, {
    ...options,
    prefixIdentifiers,
    nodeTransforms: [
      ...nodeTransforms,
      ...(options.nodeTransforms || []) // user transforms
    ],
    directiveTransforms: {
      ...directiveTransforms,
      ...(options.directiveTransforms || {}) // user transforms
    }
  })
  // 将ast 转换成render函数
  return generate(ast, {
    ...options,
    prefixIdentifiers
  })
}

baseParse生成AST

这个就不分析源码了,太累了,我们直接几个简单的测试就行

// <template><div></div id=""></template>
exports[`compiler: parse Errors END_TAG_WITH_ATTRIBUTES <template><div></div id=""></template> 1`] = `
Object {
  "cached": 0,
  "children": Array [
    ...//省略
  ],
  "codegenNode": undefined,
  "components": Array [],
  "directives": Array [],
  "helpers": Array [],
  "hoists": Array [],
  "imports": Array [],
  "loc": Object {
    "end": Object {
      "column": 39,
      "line": 1,
      "offset": 38,
    },
    "source": "<template><div></div id=\\"\\"></template>",
    "start": Object {
      "column": 1,
      "line": 1,
      "offset": 0,
    },
  },
  "temps": 0,
  "type": 0,
}
`;

通过上面可以看到会解析成如上格式的Root
接下来我们可以看到对于template里面的children解析

test('simple div', () => {
      const ast = baseParse('<div>hello</div>')
      const element = ast.children[0] as ElementNode

      expect(element).toStrictEqual({
        type: NodeTypes.ELEMENT, //Vnode类型
        ns: Namespaces.HTML,
        tag: 'div',
        tagType: ElementTypes.ELEMENT,
        codegenNode: undefined,
        props: [],
        isSelfClosing: false,
        children: [
          {
            type: NodeTypes.TEXT,
            content: 'hello',
            loc: {
              start: { offset: 5, line: 1, column: 6 },
              end: { offset: 10, line: 1, column: 11 },
              source: 'hello'
            }
          }
        ],
        loc: {//对应的代码位置,后期map处理
          start: { offset: 0, line: 1, column: 1 },
          end: { offset: 16, line: 1, column: 17 },
          source: '<div>hello</div>'
        }
      })
    })

如上我们知道他就是生成了VNode(虚拟dom),在这个过程中对vue的语法如,@/v-for/v-model并没有做处理,只是将其转化为props
下面才是对这些进行处理的

transform做了哪些处理

transform对vue的模版语法进行了处理,继续看测试:
以v-on举例:

test('basic', () => {
    const { node } = parseWithVOn(`<div v-on:click="onClick"/>`)
    expect((node.codegenNode as VNodeCall).props).toMatchObject({
      properties: [
        {
          key: {
            content: `onClick`,
            isStatic: true,
            loc: {
              start: {
                line: 1,
                column: 11
              },
              end: {
                line: 1,
                column: 16
              }
            }
          },
          value: {
            content: `onClick`,
            isStatic: false,
            loc: {
              start: {
                line: 1,
                column: 18
              },
              end: {
                line: 1,
                column: 25
              }
            }
          }
        }
      ]
    })
  })

generate做了什么

上面已经将vue的模版语法处理为浏览器支持的标准属性形式。
generate就是将ast转化成render方法。

export function generate(
  ast: RootNode,
  options: CodegenOptions = {}
): CodegenResult {
  const context = createCodegenContext(ast, options)
  const {
    mode,
    push,
    prefixIdentifiers,
    indent,
    deindent,
    newline,
    scopeId,
    ssr
  } = context
  ...
  // 这里加入import的各种下面需要的方法,如:createVNode
  if (!__BROWSER__ && mode === 'module') {
    genModulePreamble(ast, context, genScopeId)
  } else {
    genFunctionPreamble(ast, context)
  }

  // 建立render
  if (genScopeId && !ssr) {
    push(`const render = _withId(`)
  }
  if (!ssr) {
    push(`function render(_ctx, _cache) {`)
  } else {
    push(`function ssrRender(_ctx, _push, _parent) {`)
  }
  indent()
  // 生成
  if (useWithBlock) {
    push(`with (_ctx) {`)
    indent()
    // function mode const declarations should be inside with block
    // also they should be renamed to avoid collision with user properties
    if (hasHelpers) {
      push(
        `const { ${ast.helpers
          .map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
          .join(', ')} } = _Vue`
      )
      push(`\n`)
      newline()
    }
  }

  //生成component的引用
  if (ast.components.length) {
    genAssets(ast.components, 'component', context)
    if (ast.directives.length || ast.temps > 0) {
      newline()
    }
  }
  //生成directives的引用
  if (ast.directives.length) {
    genAssets(ast.directives, 'directive', context)
    if (ast.temps > 0) {
      newline()
    }
  }
  if (ast.temps > 0) {
    push(`let `)
    for (let i = 0; i < ast.temps; i++) {
      push(`${i > 0 ? `, ` : ``}_temp${i}`)
    }
  }
  if (ast.components.length || ast.directives.length || ast.temps) {
    push(`\n`)
    newline()
  }

  //生成VNode的code,通过解析VNode生成调用createVNode形式的js逻辑。
  if (ast.codegenNode) {
    genNode(ast.codegenNode, context)
  } else {
    push(`null`)
  }

  if (useWithBlock) {
    deindent()
    push(`}`)
  }

  deindent()
  push(`}`)

  if (genScopeId && !ssr) {
    push(`)`)
  }

  return {
    ast,
    code: context.code, //将编译后结果返回。
    // SourceMapGenerator does have toJSON() method but it's not in the types
    map: context.map ? (context.map as any).toJSON() : undefined
  }
}

generate转化的步骤:

  1. 生成import 渲染所需要的方法代码
  2. 生成render代码
    2.1 生成引入components/directives的代码
    2.2 通过解析VNode生成调用createVNode形式的js创建dom逻辑
    3.返回以上生成的code。
    生成如下形式的code
    image.png

总结

  1. 将tempalet内容转化成ast。
  2. 对ast中vue的模板语法进行转换。
  3. 对ast生成render形式的code

至此,我们基本已经搞定了,.vue文件到最终生成的js过程。后面我们将继续开始了解,vue如何运行起来,render函数在什么时候调用,以及常说的diff在什么时候触发。

相关文章

网友评论

      本文标题:2. vue3.0将template转化为render的过程

      本文链接:https://www.haomeiwen.com/subject/zegnyhtx.html