我们从vue-loader
开始,一步一步分析到vue/compiler-core
包源码,那么这编译阶段我们就分析完了(这里忽略compiler-ssr
) 。
这里涉及到了几个包,我们来整理一下。
我这里分析的是基于webpack
的,所以入口是vue-loader
。
简单的说下这里面做了什么。
首先在调用vue-loader
之前需要先安装vueLoaderPlugin
这里面主要做了几件事
vue-loader
标志位置为true
,这样webpack
编译器就可以调用vue-loader
了。loader
的rules
(不包含vue-loader
),分别给它们加上resouce
和resourceQuery
,这俩是用来确认请求的vue
资源是否可以被该loader
处理的,这里不用担心会影响到其它的loader
。copy
的rules
中注入templateLoader
,这样在资源是vue
的template
的时候就会被识别到并且最终处理成render function
。注意render function
是js
代码,所以它也需要被其它loader
识别处理。css-post-loader
用来处理vue
的style
,这里需要注意顺序,得在style loader
和css loader
之前执行。request
资源的路径,这样才能被vue-loader
识别到。webpack compiler
的rules
。然后回到vue-loader
中,根据请求路径返回对应的资源。这里说下script
部分没有loader
,所以在vue-loader
识别到请求的参数是js
的才会调用@vue/compiler-sfc
的compileScript
来处理并返回内容。
不同的loader
调用不同的@vue/compiler-sfc
方法处理不同的部分:
compileTemplate
:被templateLoader
调用,用来处理template
部分;compileScript
:被vue-loader
自身调用,用来处理script
部分;compileStyle
:被css-post-style
调用,用来处理style
部分;处理完后的资源在vue-loader
做runtime
拼接之后再传递给下一个loader
。
那么入口分析完了,就轮到被调用的包了。
坏蛋Dan:vue/compiler-sfc源码分析学习--汇总
坏蛋Dan:vue/compiler-sfc源码分析学习--part2:如何处理script--final章节整理
上面说了处理script
部分是通过compileScript
这个方法来处理的,在这个文件中核心是调用babel
来处理js
代码转换为AST
节点,然后基于AST
节点做处理。
场景很多,这里简单的说下:
setup
语法糖block
的script
,按optionApi
的方式来处理setup
语法糖block
的script
且有non setup block
的script
,都处理,其中non setup block
处理之后整合到setup block
的顶部。而setup block
处理成非语法糖的写法。注意:所有的语法糖都只是方便使用,而在编译阶段的都会转化成原本的写法。
坏蛋Dan:vue/compiler-sfc源码分析学习--part3:如何处理template--final章节整理
这里没怎么处理,调用的是在compiler-dom
包里,但是核心处理是在compiler-core
里。
坏蛋Dan:vue/compiler-sfc源码分析学习--part4:如何处理style--final章节整理
核心是调用postcss
[1] 来处理。
然后如果有lang
的话,调用对应的预处理器先。比如lang="scss"
,那么再开始调用postcss
解析之前会先调用sass-loader
来处理一遍。
其他没什么好说的了,但是有个点这里要提一下,就是处理cssVar
。
vue
在调用之前自定义了一个postcss
插件cssVarsPlugin
,这个插件就是用来处理vue3.x
中css
变量的方式。比如color: v-bind(color)
会变成var(--color)
的方式。
vue3.x
中使用css
变量: SFC CSS Features | Vue.js (vuejs.org)
另外还有一个点,就是scopeId
。
每一个sfc
文件都会有一个对应的scope Id
,如果这个style block
有要求scoped
,那么就会给每个css rule
加上scopeId
。
坏蛋Dan:vue/compiler-dom源码分析学习--final:整理
你应该注意到了,上面compiler-sfc
处理完了之后其实只剩下了template
没有处理好了,而script、style
都已经是可以运行在浏览器中的最终代码了。
而接下来compiler-dom
以及compiler-core
其实都是在处理template
相关的,因为template
只是一段长得像html
的js
代码。
不过尴尬的是html
解析引擎不认识它,js
的引擎也不认识它。
所以这里就需要自行去把这template
处理成js
代码。
而compiler-dom
这个包是compiler-core
的上游包,也就是基于compiler-core
封装的,加上了一些插件。
这个包没啥好说的,感兴趣的可以去看下。
不过这里有几个点需要注意:
v-on/v-model
等的modifiers
也就是修饰符的处理是放到这个包里的style\script
标签也是放在这个包里,这俩标签不应该再出现了(svg
标签里的style
和这里的不同)staticStringify
的判断和处理都是在这个包中。vue/compiler-core源码分析学习--final:汇总 - 知乎 (zhihu.com)
这个包是处理template
的核心部分
我直接把之前画的图贴出来,应该已经很清晰了。
可以参考babel
的core
包的架构。
简单地说就是把template
这一段字符串通过parse
变成AST
,然后通过transform
遍历所有节点通过不同插件处理节点,接着通过generate
生成render function
。这个时候的render function
已经是浏览器可以执行的runtime js
代码了。
这里面关于优化的东西我都没有分析,这里就简单说下我遇到过的优化:
cache
,这个cache
有runtime
的,比如指令的value
是一个表达式,那么表达式的结果其实是可以缓存的,这样每次调用返回缓存即可。而compile
阶段的缓存更多的是和hot-reload
搭配的,在下一次编译的时候可以过滤掉。staticHoist
,这个我们是有分析的,因为在主流程中。通过判断不同的点,比如属性是否是静态之类的来判断这个节点是否每次都需要create
,如果不需要就把它提升到顶部,这样就只需要create
一次。注意这个是runtime
的优化。另外这里可以hoist
不只有dom
节点,还有属性节点等也是可以的。staticStringify
,这个是在staticHoist
的基础上,如果不满足hoist
的条件那就一定不满足stringify
的条件。stringify
使用的是innerHTML
的方式插入,所以只有这一块代码中节点数量/指令数量达到了一定的个数时才会stringify
,因为如果数量少,其实innerHTML
插入的性能没啥提升。PatchFlag
,也就是插旗,给不同类型的节点插旗,这样可以快速定位需要如何处理,在patch
的时候diff
就能绕过一些逻辑,减少diff
的时间和损耗。track
,实际上和插旗一样,跟踪上面的flag
,如果没有flag
的节点,那diff
阶段就可以skip
了。分析完了忘了把最终结果发出来了。。。
这就是最后的结果,一个render function
和其它js
组成的部分。
为什么会长这样呢?还记得我在vue-loader
分析的文章中最后贴出来的runtime
代码图吗?
经过eval[2] 执行之后就会变成上面那个object
的样子,至于它们是怎么拼接到一起的,看下这图里有一个exportComponent
的方法,之前也有说过,这里再看一次代码
这也就是为什么被整合成一个对象的原因了。
render function
script runtime
注意这里的都是dev
阶段的,而对于prod
的来说则是
可以看到render function
不见了,其实是被整合到setup function
里面。
依旧的可读性极差。
此时的render function
变成了vnode
,而这些vnode
下一步就是patch/createElement
渲染到页面上了。
至于为啥dev
和prod
的区别这么大,这个还需要进一步分析。
prod
版本没有render function
字段让我们把mode
改成production
重新编译一次
可以看到template
相关的import
不见了。
也就是说没进入templateLoader
,自然就不会调用compileTemplate
。
那么返回去跟踪下,在vue-loader/index
中生成请求url
那段代码中
没想到。。。居然是我们没有分析的inline template
场景。。。。。
回想我们分析compiler-sfc
的compileScript
时确实遇到了判断inline
然后执行compiler-dom.compile
的场景。。。
那么一切就都说得通了,这里当compileScript
结束后,就已经变成vnode
的样子并且inline
到script
的setup
里面去了。。。。
那么编译阶段的源码我们就都分析完了,这一套分析下来其实我个人学到的东西非常非常多,整个vue
的编译流程也比以前清晰的多。
现在也不再觉得这是魔法了~
最后如果觉得对你有帮助的话请务必点个赞~
最后的最后,祝大家新春快乐!!
编辑于 2023-01-24 23:49・IP 属地广东