上篇文章我们找到了调用入口,找了测试单元,也分析了非setup
的script block
做了什么。如果没看过上篇文章的可以去看下。
[坏蛋Dan:vue/compiler-sfc源码分析学习--part2:如何处理script--day1
今天我们就接着往下分析。
老规矩,同样是省略一些error
判断和无关场景,一下non setup script block
指的是不带有任何属性的script block
,比如。同理`setup script block`指的是
。
由于day1
分析时分析到了non setup script block
,切换了测试单元,所以在分析代码之前我们需要切换回来。
这个测试元带有一个setup script block
以及一个non setup block
并且这个单纯的script
不带有lang
。
然后直接左上角debug
script block
啥叫单纯script block
?指的就是这种,当然,也可以带上`lang`,比如
。
注意你的script block
的lang
都需要保持一致。
代码有些长并且也不太好再切块来分析,所以我们只能是慢慢来了。
magic-string
[1]: 转换为可修改的source
源码,上篇文章中提到过了,这里就不多说了。startOffset
: setup block
代码起始位置。endOffset
: setup block
代码的结束位置。scriptStartOffset
: non setup block
代码的起始位置。scriptEndOffset
: non setup block
代码的结束位置。parse
: 来看下parse
方法的代码。其实还是调用babel
[2]的parse
方法。接下来是和babel
的ast
有关的代码。
由于我们的non script block
并不带有import
,所以我们先暂停调试,切回去加上import
然后重新debug
ImportDeclaration
[3]: import
声明ImportSpecifier
[4]: 非default
,即是通过解构的方式导入的,而default
则是:ImportDefaultSpecifier
[5]node.importKind === 'type'
: 这里提一下这个,这个是专门import
类型的,用法如下。registerUserImport
: 看名字就知道是用来注册你代码里的import
的,来看下代码。注册import
source
: 对应node.source.value
, 导入对象所在的模块local
: 对应specifier.local.name
: 在当前文件中的namespace
,比如{ a as _a }
,这个_a
就是一个local name
。imported
: 如果import
的是default
,比如import a from 'b.js'
,则不会有imported
这个字段isType
: 这指的是导入是否仅限于type
,比如import type { a } from 'b.d.ts'
这样。isFromSetup
: false
,毕竟我们在分析一个单纯的script block
。needTemplateUsageCheck
: 这个是针对是否是inlineTemplate
的,是的话不需要check
。isImportUsed
: 这个看名字就知道是干啥的,确认你的import
的local name
是否被template
使用了。代码就不看了,里面是通过ast
去处理排查的。这段代码也挺简单,主要做了仨事
vue
中import
的api
,格式为[original name]:namespace
。template
以及这个import
的对象是否被template
使用了 。userImports
中。让我们回到之前的代码,回到遍历ast
节点那里。
elseif(node.type ==='ExportDefaultDeclaration')
场景
ExportDefaultDeclaration
[6]: 这个看名字就知道是export default
声明。hasDefaultExportName
: 有name
字段hasDefaultExportRender
: 有render
字段这块代码也很简单,就是判断这个non setup block
是否有export default
,如果有,判断是否有name
和render
字段,为啥要判断这俩呢?我们后面会说到。然后将字符串export default
为const __default__ =
。
这里你是否有疑惑?前面不是也处理过了吗?为什么这里又有一个这样的,其实前面处理的是没有setup
语法糖script block
的场景。而这里是存在setup
语法糖block
的情况下又存在单个script block
。
我们接着往下看
else if (node.type === 'ExportNamedDeclaration')
场景
由于我们的测试单元没有这块逻辑,所以我们加点料
ExportNamedDeclaration
[7]: 导出声明。defaultSpecifier
: 这个指的是导出默认,比如export { c as default } from 'd.js'
但是这个需要babel
的相关插件处理,不然会报错。walkDeclaration
: 这里就不分析了,看下一个小标题即可。简单的说就是在给将被setup暴露出去的数据分门别类。这段代码也不难理解,主要做了俩事
export
了default
,如果是,先移除这行代码,然后改为import { x as __default__ } from './x'
这样,为什么要这么做呢?因为后面会统一export __default__
,所以这里自然得改为import
。declaration
),调用walkDeclaration
。我们接着往下分析
其它场景
VariableDeclaration
[8]: 变量声明。FunctionDeclaration
[9]: 函数声明,而且是function xx () {}
这种,箭头函数声明则是另一个。ClassDeclaration
[10]: 类声明。TSEnumDeclaration
: typescript
的枚举类型声明,比如:walkDeclaration
: 这里也不分析,看下一个小标题即可。简单的说就是在给将被setup
暴露出去的数据分门别类。来总结下这段对ast
节点遍历处理。
简单的说就是在从上到下整理代码,具体逻辑请往回看。
import
, 将他们注册到userImport
这个集合中。export default
,将export default
改写为const __default__ =
,等着后面统一export
。default
的export
,都被收集到bingdings
这个集合中等着后面统一setup
暴露出去。当然如果是export { x as default }
这种就会被移除,取而代之的是import { x as __default__ } from
,毕竟导出得统一导出,你这里直接导出default
就乱套了。TS
枚举声明则调用walkDeclaration
将变量分门别类收集到bindings
这个集合中。然后让我们回到处理这个non setup script block
的代码中,由于里的太远,所以这里就再贴一次代码 。
enableReactivityTransform
: 这个day1
中解释过了,简单的说就是一个目前还在实验中的语法糖,会在编译过程中替换为原来的用法。需要在options
中配置reactivityTransform: true
。shouldTransform
: day1
中也说过了,判断你这里面有没有语法糖用法,比如$ref
。transformAST
: 同上,这个方法来自@vue/reactivity-transform
[11]这个包,另外这个方法里面是会改动源码的,比如$ref
语法糖不需要xxx.value = xxx
来实现响应式,直接xxx = xxx
就行了,而这里面就是帮你把xxx = xxx
转换成xxx.value = xxx
。importedHelpers
: 辅助函数,一般runtime
才会需要辅助函数。 上篇文章里也说过了,实际上就是帮你导入对应的vue
的api
。比如你用了$ref
,然后这里会帮你import { ref as _ref } from 'vue';
。rootRefs
: 语法糖赋值的对象数组,比如const a = $ref(0)
,这里的a就会被放到rootRefs
中。这两块代码很简单,就是处理reactivityTransform
语法糖还有将non setup script block
迁移至setup script block
之前,这样non setup script
中的__default__
才能正常被setup script block
调用。
总结一下这一个标题内容
简单的说就是在处理带有setup
的script block
之前,先处理掉没有setup
属性的script block
。
babel
将代码转换为ast
,通过调用magic-string
将代码转换成可操作的source
方便后面移除或者替换代码。ast
import
;export
,具体看上面分析;export
、变量、函数、类以及typescript
的枚举(enum
)声明;activityTransform
语法糖,用了转换成原来的。non setup script block
迁移至setup script block
之前,确保__default__
能被setup
的调用到。walkDeclaration
做了什么。单独拿出来说,因为里面的逻辑还是比较绕的,涉及到很多babel
的东西。
代码有些乱,简单的说一下。
TSEnumDeclaration、 FunctionDeclaration、 ClassDeclaration
这几个都会被标记为setup-const
。
而VariableDeclaration
是用于变量声明的,而变量声明是一个不稳定状态,可能被赋值一个常量又可能被赋值一个箭头函数,所以需要特殊处理。
由于我们的测试单元没有设置变量,所以我们加点料
然后我们重新跑一下。
isConst
: 变量声明是用的const
关键字isCallOf
: 代码就不看了,简单的说一下就是判断这个node
的赋值是否是一个CallExpression
[12]节点。比如:const a = defineProps({});
isDefineCall
: 用的const
声明,然后等式右边用的defineProps
[13]、defineEmits
[14]又或者是withDefaults
[15]Identifier
[16]: 赋值的对象,比如const aaa = defineProps({})
,这里的aaa
就是indentifier
,即变量的名字。userReactiveBinding
: 表达式右边是否用了reactive
[17]。canNeverBeRef
: 顾名思义,就是不可能变为ref
的节点类型,有以下几种UnaryExpression
[18]: 表达式语句中的一元表达式,比如delete xxx;
就是一个一元表达式。BinaryExpression
[19]: 表达式语句中的二元表达式,比如const a = b + c
,这里的b + c
就是一个二元表达式。ArrayExpression
[20]: 这个不多说,大家都懂。ObjectExpression
[21]: 这个也不多说。FunctionExpression
[22]: 这个也不多说。ArrowFunctionExpression
[23]: 这个依旧是不多说,箭头表达式。UpdateExpression
[24]: 更新表达式,比如a++
。ClassExpression
[25]:类表达式,上面提到过了。taggedTemplateExpression
[26]: 标记模板文字表达式,很拗口。。找到个大佬的描述[27]。比如SequenceExpression
[28]: 序列表达式, 比如: a = 1, b = 2, c = 3;
它自身需要递归处理,因为存在多个赋值,并不清楚里面的赋值里有没有ref
赋值。type
以Literal
结尾的节点类型。registerBinding
: 顾名思义,就是将变量赋值的变量名和关键的几个api
绑定到一起ObjectPattern
[29]: 如果用了var let const
关键字但是却没有变量名,那么这个我们能猜到的就是es6
的解构。比如:const { a } = b;
ArrayPattern
[30]: 同上。walkObjectPattern
: 这个方法就不多说了,就是处理解构赋值的默认值这个问题。walkArrayPattern
: 同上。ok
,说了一大堆babel
的东西,我们来总结下walkDeclaration
这个方法。简单的说就是在分门别类。
梳理下逻辑(下面提到的ref
、reactive
、defineProps
、defineEmits
、withDefaults
均是vue3
的api
,赋值指的是const a = ref(0)
这样。)
enum、function、class
声明,如果是就直接绑定到bindings
中,至于这个bindings
是干啥的,简单的说就是准备setup
导出的家伙。bindings
中。if
条件判断)reactive
赋值给变量并且是赋值给一个const
变量,绑定并标记为setup-reactive-const
类型;reactive
赋值给变量,但不是const
变量,绑定并标记为setup-let
类型;defineProps
赋值给变量并且是const
变量,绑定并标记为setup-reactive-const
;defineEmits、withDefaults
赋值给变量并且是const
变量,绑定并标记为setup-const
;ref``api
(上面提到的那几个)并且是赋值给const
变量,绑定并标记为setup-const
;const
变量并且是用ref
赋值给变量,绑定并标记为setup-ref
;const
变量绑定并标记为setup-maybe-ref
;setup-let
;props
的解构,被skip
掉了,就不多说了,毕竟reactivity
被破坏掉了。来看下数据
今天主要是在分析非setup
语法糖的script block
。也知道了是如何处理import、export
以及收集各种变量等东西,然后给他们分门别类。最后将非setup script block
的迁移到setup
语法糖的script block
之前。
发布于 2022-11-24 09:18・IP 属地广东