深入浅析Vue模板编译

830 阅读3分钟

或许很多小伙伴在使用Vue框架的时候, 难免会有疑惑,我们常见的.vue文件为什么浏览器能够识别的呢?带着这个疑虑,我们继续往下看。

初识模板编译

在此之前,我想表达一下我个人的观点, 之前尤大曾说过, “你们为什么要去读源码”, 这句话让人深思熟虑, 其实我的观点是,读源码的目的就是为了更深层的了解框架的设计,搞懂其设计原理, 而不是仅仅会用而已。

初识编译器

感兴趣的小伙伴可以像我一样,使用 vue-template-compiler 进行源码编译 步骤如下:

  1. 创建一个文件夹 (mkdir)
  2. 选择当前创建的文件夹 (cd 刚刚创建的文件)
  3. npm init -y (cd 选择当前创建的文件夹 -y可省略对package.json的描述 默认)
  4. 在创建的文件夹下 touch index.js (新建index.js)
  5. 在index.js 中引入 vue-template-compiler 然后模拟vue中写法 ...
  6. node index.js 就能看到模板编译过后的代码 附上代码截图:

WX20211201-232629@2x.png

WX20211201-232706@2x.png

进行解析

将模板编译成渲染函数可以分为两个步骤:

  1. 将模板解析成AST (抽象语法树)
  2. 使用AST生成渲染函数

从逻辑上来看 模板编译主要是分为三个部分:

  1. 将模板解析成AST(解析器)
  2. 遍历AST标记静态节点 (优化器)
  3. 使用AST生成渲染函数 (代码生成器)
源码剖析
 // 插值 
 const template =  `<p> {{ message }} </p>`
{
  ast: {
    type: 1,
    tag: 'p',
    attrsList: [],
    attrsMap: {},
    rawAttrsMap: {},
    parent: undefined,
    children: [ [Object] ],
    plain: true,
    static: false,
    staticRoot: false
  },
  render: "with(this){return _c('p',[_v(_s(message))])}",
  staticRenderFns: [],
  errors: [],
  tips: []
} 
//  表达式  
const template = `<p> {{ flag ? message : 'no message  found'}} </p>`

{
  ast: {
    type: 1,
    tag: 'p',
    attrsList: [],
    attrsMap: {},
    rawAttrsMap: {},
    parent: undefined,
    children: [ [Object] ],
    plain: true,
    static: false,
    staticRoot: false
  },
  render: `with(this){return _c('p',[_v("\\n    "+_s(flag ? message : 'no message  found')+"\\n   ")])}`,
  staticRenderFns: [],
  errors: [],
  tips: []
}


//  属性和动态属性
const template = `
   <div id="container" :index="currentIndex">
     <img :src="imgSrc"/>
   </div>
`
{
    ast: {
      type: 1,
      tag: 'div',
      attrsList: [ [Object], [Object] ],
      attrsMap: { id: 'container', ':index': 'currentIndex' },
      rawAttrsMap: {},
      parent: undefined,
      children: [ [Object] ],
      plain: false,
      attrs: [ [Object], [Object] ],
      hasBindings: true,
      static: false,
      staticRoot: false
    },
    render: `with(this){return _c('div',{attrs:{"id":"container","index":currentIndex}},[_c('img',{attrs:{"src":imgSrc}})])}`,
    staticRenderFns: [],
    errors: [],
    tips: []
  }
//  条件 v-if 
const template = `
    <div>
        <p v-if="flag === 'a'">A</p>
        <p v-else>B</p>
    </div>
`
{
    ast: {
      type: 1,
      tag: 'div',
      attrsList: [],
      attrsMap: {},
      rawAttrsMap: {},
      parent: undefined,
      children: [ [Object] ],
      plain: true,
      static: false,
      staticRoot: false
    },
    render: `with(this){return _c('div',[(flag === 'a')?_c('p',[_v("A")]):_c('p',[_v("B")])])}`,
    staticRenderFns: [],
    errors: [],
    tips: []
  }
// 遍历  v-for
const template = `
    <ul v-for="item in list" :key="item.id">
        <li>{{item.title}}</li>
    </ul>
`
{
  ast: {
    type: 1,
    tag: 'ul',
    attrsList: [],
    attrsMap: { 'v-for': 'item in list', ':key': 'item.id' },
    rawAttrsMap: {},
    parent: undefined,
    children: [ [Object] ],
    for: 'list',
    alias: 'item',
    key: 'item.id',
    plain: false,
    static: false,
    staticRoot: false,
    forProcessed: true
  },
  render: "with(this){return _l((list),function(item){return _c('ul',{key:item.id},[_c('li',[_v(_s(item.title))])])})}",
  staticRenderFns: [],
  errors: [
    'Cannot use v-for on stateful component root element because it renders multiple elements.'
  ],
  tips: []
}
// v-model
const template = `<input type="text" v-model="name">`
{
    ast: {
      type: 1,
      tag: 'input',
      attrsList: [ [Object], [Object] ],
      attrsMap: { type: 'text', 'v-model': 'name' },
      rawAttrsMap: {},
      parent: undefined,
      children: [],
      plain: false,
      attrs: [ [Object] ],
      hasBindings: true,
      directives: [ [Object] ],
      static: false,
      staticRoot: false,
      props: [ [Object] ],
      events: { input: [Object] }
    },
    render: `with(this){return _c('input',{directives:[{name:"model",rawName:"v-model",value:(name),expression:"name"}],attrs:{"type":"text"},domProps:{"value":(name)},on:{"input":function($event){if($event.target.composing)return;name=$event.target.value}}})}`,
    staticRenderFns: [],
    errors: [],
    tips: []
  }

// 从 vue 源码中找到缩写函数的含义

function installRenderHelpers (target) {
    target._o = markOnce;
    target._n = toNumber;
    target._s = toString;
    target._l = renderList;
    target._t = renderSlot;
    target._q = looseEqual;
    target._i = looseIndexOf;
    target._m = renderStatic;
    target._f = resolveFilter;
    target._k = checkKeyCodes;
    target._b = bindObjectProps;
    target._v = createTextVNode;
    target._e = createEmptyVNode;
    target._u = resolveScopedSlots;
    target._g = bindObjectListeners;
    target._d = bindDynamicKeys;
    target._p = prependModifier;
}
编译过程

WX20211201-230604@2x.png

从被编译过后的代码可以看出,会生成一个render函数,而很多小伙伴可能会好奇,这个with是个什么东西呢? 为什么我从未见过,感兴趣的小伙伴可以去看看我另一篇文章什么是作用域

未完待续... 如有不足还望指正~