【vue3】vue3源码探索之旅:compiler-core(一)

简言

在这里插入图片描述

compiler-core包是vue3源码代码编译的核心代码包,且与平台无关,这个包主要功能是将代码解析成ast、然后转换成codegenNode对象、再编译生成可执行代码(render渲染函数)
在这里插入图片描述

compiler-core将沿着上面流程进行探索。

compiler-core

找到compiler-core/src/index.ts文件,可以看到它导出了baseCompile函数:
在这里插入图片描述
说明baseCompile是很重要的函数,它返回渲染函数。
然后进入改函数看代码:
在这里插入图片描述
看到了 baseParse函数、transform函数、generate函数,这个三个比较重要,将会围绕它们进行探索。

template解析

解析成ast的流程一般是词法分析+语法分析,然后解析实现代码由有限状态机模式实现,先逐个字符或片段进行词法分析,然后语法分析,得到ast树。

baseParse

template指的是有规则约束的代码字符串,compiler-core/src/parse.ts内的baseParse函数就是处理它的,返回ast树。

export function baseParse(
  content: string,
  options: ParserOptions = {
   }
): RootNode {
   
  const context = createParserContext(content, options)
  const start = getCursor(context)
  return createRoot(
    parseChildren(context, TextModes.DATA, []),
    getSelection(context, start)
  )
}

参数:

  • content : template字符串
  • options : 配置如何处理content字符串。

流程:

  1. createParserContext()函数,创建解析上下文context,这例存放源字符串、解析时的辅助信息和初始化配置。
  2. getCursor() : 获取当前读取位置信息。
  3. 创建根节点对象并返回,由createRoot()函数实现,根节点对象children属性存放的解析的上下文context的ast树啦。 由
    createRoot(
    parseChildren(context, TextModes.DATA, []),
    getSelection(context, start)
    )
    实现。

示例:

const source = `
<div id="foo" :class="bar.baz">
  {
    { world.burn() }}
  <div v-if="ok">yes</div>
  <template v-else>no</template>
  <div v-for="(value, index) in list"><span>{
    { value + index }}</span></div>
</div>
`.trim();
const ast = baseParse(source);
console.log("ast:: ", ast);

结果:

ast::  {
   
  type: 0,
  children: [
    {
   
      type: 1,
      ns: 0,
      tag: 'div',
      tagType: 0,
      props: [Array],
      isSelfClosing: false,
      children: [Array],
      loc: [Object],
      codegenNode: undefined
    }
  ],
  helpers: Set(0) {
   },
  components: [],
  directives: [],
  hoists: [],
  imports: [],
  cached: 0,
  temps: 0,
  codegenNode: undefined,
  loc: {
   
    start: {
    column: 1, line: 1, offset: 0 },
    end: {
    column: 7, line: 6, offset: 196 },
    source: '<div id="foo" :class="bar.baz">\n' +
      '  {
   { world.burn() }}\n' +
      '  <div v-if="ok">yes</div>\n' +
      '  <template v-else>no</template>\n' +
      '  <div v-for="(value, index) in list"><span>{
   { value + index }}</span></div>\n' +
      '</div>'
  }
}

parseChildren

parseChildren函数是进行解析的函数,它是一个解析器,解析未知内容,然后给未知内容甄别打标签,分发给解析指定内容的函数处理返回节点树。整体以堆栈的形式解析成ast树。
在这里插入图片描述

例如 : 它发现在解析的是文本,就用parseText函数处理,发现是注释就用parseComment函数处理,发现是元素就用 parseElement函数处理,发现是文本插值语法就用parseInterpolation函数处理。

codegenNode

codegenNode是什么?得到ast树之后,然后进行转换这一步,这一步就是生成codegenNode的。因为ast树只是将模板字符串读取了出来,你还要进行处理,处理得到codeenNode,才能编译成渲染函数执行。
在这里插入图片描述

transform

transform是执行转换处理的函数。在此之前需要调用getBaseTransformPreset()函数获取各种转换处理函数和操作函数。
在这里插入图片描述

export 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)
  }
  // finalize meta information
  root.helpers = new Set([...context.helpers.keys()])
  root.components = [...context.components]
  root.directives = [...context.directives]
  root.imports = context.imports
  root.hoists = context.hoists
  root.temps = context.temps
  root.cached = context.cached

  if (__COMPAT__) {
   
    root.filters = [...context.filters!]
  }
}

参数 :

  • root , 从baseCompile函数看传的的ast树(baseParse返回的),
  • options,转换配置信息。

流程 :

  1. createTransformContext函数生成转换上下文。
  2. traverseNode函数执行转换,各种转换就是在这调用执行的,指令啊,文本插值之类的。
  3. 若可以静态提升就执行hoistStatic函数
  4. 执行 createRootCodegen函数,在相应的ast树中生成codegenNode。
  5. 在根ast节点中绑定节点、指令、组件、静态提升信息、缓存等全局信息。

渲染函数

最后一步,生成渲染函数。
在这里插入图片描述
例如:

const source = `
<div id="foo" :class="bar.baz">
  {
    { world.burn() }}
  <div v-if="ok">yes</div>
  <template v-else>no</template>
  <div v-for="(value, index) in list"><span>{
    { value + index }}</span></div>
</div>
`.trim();

const render = baseCompile(source, {
   
  sourceMap: true,
  filename: `foo.vue`,
  // mode: "module",
});
console.log(render);

结果:
在这里插入图片描述

generate

compiler-core/src/codegen.ts内的generate函数就是主要的生成渲染函数的地方。

export function generate(
  ast: RootNode,
  options: CodegenOptions & {
   
    onContextCreated?: (context: CodegenContext) => void
  } = {
   }
): CodegenResult {
   
  const context = createCodegenContext(ast, options)
  if (options.onContextCreated) options.onContextCreated(context)
  const {
   
    mode,
    push,
    prefixIdentifiers,
    indent,
    deindent,
    newline,
    scopeId,
    ssr
  } = context

  const helpers = Array.from(ast.helpers)
  const hasHelpers = helpers.length > 0
  const useWithBlock = !prefixIdentifiers && mode !== 'module'
  const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
  const isSetupInlined = !__BROWSER__ && !!options.inline

  // preambles
  // in setup() inline mode, the preamble is generated in a sub context
  // and returned separately.
  const preambleContext = isSetupInlined
    ? createCodegenContext(ast, options)
    : context
  if (!__BROWSER__ && mode === 'module') {
   
    genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined)
  } else {
   
    genFunctionPreamble(ast, preambleContext)
  }
  // enter render function
  const functionName = ssr ? `ssrRender` : `render`
  const args = ssr ? ['_ctx', '_push', '_parent', '_attrs'] : ['_ctx', '_cache']
  if (!__BROWSER__ && options.bindingMetadata && !options.inline) {
   
    // binding optimization args
    args.push('$props', '$setup', '$data', '$options')
  }
  const signature =
    !__BROWSER__ && options.isTS
      ? args.map(arg => `${
     arg}: any`).join(',')
      : args.join(', ')

  if (isSetupInlined) {
   
    push(`(${
     signature}) => {
    `)
  } else {
   
    push(`function ${
     functionName}(${
     signature}) {
    `)
  }
  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 { ${
     helpers.map(aliasHelper).join(', ')} } = _Vue`)
      push(`\n`)
      newline()
    }
  }

  // generate asset resolution statements
  if (ast.components.length) {
   
    genAssets(ast.components, 'component', context)
    if (ast.directives.length || ast.temps > 0) {
   
      newline()
    }
  }
  if (ast.directives.length) {
   
    genAssets(ast.directives, 'directive', context)
    if (ast.temps > 0) {
   
      newline()
    }
  }
  if (__COMPAT__ && ast.filters && ast.filters.length) {
   
    newline()
    genAssets(ast.filters, 'filter', context)
    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()
  }

  // generate the VNode tree expression
  if (!ssr) {
   
    push(`return `)
  }
  if (ast.codegenNode) {
   
    genNode(ast.codegenNode, context)
  } else {
   
    push(`null`)
  }

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

  deindent()
  push(`}`)

  return {
   
    ast,
    code: context.code,
    preamble: isSetupInlined ? preambleContext.code : ``,
    // SourceMapGenerator does have toJSON() method but it's not in the types
    map: context.map ? (context.map as any).toJSON() : undefined
  }
}

参数:

  • ast :转换后的ast树。
  • options:编译时的配置。

流程:

  1. createCodegenContext函数创建代码生成上下文,包含配置信息和辅助操作函数。
  2. 根据编译模式(module或function)生成不同的渲染函数模板声明。
  3. 生成资产引用和优化
  4. 生成vnode表达式
  5. 返回包含ast、渲染函数、map映射信息、preamble信息的对象。

结语

compiler-core包的主要流程就是这样,是与平台无相关的实现解析编译vue模板的基础代码。

相关推荐

  1. Vue探索Vue2.x分析(

    2024-01-30 12:32:01       26 阅读
  2. 学习 Vue 3

    2024-01-30 12:32:01       44 阅读
  3. vue3 解析Reactive实现的原理

    2024-01-30 12:32:01       30 阅读
  4. vue3(四)watch

    2024-01-30 12:32:01       30 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-01-30 12:32:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-30 12:32:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-30 12:32:01       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-30 12:32:01       20 阅读

热门阅读

  1. 四:C语言-条件分支语句

    2024-01-30 12:32:01       42 阅读
  2. 【前端】防抖和节流

    2024-01-30 12:32:01       43 阅读
  3. nginx项目部署+项目优化

    2024-01-30 12:32:01       40 阅读
  4. L1-032 Left-pad

    2024-01-30 12:32:01       42 阅读
  5. 美易官方《盘前:道指期货跌0.04% 风险周降临》

    2024-01-30 12:32:01       41 阅读
  6. 随机森林和决策树区别

    2024-01-30 12:32:01       40 阅读
  7. ID3算法 决策树学习 Python实现

    2024-01-30 12:32:01       40 阅读
  8. ST表板子 类似归并的有条理暴力 sparse-table

    2024-01-30 12:32:01       42 阅读
  9. 2024年最新版 在AlmaLinux上安装MongoDB

    2024-01-30 12:32:01       41 阅读