上篇我们拉了包,跑通了调试环境,所以现在我们直接来调试。
坏蛋Dan:vue/compiler-sfc源码分析学习1--如何调试
如果不清楚如何调试,可以先看下这篇文章。
另外文章里只是分析主要流程,不会分析每一处,毕竟是一个牛逼的框架~,里面的东西需要兼容到各种场景。
上篇文章里执行的文件是compileScript.spec.ts
,看名字就知道是用来测试转换script
的,所以我们要换成parse.spec.ts
文件测试。
那么该找哪个测试元呢?我们仅是学习分析流程,所以有个壳即可。后面有需要再自己加。
就选这个pad content
了。该有的都有template, script, style, customBlock
,然后加点自己的代码
打上断点防止它跑了,然后我们进入到index.ts
中发现parse
方法是来自parse.ts
文件,所以让我们进入src/parse.ts
文件中找到parse
方法。
先在入口打上断点,看下数据是啥
然后在parse.spec.ts
中直接F5
又或者之前推荐的插件jest runner
直接debug
。
source
:自然就是我们的源码,这里截图没法截全。filename
: 如果是在vue
项目中,这里就会是处理的文件名,但由于我们这里是单元测试,所以只有个默认名。pad
: 咱暂时未知是干嘛用的,先mark
下。sourceRoot
:暂时未知是干嘛的,看名字可能和根文件App.vue
有关,先mark
一下。compiler
: CompilerDOM
,也就是@vue/compiler-dom
这个包,这个包我们以后再分析。我们接着往下看。
sourceKey
: 很好理解,就是获取缓存的key
值(喜闻乐见的key
值~,生怕会重复)。cache
如果已经有缓存的了就直接返回,不用怕里面的内容有更新,如果有更新,key
值也必然会更新。descriptor
: 一个vue sfc
文件对应一个,看结构就是要把template\script\style\customBlock
进行拆分shouldForceReload
: 这个一看就和热更新有关,我们先mark
下来,后面再分析。接着往下看。
source
转换成ast
这一段目前我们还看不懂,毕竟我们并没有看过@vue/compiler-dom
[1]这个包的代码,不知道这个包是干什么用的。
我们可以猜测,ast
这个变量名一眼就能猜到这个包的作用,compiler.parse
将我们的源代码source
转换为ast(abstract syntax tree)
抽象语法树[2]。
简单分析下getTextMode
这个回调,首先看名字这回调的作用是获取text
的类型,回调第一个参数是一个ast
,第二个参数parent
应该也是一个ast
,指向当前node
的parent node
也就是父节点。
看下数据,果然是一个ast
。
是根node
(这里根node
指的是template\script
等这四个) 并且不是template
或者是template
,template
的属性(props
)中存在lang
并且这个lang
属性存在且不是html
的template
都被标记为RAWTEXT
这个类型,其余都是DATA
类型。
目前我们还未知这两个类型指啥,不过我们可以根据这个判断条件猜测下。
如果template
的lang
不是html
那就被标记为RAWTEXT
,我猜测是特殊结构格式才会被标记为RAWTEXT
,后面需要特殊处理。 具体我们后面再分析。
我们来看下数据,至于里面具体做了什么我们现在先不分析,知道是转换为ast
即可。
简单的分析下
loc
: 如果学过babel
或者看过我之前的文章,你应该知道这个是定位代码的,代码在文件的第几行的第几列开始,又在第几行第几列结束。type
: 用来表示该节点(node
) 的类型,这里我们暂时并不清楚,后面分析compiler-dom
这个包的时候再说。codegenNode
: 不清楚,mark
下。components
: 这个很明显就是和子组件相关的。directives
: 这个很明显就是和自定义指令相关的。helpers
: 暂时未知,mark
下。hoists
: 未知,老规矩。imports
: 引入相关的。children
: 该节点(node
)的子node
。结构都差不多,一个sfc
文件大概可以分为前面提到过的四部分template、script、style、customBlock
,由此我们可以猜测可能是基于堆栈或者递归的方式处理的source
。然后我们接着往下看。
template、script、style、customeBlock
转换成block
遍历ast.children
。这里仅仅只是遍历,并不做递归处理,所以可以猜测只是处理那四个重要节点。
如果不是element
节点(node
)或者是空节点(指children
是空的~看着相像是命名有问题,但实际上没问题,因为如果children
是空的那就说明这个标签闭合时里面没有一丁点东西,自然就是空标签,如果里面有一个space
空格字符,那也会被标记为子节点,也不会为空。)且该节点不是template
标签且节点自身不存在src
这个属性prop
,就不处理。
简单地说就是处理对之后“有用“的节点。
在分析tag === 'template'
之前,我们来分析下方法createBlock
的代码。
首先是源码(source
)截取content
,根据ast
的loc
定位。
然后是对节点的属性props
的处理。这里仅处理静态属性NodeTypes.ATTRIBUTE
(type === 6
),也就是没有动态绑定的。
需要注意的一点是即使你的prop
命名是非原有的,不加上v-bind
或者:
都会被算作type === 6
。
比如我这里自己加了个www
的属性,在这里还是算ATTRIBUTE
。 (包括script
的lang、setup
以及style
的lang、scoped
)
然后是针对这四个节点做处理,统一成block
的格式,不同block
有不同的格式,比如style
多了scoped
,script
多了setup
。
这段很简单没什么好说的,不过最后一段可以看下elseif(type ==='script'&& p.name ==='setup'){;(block as SFCScriptBlock).setup = attrs.setup}
。这很明显是用来兼容setup
语法糖的。
然后我们回过头来分析下tag === template
的场景
if (!descriptor.template)
:最外层用if
判断是否已经处理过template
了,也就是说一个sfc
只能有一个template
并且有顺序要求。createBlock
:前面已经分析过了,这里会变成一个block
,来看下数据然后就是语法警告,不再支持vue2.x
的functional template
。
接着我们再来分析tag === 'script'
的代码
没什么好说的,就是转换成block
之后做了2.x
和3.x
的兼容。看下数据
再看下tag === 'style'
也没什么好说的,看下数据。
最后来看下default
也就是customBlock
看完代码有些点我们需要注意,一是style、customBlock
都会push
到对应的数组当中,也就意味着是可以存在多个的。而script
和template
都是只能存在一个,不同的是script
会后面的覆盖前面的,但是允许setup
和非setup
的两个script
同时存在,template
则是只拿第一个。
我们接着往下看
setup
语法糖的语法限制如果用了setup
就不允许使用src
链接外面的脚本。
sourceMap
这个就不多说了,直接看下数据即可。
其实vue2.x
就能用原生css
变量,但是需要通过:style
绑定进入,应该是觉得这样不够优雅,所以给调整成这个样子了。
这里我们并没有用到css
变量,所以为了测试下,我们补充下用例。
然后我们重新跑一下
正则去匹配v-bind
,判断是否有这个变量了,没有就放到descriptor.vars
里面。
然后继续往下看
接着是判断是否使用了:slotted
[5]
然后拼装数据写入缓存
最后返回。
vue/compiler-sfc/parse
方法做了什么为啥首先分析这个文件呢?因为这个方法是vue-loader
调用的,是解析的入口,也就是之前没有分析的现在补上了,具体可以看下这篇文章。
然后来总结下做了什么。
source + sourceMap + filename + sourceRoot + pad + compiler.parse
作为key
值,读缓存,如果存在则直接叉出去返回,不必担心存在缓存问题,连源码都做key
值了。@vue/compiler-dom
解析源码,转换成ast
。template、script、style、customBlocks
将ast
转换成block
,具体这么做的看上面。3.x setup
语法糖做用法限制,不允许使用script src=""
。source map
。css vars
。return
返回 。上面有一个点我们绕过的,那就是@vue/compiler-dom
做了什么,但是关于@vue/compiler-sfc
的文章都不准备将它,只要知道它将source
源码转换成ast
即可,后面会有专门的文章讲它。
如果觉得对你有帮助的话麻烦点个赞,谢谢!
编辑于 2022-11-21 16:15・IP 属地广东