前言
在vue项目中,.vue 文件称为 SFC(Single File Components),在vue的源码中,有一个sfc模块专门负责.vue单文件组件的解析
vue 会先对 .vue 文件进行解析,分成 template、script、styles、customBlocks 四个部分,称为 descriptor。之后,再对这四个部分分别进行编译最终得到可以在浏览器中执行的 .js 文件。
SFCDescriptor,是表示 .vue 各个代码块的对象,为以下数据格式:
1 | // an object format describing a single-file component. |
vue 提供了一个 compiler.parseComponent(file, [options])方法,来将 .vue 文件解析成一个 SFCDescriptor。
1. 文件入口
解析 sfc 文件的源码入口在 src/sfc/parser.js 中,编译后的产出在 /packages/vue-template-compiler 和 /packages/vue-server-renderer 下的 build.js 中。
build.js 文件中直接 export 出了parseComponent方法。
2. parseComponent方法
1 | /** |
以上代码中,parseComponent方法中主要定义了start和end两个函数,之后调用了parseHTML方法来对 .vue 文件内容践行编译。start和end两个函数作为参数传给了parseHTML
3. parseHTML方法
parseHTML是一个html-parser,分解分析 .vue 的关键,parseHTML的代码细节较多,遍历解析查找文件中的各个标签,解析到每个起始标签时,调用 option 中的 start 方法进行处理;解析到每个结束标签时,调用 option 中的 end 方法进行处理。(即parseComponent中定义的 start 和 end)
由于我们这里只是想要找到第一层标签,也就是 template、script这些。因此可以在parseComponent中维护一个 depth 变量,在start中将depth++,在end中depth–。那么,每个depth === 1的标签就是我们需要获取的信息,包含 template、script、style 以及一些自定义标签。
1 |
|
1)一个 while 循环
在 while 循环中,存在两个大的分支,一个用来分析 template ,一个是用来分析 script 和 style。
2)函数 advance
向前跳过文本
3)函数 parseStartTag
判断当前的 node 是不是 openTag
4)函数 handleStartTag
处理 openTag, 这里就用到了之前提到的 start() 函数
5)函数 parseEndTag
判断当前的 node 是不是 closeTag,同时这里也用到了 end() 函数
通过以上各个函数的组合,在while循环中就将 sfc 分割成了三个不同的部分
4. start
1 | function start ( |
1)记录下 currentBlock。每个 currentBlock 包含以下内容
1 | declare type SFCBlock = { |
2)根据 tag 名称,将 currentBlock 对象保存在在返回结果对象中。
返回结果对象定义为 sfc,如果tag不是 script,style,template 中的任一个,就放在 sfc.customBlocks 中。如果是style,就放在 sfc.styles 中。script 和 template 则直接放在 sfc.script 和 sfc.template 下。
5. end
每当遇到一个结束标签时,执行end函数。
1 | function end (tag: string, start: number, end: number) { |
如果当前是第一层标签(depth === 1),并且 currentBlock 变量存在,那么取出这部分text,放在 currentBlock.content 中。
在将 .vue 整个遍历一遍后,得到的 sfc 对象即为我们需要的 SFCDescriptor。
6. 生成 .js
compiler.parseComponent(file, [options])得到的只是一个组件的 SFCDescriptor,最终编译成.js 文件是交给 vue-loader 等库来做的。
https://github.com/vuejs/vue/blob/dev/packages/vue-template-compiler/index.js