const ast = compiler.parse(source, {
// there are no components at SFC parsing level
isNativeTag: () => true,
// preserve all whitespaces
isPreTag: () => true,
getTextMode: ({ tag, props }, parent) => {
// all top level elements except <template> are parsed as raw text
// containers
if (
(!parent && tag !== 'template') ||
// <template lang="xxx"> should also be treated as raw text
(tag === 'template' &&
props.some(
p =>
p.type === NodeTypes.ATTRIBUTE &&
p.name === 'lang' &&
p.value &&
p.value.content &&
p.value.content !== 'html'
))
) {
return TextModes.RAWTEXT
} else {
return TextModes.DATA
}
},
onError: e => {
errors.push(e)
}
})
接着遍历 ast 来处理 template、script、style
switch (node.tag) {
case 'template':
if (!descriptor.template) {
const templateBlock = (descriptor.template = createBlock(
node,
source,
false
) as SFCTemplateBlock)
templateBlock.ast = node
break
是创建一个 block 然后存放到 descriptor.template 内
case 'script':
const scriptBlock = createBlock(node, source, pad) as SFCScriptBlock
const isSetup = !!scriptBlock.attrs.setup
if (isSetup && !descriptor.scriptSetup) {
descriptor.scriptSetup = scriptBlock
break
}
if (!isSetup && !descriptor.script) {
descriptor.script = scriptBlock
break
}
errors.push(createDuplicateBlockError(node, isSetup))
break
也是创建一个 block 放到 descriptor.scriptSetup
case 'style':
const styleBlock = createBlock(node, source, pad) as SFCStyleBlock
descriptor.styles.push(styleBlock)
break
test('should expose top level declarations', () => {
const { content, bindings } = compile(`
<script setup>
import { x } from './x'
let a = 1
const b = 2
function c() {}
class d {}
</script>
<script>
import { xx } from './x'
let aa = 1
const bb = 2
function cc() {}
class dd {}
</script>
`)
expect(content).toMatch('return { aa, bb, cc, dd, a, b, c, d, xx, x }')
expect(bindings).toStrictEqual({
x: BindingTypes.SETUP_MAYBE_REF,
a: BindingTypes.SETUP_LET,
b: BindingTypes.SETUP_CONST,
c: BindingTypes.SETUP_CONST,
d: BindingTypes.SETUP_CONST,
xx: BindingTypes.SETUP_MAYBE_REF,
aa: BindingTypes.SETUP_LET,
bb: BindingTypes.SETUP_CONST,
cc: BindingTypes.SETUP_CONST,
dd: BindingTypes.SETUP_CONST
})
assertCode(content)
})
这里的 compile 实际是调用了 compileSFCScript
这里最终实现的就是把 script 代码编译成可以让 runtime 执行的js代码
import { x } from './x'\n \nexport default {\n setup(__props, { expose }) {\n expose()\n\n let a = 1\n const b = 2\n function c() {}\n class d {}\n \nreturn { aa, bb, cc, dd, a, b, c, d, xx, x }\n}\n\n}\n import { xx } from './x'\n let aa = 1\n const bb = 2\n function cc() {}\n class dd {}
接着看看调用的主流程
会调用compileScript,代码量很大,我们分拆这来看
export function compileScript(
sfc: SFCDescriptor,
options: SFCScriptCompileOptions
): SFCScriptBlock {
sfc 这个模块是由 vue-loader 来调用的,目的是解析 SFC文件
主入口是 parse
接着来分析一下
一开始的时候还是基于 compiler.parse 去生成 ast 对象
接着遍历 ast 来处理 template、script、style
是创建一个 block 然后存放到 descriptor.template 内
也是创建一个 block 放到 descriptor.scriptSetup
style 和上面不同的话,是可以有多个
最后是处理自定义的
都不是的话,那么就是 custom类型的blocks
最后是返回了 result 对象
这个东西就需要和 vue-loader 结合去看了
接着我们从单元测试看
然后我们看看 descriptor 长什么样子
compileTemplate
先看看是如何解析 template 的
先从单元测试看起
compile 就是调用的 compileTemplate
接着我们去把逻辑给拆分
是给用户做扩展用的接口,可以先忽略掉
接着是调用了 doCompileTemplate
继续拆分
这里依然是给用户做扩展的
最终会影响 nodeTransforms 的值
也就是说 不同的 transformAssetUrls 会有不同的 transform
下面是核心代码
这里的 compiler 是来自 compiler-dom ,执行 compile 开始编译
所以 template 就是收集一些特有的数据 ,然后给到 compiler.compile 进行编译,最后得到数据完事。
看看 template 如果是 pug 的话,是如何处理的
如果template 是 pug 的话,那么 vue3 会调用 consolidate 这个库来处理解析
consolidate 是个template 大杂烩,做了一个中间层
那么换个角度来讲的话,只要是 consolidate 支持的template ,vue3 的template 就会支持
compileScript
接着看看如何处理 script 的
还是先从单元测试入手
这里的 compile 实际是调用了 compileSFCScript
这里最终实现的就是把 script 代码编译成可以让 runtime 执行的js代码
接着看看调用的主流程
会调用compileScript,代码量很大,我们分拆这来看
先看看 input ,这里的 sfc 是通过 parse 过得到的对象,是已经把src也就是string代码编译成对象了
接着就是通过这个对象上面的数据信息来进行处理就可以了
在一开始的时候先收集数据,进行初始化
然后先处理的是 普通的 scirpt
处理普通的 script
先解析 script里面的代码,搞成 ast 对象
基于 ast.body 生成bindings
看看有没有开启 enableRefTransform,开启的话处理
完事就直接返回了,可以看到普通的 script 的处理过程是比较简单的
处理 script setup
处理 setup 代码就多了去了
先初始化后面需要用到的数据
总结
目标:compiler-sfc 是把 sfc 里面的string生成对应的用 js 表达的组件代码
基本的逻辑是先解析 SFC 文件,生成对应的对象。这个对象里面包含了所有 SFC 的信息
然后在把 template script style 分别交给各自的编译器来编译成对象
template 就是用的compiler-dom 来解析的
script 用的是 babel 来解析的
style 暂时还不知道 TODO
然后基于ast 对象就可以对代码做手术了,比如获取到某些信息,然后基于这些信息生成对应的 js 代码