前言#
之前我们分析@vue/compiler-sfc处理template的时候发现核心是调用@vue/compiler-dom[1]这个包来处理的,所以我们就接下来就来分析这个包。
坏蛋Dan:vue/compiler-sfc源码分析学习--汇总
入口#
我们先回到@vue/compiler-sfc/src/compileTemplate.ts文件中
let { code, ast, preamble, map } = compiler.compile(source, {
mode: 'module',
prefixIdentifiers: true,
hoistStatic: true,
cacheHandlers: true,
ssrCssVars:
ssr && ssrCssVars && ssrCssVars.length
? genCssVarsFromList(ssrCssVars, shortId, isProd, true)
: '',
scopeId: scoped ? longId : undefined,
slotted,
sourceMap: true,
...compilerOptions,
nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []),
filename,
onError: e => errors.push(e),
onWarn: w => warnings.push(w)
})也就是说入口是compile这个方法。
parse#
在开始分析compile方法之前,这里有个问题需要分析,那就是@vue/compiler-sfc这个包中的parse.ts中我们也有用到@vue/compiler-dom这个包,但是我们是用的parse方法,在这里有什么作用呢?
其实是把整个sfc文件转换成了ast(只有template里全转了,script、style、custom这些都只是处理了外层也就是<script>这些副作用标签,当然你让它转它也转不了啊,专业不对口) 。
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)
}
})这里会把template标签(并非所有情况)、script、style这些标签标记为RAWTEXT,也就是原始文本,这些tag是会被忽略掉的。
所以其实这里的template的ast并没有被使用,而后compileTemplate还是基于source也就是源码来转换的。
compile#
export function compile(
template: string,
options: CompilerOptions = {}
): CodegenResult {
return baseCompile(
template,
extend({}, parserOptions, options, {
nodeTransforms: [
// ignore <script> and <tag>
// this is not put inside DOMNodeTransforms because that list is used
// by compiler-ssr to generate vnode fallback branches
ignoreSideEffectTags,
...DOMNodeTransforms,
...(options.nodeTransforms || [])
],
directiveTransforms: extend(
{},
DOMDirectiveTransforms,
options.directiveTransforms || {}
),
transformHoist: __BROWSER__ ? null : stringifyStatic
})
)
}没想到吧。。。最核心的把template转换为ast的代码不在这,在@vue/compiler-core[2]里的baseCompile方法。
但有些东西还是放到了这个包中,比如v-on、v-html等指令的处理、特殊标签比如transition以及静态节点提升都在这个包中,所以还是有学习的必要的。
所以我们准备分三部分来分析
- 特殊标签以及副作用标签等,简单的说就是节点转换
node transform。 - 解析指令
directive transform。 - 字符串化
hoist节点。
选择测试单元#
差点把这个忘了,由于这里只有一个简陋的测试单元。

所以我们还是把之前的那个team.vue引进来吧,毕竟花花绿绿有代码提示写起来才舒服。
在packages/compiler-dom/__tests__/index.spec.ts文件中
import { readFileSync } from 'fs';
import { resolve } from 'path';
import { parse } from '../../compiler-sfc/src/parse'
const teamStr = readFileSync(resolve(__dirname, '../../compiler-sfc/__tests__/team/team.vue'), 'utf-8');
test('test_compile', () => {
const { descriptor } = parse(teamStr);
expect(compile(descriptor.template?.content as string)).toBe(teamStr);
})我这直接使用parse出来的content字符串传入。
这样就ok啦。
噢,别忘了带上options,毕竟有些东西是在@vue/compiler-sfc里面的。
这样就未免太麻烦了!!!!!
实际上我们完全没有必要重写一个测试单元,我们可以直接使用compileTemplate.spec.ts里的!
是的,你应该猜到如何操作了。
让我们回到@vue/compiler-sfc包的compileTemplate.ts文件中

这样就可以啦~
试了下,也是可以直接进入到@vue/compiler-dom这个包中的,多包管理(monorepo)真得劲~

最后#
可能目前的分析会有些问题,如果后面发现问题会回来补充
补充的点1:这个包中只包含部分指令
补充的点2:原本是起名静态节点提升的,但是compiler-dom这个包中仅是将节点stringify并替换原来的hoist节点而已,所以改成字符串化hoist节点。
参考#
- ^@vue/compiler-dom https://github.com/vuejs/core/tree/main/packages/compiler-dom
- ^@vue/compiler-core https://github.com/vuejs/core/tree/main/packages/compiler-core
编辑于 2022-12-27 12:36・IP 属地广东
