或许很多小伙伴在使用Vue框架的时候, 难免会有疑惑,我们常见的.vue文件为什么浏览器能够识别的呢?带着这个疑虑,我们继续往下看。
初识模板编译
在此之前,我想表达一下我个人的观点, 之前尤大曾说过, “你们为什么要去读源码”, 这句话让人深思熟虑, 其实我的观点是,读源码的目的就是为了更深层的了解框架的设计,搞懂其设计原理, 而不是仅仅会用而已。
初识编译器
感兴趣的小伙伴可以像我一样,使用 vue-template-compiler 进行源码编译 步骤如下:
- 创建一个文件夹 (mkdir)
- 选择当前创建的文件夹 (cd 刚刚创建的文件)
- npm init -y (cd 选择当前创建的文件夹 -y可省略对package.json的描述 默认)
- 在创建的文件夹下 touch index.js (新建index.js)
- 在index.js 中引入 vue-template-compiler 然后模拟vue中写法 ...
- node index.js 就能看到模板编译过后的代码
附上代码截图:
进行解析
将模板编译成渲染函数可以分为两个步骤:
- 将模板解析成AST (抽象语法树)
- 使用AST生成渲染函数
从逻辑上来看 模板编译主要是分为三个部分:
- 将模板解析成AST(解析器)
- 遍历AST标记静态节点 (优化器)
- 使用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;
}
复制代码
编译过程
未完待续... 如有不足还望指正~