看不懂来打我,vue3如何将template编译成render函数

news2025/2/27 11:24:46

前言

在之前的 通过debug搞清楚.vue文件怎么变成.js文件 文章中我们讲过了vue文件是如何编译成js文件,通过那篇文章我们知道了,template编译为render函数底层就是调用了@vue/compiler-sfc包暴露出来的compileTemplate函数。由于文章篇幅有限,我们没有去深入探索compileTemplate函数是如何将template模块编译为render函数,在这篇文章中我们来了解一下。

@vue下面的几个包

先来介绍一下本文中涉及到vue下的几个包,分别是:@vue/compiler-sfc@vue/compiler-dom@vue/compiler-core

  • @vue/compiler-sfc:用于编译vue的SFC文件,这个包依赖vue下的其他包,比如@vue/compiler-dom@vue/compiler-core。这个包一般是给vue-loader 和 @vitejs/plugin-vue使用的。

  • @vue/compiler-dom:这个包专注于浏览器端的编译,处理浏览器dom相关的逻辑都在这里面。

  • @vue/compiler-core:从名字你也能看出来这个包是vue编译部分的核心,提供了通用的编译逻辑,不管是浏览器端还是服务端编译最终都会走到这个包里面来。

先来看个流程图

先来看一下我画的template模块编译为render函数这一过程的流程图,让你对整个流程有个大概的印象,后面的内容看着就不费劲了。如下图:
full-progress

从上面的流程图可以看到整个流程可以分为7步:

  • 执行@vue/compiler-sfc包的compileTemplate函数,里面会调用同一个包的doCompileTemplate函数。

  • 执行@vue/compiler-sfc包的doCompileTemplate函数,里面会调用@vue/compiler-dom包中的compile函数。

  • 执行@vue/compiler-dom包中的compile函数,里面会对options进行了扩展,塞了一些处理dom的转换函数进去。分别塞到了options.nodeTransforms数组和options.directiveTransforms对象中。然后以扩展后的options去调用@vue/compiler-core包的baseCompile函数。

  • 执行@vue/compiler-core包的baseCompile函数,在这个函数中主要分为4部分。第一部分为检查传入的source是不是html字符串,如果是就调用同一个包下的baseParse函数生成模版AST抽象语法树。否则就直接使用传入的模版AST抽象语法树。此时node节点中还有v-forv-model等指令。这里的模版AST抽象语法树结构和template模块中的代码结构是一模一样的,所以说模版AST抽象语法树就是对template模块中的结构进行描述。

  • 第二部分为执行getBaseTransformPreset函数拿到@vue/compiler-core包中内置的nodeTransformsdirectiveTransforms转换函数。

  • 第三部分为将传入的options.nodeTransformsoptions.directiveTransforms分别和本地的nodeTransformsdirectiveTransforms进行合并得到一堆新的转换函数,和模版AST抽象语法树一起传入到transform函数中执行,就会得到转换后的javascript AST抽象语法树。在这一过程中v-forv-model等指令已经被转换函数给处理了。得到的javascript AST抽象语法树的结构和将要生成的render函数的结构是一模一样的,所以说javascript AST抽象语法树就是对render函数的结构进行描述。

  • 第四部分为由于已经拿到了和render函数的结构一模一样的javascript AST抽象语法树,只需要在generate函数中遍历javascript AST抽象语法树进行字符串拼接就可以得到render函数了。

关注公众号:前端欧阳,解锁我更多vue干货文章。还可以加我微信,私信我想看哪些vue原理文章,我会根据大家的反馈进行创作。

@vue/compiler-sfc包的compileTemplate函数

还是同样的套路,我们通过debug一个简单的demo来搞清楚compileTemplate函数是如何将template编译成render函数的。demo代码如下:

<template>
  <input v-for="item in msgList" :key="item.id" v-model="item.value" />
</template>

<script setup lang="ts">
import { ref } from "vue";

const msgList = ref([
  {
    id: 1,
    value: "",
  },
  {
    id: 2,
    value: "",
  },
  {
    id: 3,
    value: "",
  },
]);
</script>

通过debug搞清楚.vue文件怎么变成.js文件 文章中我们已经知道了在使用vite的情况下template编译为render函数是在node端完成的。所以我们需要启动一个debug终端,才可以在node端打断点。这里以vscode举例,首先我们需要打开终端,然后点击终端中的+号旁边的下拉箭头,在下拉中点击Javascript Debug Terminal就可以启动一个debug终端。
debug-terminal

compileTemplate函数在node_modules/@vue/compiler-sfc/dist/compiler-sfc.cjs.js文件中,找到compileTemplate函数打上断点,然后在debug终端中执行yarn dev(这里是以vite举例)。在浏览器中访问 http://localhost:5173/,此时断点就会走到compileTemplate函数中了。在我们这个场景中compileTemplate函数简化后的代码非常简单,代码如下:

function compileTemplate(options) {
  return doCompileTemplate(options);
}

@vue/compiler-sfc包的doCompileTemplate函数

我们接着将断点走进doCompileTemplate函数中,看看里面的代码是什么样的,简化后的代码如下:

import * as CompilerDOM from '@vue/compiler-dom'

function doCompileTemplate({
  source,
  ast: inAST,
  compiler
}) {
  const defaultCompiler = CompilerDOM;
  compiler = compiler || defaultCompiler;
  let { code, ast, preamble, map } = compiler.compile(inAST || source, {
    // ...省略传入的options
  });
  return { code, ast, preamble, source, errors, tips, map };
}

doCompileTemplate函数中代码同样也很简单,我们在debug终端中看看compilersourceinAST这三个变量的值是长什么样的。如下图:
doCompileTemplate

从上图中我们可以看到此时的compiler变量的值为undefinedsource变量的值为template模块中的代码,inAST的值为由template模块编译而来的AST抽象语法树。不是说好的要经过parse函数处理后才会得到AST抽象语法树,为什么这里就已经有了AST抽象语法树?不要着急接着向下看,后面我会解释。

由于这里的compiler变量的值为undefined,所以compiler会被赋值为CompilerDOM。而CompilerDOM就是@vue/compiler-dom包中暴露的所有内容。执行compiler.compile函数,就是执行@vue/compiler-dom包中的compile函数。compile函数接收的第一个参数为inAST || source,从这里我们知道第一个参数既可能是AST抽象语法树,也有可能是template模块中的html代码字符串。compile函数的返回值对象中的code字段就是编译好的render函数,然后return出去。

@vue/compiler-dom包中的compile函数

我们接着将断点走进@vue/compiler-dom包中的compile函数,发现代码同样也很简单,简化后的代码如下:

import {
  baseCompile,
} from '@vue/compiler-core'

function compile(src, options = {}) {
  return baseCompile(
    src,
    Object.assign({}, parserOptions, options, {
      nodeTransforms: [
        ...DOMNodeTransforms,
        ...options.nodeTransforms || []
      ],
      directiveTransforms: shared.extend(
        {},
        DOMDirectiveTransforms,
        options.directiveTransforms || {}
      )
    })
  );
}

从上面的代码中可以看到这里的compile函数也不是具体实现的地方,在这里调用的是@vue/compiler-core包的baseCompile函数。看到这里你可能会有疑问,为什么不在上一步的doCompileTemplate函数中直接调用@vue/compiler-core包的baseCompile函数,而是要从@vue/compiler-dom包中绕一圈再来调用呢baseCompile函数呢?

答案是baseCompile函数是一个处于@vue/compiler-core包中的API,而@vue/compiler-core可以运行在各种 JavaScript 环境下,比如浏览器端、服务端等各个平台。baseCompile函数接收这些平台专有的一些options,而我们这里的demo是浏览器平台。所以才需要从@vue/compiler-dom包中绕一圈去调用@vue/compiler-core包中的baseCompile函数传入一些浏览器中特有的options。在上面的代码中我们看到使用DOMNodeTransforms数组对options中的nodeTransforms属性进行了扩展,使用DOMDirectiveTransforms对象对options中的directiveTransforms属性进行了扩展。

我们先来看看DOMNodeTransforms数组:

const DOMNodeTransforms = [
  transformStyle
];

options对象中的nodeTransforms属性是一个数组,里面包含了许多transform转换函数用于处理AST抽象语法树。经过@vue/compiler-domcompile函数处理后nodeTransforms数组中多了一个处理style的transformStyle函数。这里的transformStyle是一个转换函数用于处理dom上面的style,比如style="color: red"

我们再来看看DOMDirectiveTransforms对象:

const DOMDirectiveTransforms = {
  cloak: compilerCore.noopDirectiveTransform,
  html: transformVHtml,
  text: transformVText,
  model: transformModel,
  on: transformOn,
  show: transformShow
};

options对象中的directiveTransforms属性是一个对象,经过@vue/compiler-domcompile函数处理后directiveTransforms对象中增加了处理v-cloakv-htmlv-textv-modelv-onv-show等指令的transform转换函数。很明显我们这个demo中input标签上面的v-model指令就是由这里的transformModel转换函数处理。

你发现了没,不管是nodeTransforms数组还是directiveTransforms对象,增加的transform转换函数都是处理dom相关的。经过@vue/compiler-domcompile函数处理后,再调用baseCompile函数就有了处理dom相关的转换函数了。

@vue/compiler-core包的baseCompile函数

继续将断点走进vue/compiler-core包的baseCompile函数,简化后的baseCompile函数代码如下:

function baseCompile(
  source: string | RootNode,
  options: CompilerOptions = {},
): CodegenResult {
  const ast = isString(source) ? baseParse(source, options) : source

  const [nodeTransforms, directiveTransforms] = getBaseTransformPreset()

  transform(
    ast,
    Object.assign({}, options, {
      nodeTransforms: [
        ...nodeTransforms,
        ...(options.nodeTransforms || []), // user transforms
      ],
      directiveTransforms: Object.assign(
        {},
        directiveTransforms,
        options.directiveTransforms || {}, // user transforms
      ),
    }),
  )

  return generate(ast, options)
}

我们先来看看baseCompile函数接收的参数,第一个参数为source,类型为string | RootNode。这句话的意思是接收的source变量可能是html字符串,也有可能是html字符串编译后的AST抽象语法树。再来看看第二个参数options,我们这里只关注options.nodeTransforms数组属性和options.directiveTransforms对象属性,这两个里面都是存了一堆转换函数,区别就是一个是数组,一个是对象。

我们再来看看返回值类型CodegenResult,定义如下:

interface CodegenResult {
  code: string
  preamble: string
  ast: RootNode
  map?: RawSourceMap
}

从类型中我们可以看到返回值对象中的code属性就是编译好的render函数,而这个返回值就是最后调用generate函数返回的。

明白了baseCompile函数接收的参数和返回值,我们再来看函数内的代码。主要分为四块内容:

  • 拿到由html字符串转换成的AST抽象语法树。

  • 拿到由一堆转换函数组成的nodeTransforms数组,和拿到由一堆转换函数组成的directiveTransforms对象。

  • 执行transform函数,使用合并后的nodeTransforms中的所有转换函数处理AST抽象语法树中的所有node节点,使用合并后的directiveTransforms中的转换函数对会生成props的指令进行处理,得到处理后的javascript AST抽象语法树

  • 调用generate函数根据上一步处理后的javascript AST抽象语法树进行字符串拼接,拼成render函数。

获取AST抽象语法树

我们先来看第一块的内容,代码如下:

const ast = isString(source) ? baseParse(source, options) : source

如果传入的source是html字符串,那就调用baseParse函数根据html字符串生成对应的AST抽象语法树,如果传入的就是AST抽象语法树那么就直接赋值给ast变量。为什么这里有这两种情况呢?

原因是baseCompile函数可以被直接调用,也可以像我们这样由vite的@vitejs/plugin-vue包发起,经过层层调用后最终执行baseCompile函数。在我们这个场景中,在前面我们就知道了走进compileTemplate函数之前就已经有了编译后的AST抽象语法树,所以这里不会再调用baseParse函数去生成AST抽象语法树了。那么又是什么时候生成的AST抽象语法树呢?

在之前的 通过debug搞清楚.vue文件怎么变成.js文件 文章中我们讲了调用createDescriptor函数会将vue代码字符串转换为descriptor对象,descriptor对象中拥有template属性、scriptSetup属性、styles属性,分别对应vue文件中的template模块、<script setup>模块、<style>模块。如下图:
progress-createDescriptor
createDescriptor函数在生成template属性的时候底层同样也会调用@vue/compiler-core包的baseParse函数,将template模块中的html字符串编译为AST抽象语法树。

所以在我们这个场景中走到baseCompile函数时就已经有了AST抽象语法树了,其实底层都调用的是@vue/compiler-core包的baseParse函数。

获取转换函数

接着将断点走到第二块内容处,代码如下:

const [nodeTransforms, directiveTransforms] = getBaseTransformPreset()

从上面的代码可以看到getBaseTransformPreset函数的返回值是一个数组,对返回的数组进行解构,数组的第一项赋值给nodeTransforms变量,数组的第二项赋值给directiveTransforms变量。

将断点走进getBaseTransformPreset函数,代码如下:

function getBaseTransformPreset() {
  return [
    [
      transformOnce,
      transformIf,
      transformMemo,
      transformFor,
      transformFilter,
      trackVForSlotScopes,
      transformExpression
      transformSlotOutlet,
      transformElement,
      trackSlotScopes,
      transformText
    ],
    {
      on: transformOn,
      bind: transformBind,
      model: transformModel
    }
  ];
}

从上面的代码中不难看出由getBaseTransformPreset函数的返回值解构出来的nodeTransforms变量是一个数组,数组中包含一堆transform转换函数,比如处理v-oncev-ifv-memov-for等指令的转换函数。很明显我们这个demo中input标签上面的v-for指令就是由这里的transformFor转换函数处理。

同理由getBaseTransformPreset函数的返回值解构出来的directiveTransforms变量是一个对象,对象中包含处理v-onv-bindv-model指令的转换函数。

经过这一步的处理我们就拿到了由一系列转换函数组成的nodeTransforms数组,和由一系列转换函数组成的directiveTransforms对象。看到这里我想你可能有一些疑问,为什么nodeTransforms是数组,directiveTransforms却是对象呢?为什么有的指令转换转换函数是在nodeTransforms数组中,有的却是在directiveTransforms对象中呢?别着急,我们下面会讲。

transform函数

接着将断点走到第三块内容,transform函数处,代码如下:

transform(
  ast,
  Object.assign({}, options, {
    nodeTransforms: [
      ...nodeTransforms,
      ...(options.nodeTransforms || []), // user transforms
    ],
    directiveTransforms: Object.assign(
      {},
      directiveTransforms,
      options.directiveTransforms || {}, // user transforms
    ),
  }),
)

调用transform函数时传入了两个参数,第一个参数为当前的AST抽象语法树,第二个参数为传入的options,在options中我们主要看两个属性:nodeTransforms数组和directiveTransforms对象。

nodeTransforms数组由两部分组成,分别是上一步拿到的nodeTransforms数组,和之前在options.nodeTransforms数组中塞进去的转换函数。

directiveTransforms对象就不一样了,如果上一步拿到的directiveTransforms对象和options.directiveTransforms对象拥有相同的key,那么后者就会覆盖前者。以我们这个例子举例:在上一步中拿到的directiveTransforms对象中有key为model的处理v-model指令的转换函数,但是我们在@vue/compiler-dom包中的compile函数同样也给options.directiveTransforms对象中塞了一个key为model的处理v-model指令的转换函数。那么@vue/compiler-dom包中的v-model转换函数就会覆盖上一步中定义的v-model转换函数,那么@vue/compiler-core包中v-model转换函数是不是就没用了呢?答案是当然有用,在@vue/compiler-dom包中的v-model转换函数会手动调用@vue/compiler-core包中v-model转换函数。这样设计的目的是对于一些指令的处理支持不同的平台传入不同的转换函数,并且在这些平台中也可以手动调用@vue/compiler-core包中提供的指令转换函数,根据手动调用的结果再针对各自平台进行一些特别的处理。

我们先来回忆一下前面demo中的代码:

<template>
  <input v-for="item in msgList" :key="item.id" v-model="item.value" />
</template>

<script setup lang="ts">
import { ref } from "vue";

const msgList = ref([
  {
    id: 1,
    value: "",
  },
  {
    id: 2,
    value: "",
  },
  {
    id: 3,
    value: "",
  },
]);
</script>

接着在debug终端中看看执行transform函数前的AST抽象语法树是什么样的,如下图:
AST

从上图中可以看到AST抽象语法树根节点下面只有一个children节点,这个children节点对应的就是input标签。在input标签上面有三个props,分别对应的是input标签上面的v-for指令、:key属性、v-model指令。说明在生成AST抽象语法树的阶段不会对指令进行处理,而是当做普通的属性一样使用正则匹配出来,然后塞到props数组中。

既然在生成AST抽象语法树的过程中没有对v-modelv-for等指令进行处理,那么又是在什么时候处理的呢?答案是在执行transform函数的时候处理的,在transform函数中会递归遍历整个AST抽象语法树,在遍历每个node节点时都会将nodeTransforms数组中的所有转换函数按照顺序取出来执行一遍,在执行时将当前的node节点和上下文作为参数传入。经过nodeTransforms数组中全部的转换函数处理后,vue提供的许多内置指令、语法糖、内置组件等也就被处理了,接下来只需要执行generate函数生成render函数就行了。

nodeTransforms数组

nodeTransforms 主要是对 node节点 进行操作,可能会替换或者移动节点。每个node节点都会将nodeTransforms数组中的转换函数按照顺序全部执行一遍,比如处理v-if指令的transformIf转换函数就要比处理v-for指令的transformFor函数先执行。所以nodeTransforms是一个数组,而且数组中的转换函数的顺序还是有讲究的。

在我们这个demo中input标签上面的v-for指令是由nodeTransforms数组中的transformFor转换函数处理的,很简单就可以找到transformFor转换函数。在函数开始的地方打一个断点,代码就会走到这个断点中,在debug终端上面看看此时的node节点是什么样的,如下图:
before-transformFor

从上图中可以看到在执行transformFor转换函数之前的node节点和上一张图打印的node节点是一样的。

我们在执行完transformFor转换函数的地方打一个断点,看看执行完transformFor转换函数后node节点变成什么样了,如下图:
after-transformFor

从上图我们可以看到经过transformFor转换函数处理后当前的node节点已经变成了一个新的node节点,而原来的input的node节点变成了这个节点的children子节点。新节点的source.content里存的是v-for="item in msgList"中的msgList变量。新节点的valueAlias.content里存的是v-for="item in msgList"中的item。我们发现input子节点的props数组现在只有两项了,原本的v-for指令的props经过transformFor转换函数的处理后已经被消费掉了,所以就只有两项了。

看到这里你可能会有疑问,为什么执行transform函数后会将AST抽象语法树的结构都改变了呢?

这样做的目的是在后续的generate函数中递归遍历AST抽象语法树时,只想进行字符串拼接就可以拼成render函数。这里涉及到模版AST抽象语法树Javascript AST抽象语法树的概念。

我们来回忆一下template模块中的代码:

<template>
<input v-for="item in msgList" :key="item.id" v-model="item.value" />
</template>

template模版经过parse函数拿到AST抽象语法树,此时的AST抽象语法树的结构和template模版的结构是一模一样的,所以我们称之为模版AST抽象语法树模版AST抽象语法树其实就是描述template模版的结构。如下图:
template-AST

我们再来看看生成的render函数的代码:

function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  return _openBlock(true), _createElementBlock(
    _Fragment,
    null,
    _renderList($setup.msgList, (item) => {
      return _withDirectives((_openBlock(), _createElementBlock("input", {
        key: item.id,
        "onUpdate:modelValue": ($event) => item.value = $event
      }, null, 8, _hoisted_1)), [
        [_vModelText, item.value]
      ]);
    }),
    128
    /* KEYED_FRAGMENT */
  );
}

很明显模版AST抽象语法树无法通过简单的字符串拼接就可以拼成上面的render函数,所以我们需要一个结构和上面的render函数一模一样的Javascript AST抽象语法树Javascript AST抽象语法树的作用就是描述render函数的结构。如下图:
javascript-AST

上面这个Javascript AST抽象语法树就是执行transform函数时根据模版AST抽象语法树生成的。有了Javascript AST抽象语法树后再来执行generate函数时就可以只进行简单的字符串拼接,就能得到render函数了。

directiveTransforms对象

directiveTransforms对象的作用是对指令进行转换,给node节点生成对应的props。比如给子组件上面使用了v-model指令,经过directiveTransforms对象中的transformModel转换函数处理后,v-mode节点上面就会多两个props属性:modelValueonUpdate:modelValue属性。directiveTransforms对象中的转换函数不会每次都全部执行,而是要node节点中有对应的指令,才会执行指令的转换函数。所以directiveTransforms是对象,而不是数组。

那为什么有的指令转换函数在directiveTransforms对象中,有的又在nodeTransforms数组中呢?

答案是在directiveTransforms对象中的指令全部都是会给node节点生成props属性的,那些不生成props属性的就在nodeTransforms数组中。

很容易就可以找到@vue/compiler-dom包的transformModel函数,然后打一个断点,让断点走进transformModel函数中,如下图:
transformModel

从上面的图中我们可以看到在@vue/compiler-dom包的transformModel函数中会调用@vue/compiler-core包的transformModel函数,拿到返回的baseResult对象后再一些其他操作后直接return baseResult。从左边的call stack调用栈中我们可以看到transformModel函数是由一个buildProps函数调用的,看名字你应该猜到了buildProps函数的作用是生成props属性的。点击Step Out将断点跳出transformModel函数,走进buildProps函数中,可以看到buildProps函数中调用transformModel函数的代码如下图:
buildProps

从上图中可以看到,name变量的值为modelcontext.directiveTransforms[name]的返回值就是transformModel函数,所以执行directiveTransform(prop, node, context)其实就是在执行transformModel函数。在debug终端中可以看到返回的props2是一个数组,里面存的是v-model指令被处理后生成的props属性。props属性数组中只有一项是onUpdate:modelValue属性,看到这里有的小伙伴会疑惑了v-model指令不是会生成modelValueonUpdate:modelValue两个属性,为什么这里只有一个呢?答案是只有给自定义组件上面使用v-model指令才会生成modelValueonUpdate:modelValue两个属性,对于这种原生input标签是不需要生成modelValue属性的,因为input标签本身是不接收名为modelValue属性,接收的是value属性。

其实transform函数中的内容是非常复杂的,里面包含了vue提供的指令、filter、slot等功能的处理逻辑。transform函数的设计高明之处就在于插件化,将处理这些功能的transform转换函数以插件的形式插入的,这样逻辑就会非常清晰了。比如我想看v-model指令是如何实现的,我只需要去看对应的transformModel转换函数就行了。又比如哪天vue需要实现一个v-xxx指令,要实现这个指令只需要增加一个transformXxx的转换函数就行了。

generate函数

经过上一步transform函数的处理后,已经将描述模版结构的模版AST抽象语法树转换为了描述render函数结构的Javascript AST抽象语法树。在前面我们已经讲过了Javascript AST抽象语法树就是描述了最终生成render函数的样子。所以在generate函数中只需要递归遍历Javascript AST抽象语法树,通过字符串拼接的方式就可以生成render函数了。

将断点走到执行generate函数前,看看这会儿的Javascript AST抽象语法树是什么样的,如下图:
before-generate

从上面的图中可以看到Javascript AST模版AST的区别主要有两个:

  • node节点中多了一个codegenNode属性,这个属性中存了许多node节点信息,比如codegenNode.props中就存了keyonUpdate:modelValue属性的信息。在generate函数中遍历每个node节点时就会读取这个codegenNode属性生成render函数

  • 模版AST中根节点下面的children节点就是input标签,但是在这里Javascript AST中却是根节点下面的children节点,再下面的children节点才是input标签。多了一层节点,在前面的transform函数中我们已经讲了多的这层节点是由v-for指令生成的,用于给v-for循环出来的多个节点当父节点。

将断点走到generate函数执行之后,可以看到已经生成render函数啦,如下图:
after-generate

总结

现在我们再来看看最开始讲的流程图,我想你应该已经能将整个流程串起来了。如下图:
full-progress

将template编译为render函数可以分为7步:

  • 执行@vue/compiler-sfc包的compileTemplate函数,里面会调用同一个包的doCompileTemplate函数。这一步存在的目的是作为一个入口函数给外部调用。

  • 执行@vue/compiler-sfc包的doCompileTemplate函数,里面会调用@vue/compiler-dom包中的compile函数。这一步存在的目的是入口函数的具体实现。

  • 执行@vue/compiler-dom包中的compile函数,里面会对options进行了扩展,塞了一些处理dom的转换函数进去。给options.nodeTransforms数组中塞了处理style的转换函数,和给options.directiveTransforms对象中塞了处理v-cloakv-htmlv-textv-modelv-onv-show等指令的转换函数。然后以扩展后的options去调用@vue/compiler-core包的baseCompile函数。

  • 执行@vue/compiler-core包的baseCompile函数,在这个函数中主要分为4部分。第一部分为检查传入的source是不是html字符串,如果是就调用同一个包下的baseParse函数生成模版AST抽象语法树。否则就直接使用传入的模版AST抽象语法树。此时node节点中还有v-forv-model等指令,并没有被处理掉。这里的模版AST抽象语法树的结构和template中的结构一模一样,模版AST抽象语法树是对template中的结构进行描述。

  • 第二部分为执行getBaseTransformPreset函数拿到@vue/compiler-core包中内置的nodeTransformsdirectiveTransforms转换函数。nodeTransforms数组中的为一堆处理node节点的转换函数,比如处理v-on指令的transformOnce转换函数、处理v-if指令的transformIf转换函数。directiveTransforms对象中存的是对一些“会生成props的指令”进行转换的函数,用于给node节点生成对应的props。比如处理v-model指令的transformModel转换函数。

  • 第三部分为将传入的options.nodeTransformsoptions.directiveTransforms分别和本地的nodeTransformsdirectiveTransforms进行合并得到一堆新的转换函数。其中由于nodeTransforms是数组,所以在合并的过程中会将options.nodeTransformsnodeTransforms中的转换函数全部合并进去。由于directiveTransforms是对象,如果directiveTransforms对象和options.directiveTransforms对象拥有相同的key,那么后者就会覆盖前者。然后将合并的结果和模版AST抽象语法树一起传入到transform函数中执行,就可以得到转换后的javascript AST抽象语法树。在这一过程中v-forv-model等指令已经被转换函数给处理了。得到的javascript AST抽象语法树的结构和render函数的结构一模一样,javascript AST抽象语法树就是对render函数的结构进行描述。

  • 第四部分为由于已经拿到了和render函数的结构一模一样的javascript AST抽象语法树,只需要在generate函数中遍历javascript AST抽象语法树进行字符串拼接就可以得到render函数了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1585285.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据降维方法-主成分分析(PCA)

目录 一、前言 二、向量的表示及基变换 三、基变换 四、协方差矩阵 五、协方差 六、优化目标 一、前言 主成分分析(Principal Component Analysis) 用途&#xff1a;降维中的常用手段 目标&#xff1a;提取最有价值的信息&#xff08;基于方差&#xff09; 问题&#x…

使用 Citavi 和 NVivo 简化您的文献综述和研究分析

NVivo 是一款支持定性研究方法和混合研究方法的软件。它可以帮助您收集、整理和分析访谈、焦点小组讨论、问卷调查、音频等内容。NVivo&#xff08;1.0版&#xff09;是Windows和Mac的主要版本。遵循最新的主要版本NVivo 12&#xff08;Windows和Mac&#xff09;。 NVivo 强大…

前端开发攻略---利用Flexbox和Margin实现智能布局:如何巧妙分配剩余空间,让你的网页设计更上一层楼?

1、演示 2、flex布局 Flex布局是一种用于Web开发的弹性盒子布局模型&#xff0c;它可以让容器内的子元素在空间分配、对齐和排列方面具有更大的灵活性。以下是Flex布局的基本用法&#xff1a; 容器属性&#xff1a; display: flex;&#xff1a;将容器指定为Flex布局。flex-dire…

面试(02)————Java基础和集合

一、Java基础知识 1、面向对象的特征 2、Java 的基本数据类型有哪些 3、JDK JRE JVM 的区别 4、重载和重写的区别 5、Java中和equals的区别 6 、String、StringBuffer、StringBuilder三者之间的区别 7、接口和抽象类的区别是什么&#xff1f; 8、反射 9、jdk1.8 的新特…

行云堡垒国密算法应用与信创支持

一、 国密算法和信创的介绍 1.1 什么是国密算法 国密算法是国家密码管理局制定颁布的一系列的密码标准&#xff0c;即已经被国家密码局认定的国产密码算法&#xff0c;又称商用密码&#xff08;是指能够实现商用密码算法的加密&#xff0c;解密和认证等功能的技术&#xff09;…

视频图像的两种表示方式YUV与RGB(4)

本篇主要讲YUV与RGB之间的转换&#xff0c;包括YUV444 颜色编码格式 转为 RGB 格式 &#xff0c;RGB颜色编码格式转为 YUV444 格式。 一、 YUV与RGB之间的转换 YUV与RGB颜色格式之间进行转换时 , 涉及一系列的数学运算 ; YUV 颜色编码格式转为RGB格式的转换公式 取决于 于 YUV …

个人在线要饭网站源码

源码简介 施舍也要讲究便捷&#xff0c;如果能像购物一样&#xff0c;那也是很美的一件事情&#xff1b; 接入了支付宝当面付系统. 安装环境 php5.6 Nginx 安装教程 1.上传源码压缩包到网站目录并解压即可 2.支付配置 /修改文件 app/config.php /*** 请填写以下配置信…

微服务-4 Nacos

目录 一、注册中心 二、配置管理 1. 添加配置 2. 配置自动刷新 3. 多环境配置共享​编辑 一、注册中心 服务列表&#xff1a; 服务详情&#xff1a; 二、配置管理 1. 添加配置 (1). 在 nacos 界面中添加配置文件&#xff1a; 配置列表&#xff1a; 配置详情&#xff1a;…

东方博宜 1738. 胜负对决

东方博宜 1738. 胜负对决 以为这道题很简单呢&#xff0c;结果提交两次还不对&#xff0c;气死个人~ 思路&#xff1a;这道题的重点在于看清楚题意&#xff0c;是第奇数个&#xff0c;而不是数是奇数 。 还有&#xff0c;如果按照位数的奇偶来判定&#xff0c;那在读取数组的时…

The C programming language (second edition,KR) exercise(CHAPTER 2)

E x c e r c i s e 2 − 1 Excercise\quad 2-1 Excercise2−1&#xff1a;输出结果如图1和图2所示&#xff0c;这道练习题需要文章1和文章2的知识。 #include <stdio.h> #include <limits.h>float getFloat(char sign, unsigned char exp, unsigned mantissa); do…

Windows虚拟主机上多个域名访问同一个网站

近日老板提出了想要多个域名访问同一个网站的想法。这边了解后&#xff0c;由于我们公司使用的是Hostease的Windows虚拟主机产品&#xff0c;因此咨询了Hostease的技术支持&#xff0c;寻求帮助了解到可以实现Windows主机上多个域名访问同一个网站&#xff0c;是需要进入Window…

【洛谷 P8804】[蓝桥杯 2022 国 B] 故障 题解(概率论+条件概率+贝叶斯公式)

[蓝桥杯 2022 国 B] 故障 题目描述 在软件或系统开发中&#xff0c;我们会遇到各种各样的故障。为了从故障现象反推故障原因&#xff0c;工程师们会总结一种叫做相关性矩阵的二维表格&#xff0c;来表示故障原因与故障现象之间的关系。比如: 其中每行表示一种故障原因&#x…

Python学习从0到1 day25 第二阶段 SQL ② Python操作数据库

少年有梦&#xff0c;不应至于心动&#xff0c;更要付诸行动 —— 24.4.11 pymysql 除了使用图形化工具以外&#xff0c;我们也可以使用编程语言来执行SQL从而操作数据库 在Python中&#xff0c;使用第三方库&#xff1a;pymysql来完成对MySQl数据库的操作 安装 pip install py…

微信小程序视频下载工具

推荐您使用"下载高手"微信小程序视频下载工具&#xff0c;它利用占领系统代理&#xff0c;抓取小程序的请求&#xff0c;并集成了下载功能&#xff0c;让您轻松获取所需视频资源。 首先下载我给大家准备好的压缩包 1.首先先退出微信 注意:一定要右下角退出 2.然后来…

Element-UI 下拉框单选转多选回显不清空绑定的值

需求 根据radio切换来更改下拉框是否多选 原因 单选和多选这两个 input 看上去没差别&#xff08;自身和层级都一致&#xff09;&#xff0c;vue出于提高性能&#xff0c;所以 vue 给复用了 解决方案 <template><section><el-radio-group v-model"radi…

【机器学习算法】决策树和随机森林在计算机视觉中的应用

前言 决策树和随机森林在计算机视觉中有着广泛的应用。决策树作为一种简单而强大的分类模型&#xff0c;可以用于图像分类、目标检测、特征提取等任务。它能够根据图像的特征逐层进行判断和分类&#xff0c;从而实现对图像数据的智能分析和理解。随机森林作为一种集成学习方法&…

基于Lipschitz李式指数的随机信号特征识别和故障检测matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 Lipschitz李式指数定义与性质 4.2 Lipschitz李式指数的估计 4.3 Lipschitz李式指数在信号特征识别与故障检测中的应用 5.完整程序 1.程序功能描述 基于Lipschitz李式指数的随机信号特…

docker基本的掌握

前言&#xff1a;先要了解docker是干什么的&#xff0c; 1掌握基本概念&#xff0c;如;镜像&#xff0c;容器&#xff0c;数据卷 2知道使用常用命令 简易图; 补充&#xff1a; 默认情况下&#xff0c;每次重启虚拟机我们都需要手动启动Docker和Docker中的容器。通过命令可以实…

【MATLAB】基于Wi-Fi指纹匹配的室内定位-仿真获取WiFi RSSI数据(附代码)

基于Wi-Fi指纹匹配的室内定位-仿真获取WiFi RSSI数据 WiFi指纹匹配是室内定位最为基础和常见的研究&#xff0c;但是WiFi指纹的采集可以称得上是labor-intensive和time-consuming。现在&#xff0c;给大家分享一下我们课题组之前在做WiFi指纹定位时的基于射线跟踪技术仿真WiFi…

解决 VSCode 编辑器点击【在集成终端中打开】出现新的弹框

1、问题描述 在 VSCode 的项目下&#xff0c;鼠标右键&#xff0c;点击【在集成终端中打开】&#xff0c;出现新的一个弹框。新版的 VSCode 会有这个问题&#xff0c;一般来说我们都希望终端是在 VSCode 的控制台中打开的&#xff0c;那么如何关闭这个弹框呢&#xff1f; 2、解…