了解如何编译之前, 先介绍一个重要但经常被忽略的知识
- 进入Vue2官网, 点击”起步”默认会进入”介绍”(见下图)
- 这次我们从”真正的第一项” — 安装开始看起
- 其中”安装”介绍了关于Vue的各种版本
-
同时介绍了表格里的”术语”
💡 术语-
完整版:同时包含编译器和运行时的版本。
-
编译器:用来将模板字符串编译成为 JavaScript 渲染函数的代码。
-
运行时:用来创建 Vue 实例、渲染并处理虚拟 DOM 等的代码。基本上就是除去编译器的其它一切。
-
-
这里提到的”编译器”就是编译 .vue文件 的方法, 见下方代码
<div id="app">
<div>{{ a }}</div>
</div>
<script src="<https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js>"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
a: 'hello world',
},
})
</script>
-
以上是一段简单的Vue2代码, 打开浏览器可以正确显示出 “hello world”
-
可以发现
<div>{{ a }}</div>不是浏览器支持的语法. 所以实际上能显示 hello world, 并不是由以上模板代码直接在浏览器执行的结果, 而是通过”编译器”编译, 再由浏览器执行 -
接下来用代码为大家演示Vue2是如何将上面代码编译的
-
首先必须引入”完整版”
<script src="<https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js>"></script> -
打开页面, 在控制台可以发现方法:
Vue.compile
- 将刚才的
template代码放到编译方法里执行, 结果如下
直接输出结果
.render 显示函数内容
点击 VM183:1 显示函数内容
-
可以看到
💡 简写的函数名分别是:template被编译成JavaScript_c=createElement
* `_v` = `createTextVNode`
* `_s` = `toString`
PS: 这里的 `toString` 是Vue实现的:
</aside>
-
编译结果可以翻译成
<div id="app" /> <script> const vm = new Vue({ el: '#app', name: 'App', data: () => ({ a: 'hello world!!!', }), render() { const {createElement, createTextNode, toString, a} = this return createElement('div', [createTextNode(toString(a))]) } }) <script> -
通过这些
JavaScript就可以顺利展示出 “hello world” -
以上便是在 .html 文件中使用
template开发, 由 Vue2完整版 进行编译的过程
但通常项目中是使用Webpack打包, 再由浏览器引入这些js, css等文件, 那么Webpack是如何对.vue文件进行编译的?
-
vue-loader
💡 Vue Loader 是一个 **[webpack](https://webpack.js.org/)** 的 loader,它允许你以一种名为**[单文件组件 (SFCs)](https://vue-loader.vuejs.org/zh/spec.html)** 的格式撰写 Vue 组件 -
接下来通过vue-loader的源码来了解是如何实现编译的
-
.vue文件分为
template,script,style三个部分, 而实际必须通过编译才能在浏览器执行的部分只有template, 所以我们在看 vue-loader 源码时可以重点关注 “template” 关键词
-
如上图所示, 在源码搜索 template 关键字可以找到这段处理
script,template,style的代码, 仔细观察发现这三段都在用descriptor对象, 接下来搜索descriptor, 找到这个变量在哪里生成的
- 再来找
parse方法
parse方法从vue/compiler-sfc中引用
core/packages/compiler-sfc at main · vuejs/core
-
至此, 了解到 vue-loader 是通过
@vue/compiler-sfc****的parse方法解析 .vue 文件, 从中解析出script,template,style -
用一段nodejs代码进行验证
// App.vue <template> <div id="app"> <div>{{ a }}</div> <input type="text" v-model="a"> </div> </template> <script> export default { name: 'App', data: () => ({ a: 'hello world' }), } </script>// build.js const { parse } = require('@vue/compiler-sfc') const fs = require('fs') const data = fs.readFileSync('./App.vue') const { descriptor } = parse(data.toString()) // 结果包含很多项, 本文档中只展示重点字段 console.log(Object.keys(descriptor)) -
运行 build.js
[ 'styles', 'template', 'script', // ... ] -
通过以上代码可以解析出 .vue 文件的三部分, 接下来开始解析
template -
vue-template-compiler可以将template编译成Javascript
- 继续修改 build.js, 根据文档加上
compile的部分
// build.js
const { parse } = require('@vue/compiler-sfc')
const fs = require('fs')
const compiler = require('vue-template-compiler')
const data = fs.readFileSync('./App.vue')
const { descriptor } = parse(data.toString())
// console.log(Object.keys(descriptor))
console.log(compiler.compile(descriptor.template.content).render)
- 运行结果如下:
with(this){
return _c(
'div',
{attrs:{"id":"app"}},
[
_c('div',[_v(_s(a))]),
_v(" "),
_c(
'input',
{
directives:[{name:"model",rawName:"v-model",value:(a),expression:"a"}],
attrs:{"type":"text"},
domProps:{"value":(a)},
on:{
"input": function($event){
if($event.target.composing) return;
a=$event.target.value
}
}
}
)
]
)
}
- 再将以上代码放到 .html 的
new Vue(...)方法中进行验证
<div id="app"/>
<script src="<https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js>"></script>
<script>
const vm = new Vue({
name: 'App',
data: () => ({
a: 'hello world!!!',
}),
render() {
const {_c, _v, _s, a} = this
return _c(
'div',
{attrs: {'id': 'app'}},
[
_c('div', [_v(_s(a))]),
_v(' '),
_c(
'input',
{
directives: [{name: 'model', rawName: 'v-model', value: (a), expression: 'a'}],
attrs: {'type': 'text'},
domProps: {'value': (a)},
on: {
// 注意这里的 this, 需要改成箭头函数
'input': ($event) => {
if ($event.target.composing) return
this.a = $event.target.value
},
},
},
),
],
)
},
}).$mount('#app')
</script>
- 以上便是 Webpack 如何将 .vue 文件编译成浏览器可执行文件的过程
扩展内容1: 可以在 input事件看到 $event.target.composing
composing是 Vue2 实现的自定义属性, 为了解决中文, 日文, 韩文等需要弹出输入选框的语言, 在选中前录入输入内容, 造成统计总字符数等判断时的错误
- 可通过以下代码验证
// window.kw 为 www.baidu.com 的主输入框
window.kw.addEventListener('compositionstart', () => console.log('open'))
window.kw.addEventListener('compositionend', () => console.log('close'))
扩展内容2: 解析 template 的过程
在解析过程中 vue-template-compiler 库调用 @vue/compiler-dom 逐行解析 template , 可查看源码进一步了解: