vue-template-compiler 的原理

什么是 Vue 模板?

Vue 模板是一种用 HTML 语法来描述 Vue 组件的视图的方式,

例如:

<template>
  <div class="example">{
   
   { msg }}</div>
</template>
<script>
  export default {
      
      
    data() {
      
      
      return {
      
      
        msg: "Hello world!",
      };
    },
  };
</script>
<style>
  .example {
      
      
    color: red;
  }
</style>

这里的 template 标签内的内容就是 Vue 模板,它可以使用一些 Vue 特有的指令和表达式,例如 v-if、v-for、{ { }} 等,来实现动态的视图渲染。

为什么要预编译 Vue 模板?

Vue 模板本身是一种字符串,要想让它在浏览器中显示出来,就需要将它转换为一个可以创建和更新 DOM 的函数,这个函数就叫做渲染函数。

Vue 提供了两种方式来生成渲染函数:

  • 运行时编译:这种方式是在浏览器中,使用 vue-template-compiler 的浏览器版本,将模板字符串动态地编译为渲染函数,然后执行渲染函数,创建和更新 DOM。这种方式的优点是可以使用任意的模板字符串,甚至可以从服务器端获取模板字符串,实现动态的视图渲染。缺点是需要额外的编译时间和编译器的代码体积,以及可能的 CSP 限制,因为编译器会使用 new Function 来创建渲染函数,这在一些严格的安全策略下是不允许的。
  • 预编译:这种方式是在构建时,使用 vue-template-compiler 的 Node.js 版本,将模板字符串静态地编译为渲染函数,并将渲染函数打包到最终的 js 文件中,然后在浏览器中直接执行渲染函数,创建和更新 DOM。这种方式的优点是可以避免运行时编译的开销和限制,提高性能和安全性。缺点是不能使用动态的模板字符串,只能使用固定的模板字符串,或者使用 render 函数来手动编写渲染函数。

预编译 Vue 模板的好处是显而易见的,它可以让我们的 Vue 应用更快更安全地运行,而不需要牺牲太多的灵活性。因此,预编译 Vue 模板是一种推荐的做法,尤其是在使用 webpack 或其他构建工具的情况下,我们可以很方便地使用 vue-loader 或其他方式来实现预编译。

vue-template-compiler 是如何工作的?

vue-template-compiler 的工作原理是,它会将 Vue 模板字符串转换为一个包含渲染函数和静态渲染函数的对象,这个对象可以被 vue-loader 或其他方式引用,从而创建一个 Vue 组件。

分为以下几个步骤:

  • 首先,它会使用 html-parser 来解析模板字符串,将其转换为一个抽象语法树 (AST)。AST 是一种用 JavaScript 对象来表示 HTML 结构的方式,它包含了元素、属性、文本、表达式等节点,以及它们之间的关系。例如,上面的模板字符串的 AST 大致如下:

    {
          
          
      type: 1, // 元素节点
      tag: "div", // 标签名
      attrsList: [{
          
           name: "class", value: "example" }], // 属性列表
      attrsMap: {
          
           class: "example" }, // 属性映射
      children: [
        {
          
          
          type: 2, // 表达式节点
          expression: "_s(msg)", // 表达式内容
          text: "{
          
          { msg }}", // 文本内容
        },
      ], // 子节点列表
    }
    
  • 然后,它会使用 optimize 来优化 AST,标记出静态节点和静态根节点,这样可以在渲染时跳过它们,提高性能。静态节点是指不依赖于数据的节点,例如纯文本节点或固定属性的元素节点。静态根节点是指只有一个子节点,并且这个子节点是静态节点的元素节点。例如,上面的模板字符串的 AST 中,div 节点就是一个静态根节点,它的子节点 { { msg }} 就是一个静态节点。optimize 会在 AST 的每个节点上添加一个 static 属性,表示是否是静态节点,以及一个 staticRoot 属性,表示是否是静态根节点。例如,上面的模板字符串的 AST 的 optimize 后的结果大致如下:

    {
          
          
      type: 1,
      tag: "div",
      attrsList: [{
          
           name: "class", value: "example" }],
      attrsMap: {
          
           class: "example" },
      children: [
        {
          
          
          type: 2,
          expression: "_s(msg)",
          text: "{
          
          { msg }}",
          static: true, // 静态节点
        },
      ],
      static: false, // 非静态节点
      staticRoot: true, // 静态根节点
    }
    
  • 接着,它会使用 codegen 来生成渲染函数的代码,包括创建元素、绑定属性、插入文本、添加事件等。codegen 会遍历 AST,将其中的节点转换为相应的渲染函数的代码片段,然后将这些代码片段拼接成一个完整的渲染函数,并添加一些必要的辅助函数和变量。例如,上面的模板字符串的 AST 的 codegen 后的结果大致如下:

    with (this) {
          
          
      return _c(
        "div",
        {
          
           staticClass: "example" },
        [_v(_s(msg))],
        1 /* STATIC */
      );
    }
    

    这里的 _c、_v、_s 等都是 Vue 提供的辅助函数,用于创建元素、创建文本、转义字符串等。最后的 1 是一个标志位,表示这个节点是静态的,可以跳过更新。

  • 最后,它会导出一个包含渲染函数和静态渲染函数的对象,可以被 vue-loader 或其他方式引用,从而创建一个 Vue 组件。渲染函数是用于创建和更新动态节点的函数,静态渲染函数是用于创建和缓存静态节点的函数。例如,上面的模板字符串的导出结果大致如下:

    export default {
          
          
      render: function () {
          
          
        with (this) {
          
          
          return _c(
            "div",
            {
          
           staticClass: "example" },
            [_v(_s(msg))],
            1 /* STATIC */
          );
        }
      },
      staticRenderFns: [],
    };
    

    这里的 staticRenderFns 是一个空数组,表示没有静态节点需要缓存。如果有静态节点需要缓存,它会包含一些静态渲染函数,用于创建和返回静态节点。

猜你喜欢

转载自blog.csdn.net/olderandyanger/article/details/134812850