vue-compiler-sfc主要是用来解析SFC组件,我们都知道,一个SFC(*.vue)文件三大要素是template、script、style,vue-compiler-sfc就是负责解析这三大要素。从源码src目录下,每个文件的命名大致就可以看出来各个文件的作用,我先从 compiler-sfc 的 index.ts 开始。
(最近看源码深有感触的是,官文只是在说要怎么做,看了源码才知道为什么要这么做,并且还可以怎么做……,再就是测试用例真的很有用)
index.ts 主要是导出了其他文件的功能。
1.parse.ts:解析三大要素
三大要素template、script、style的类型定义:
想要看看具体解析后的sfc里面descriptor的结构,可以去运行vue-next/packages/compiler-sfc/__tests__ 下面个文件的测试用例。
我们运行一个单测,看看parse script的结果:
test('should expose top level declarations', () => { const script = compile(` <script setup> import { x } from './x' let a = 1 const b = 2 function c() {} class d {} </script> `) assertCode(script.content) console.log('----------------------') console.log(script) expect(script.content).toMatch('return { a, b, c, d, x }') }) // 结果 { type: 'script', // 可以看到,编译后的content,帮忙 return 了所有变量 content: "import { x } from './x'\n" + ' \n' + 'export default {\n' + ' expose: [],\n' + ' setup(__props) {\n' + '\n' + ' let a = 1\n' + ' const b = 2\n' + ' function c() {}\n' + ' class d {}\n' + ' \n' + 'return { a, b, c, d, x }\n' + '}\n' + '\n' + '}', loc: { source: '\n' + " import { x } from './x'\n" + ' let a = 1\n' + ' const b = 2\n' + ' function c() {}\n' + ' class d {}\n' + ' ', start: { column: 21, line: 2, offset: 21 }, end: { column: 7, line: 8, offset: 131 } }, attrs: { setup: true }, setup: true, bindings: { x: 'setup-maybe-ref', a: 'setup-let', b: 'setup-const', c: 'setup-const', d: 'setup-const' }, map: SourceMap { version: 3, file: null, sources: [ 'anonymous.vue' ], sourcesContent: [ '\n' + ' <script setup>\n' + " import { x } from './x'\n" + ' let a = 1\n' + ' const b = 2\n' + ' function c() {}\n' + ' class d {}\n' + ' </script>\n' + ' ' ], names: [], mappings: 'AAEM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7B,CAAC,CAAC,CAAC,CAAC,CAAC;;;;AAFe;AACpB,CAAC,CAAC,CAAC,CAAC,CAAC,CACC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACf,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChB,CAAC,CAAC,CAAC,CAAC,CAAC;;;;' }, scriptAst: undefined, scriptSetupAst: [ Node { type: 'ImportDeclaration', start: 7, end: 30, loc: [SourceLocation], range: undefined, leadingComments: undefined, trailingComments: undefined, innerComments: undefined, extra: undefined, specifiers: [Array], source: [Node] }, Node { type: 'VariableDeclaration', start: 37, end: 46, loc: [SourceLocation], range: undefined, leadingComments: undefined, trailingComments: undefined, innerComments: undefined, extra: undefined, declarations: [Array], kind: 'let' }, Node { type: 'VariableDeclaration', start: 53, end: 64, loc: [SourceLocation], range: undefined, leadingComments: undefined, trailingComments: undefined, innerComments: undefined, extra: undefined, declarations: [Array], kind: 'const' }, Node { type: 'FunctionDeclaration', start: 71, end: 86, loc: [SourceLocation], range: undefined, leadingComments: undefined, trailingComments: undefined, innerComments: undefined, extra: undefined, id: [Node], generator: false, async: false, params: [], body: [Node] }, Node { type: 'ClassDeclaration', start: 93, end: 103, loc: [SourceLocation], range: undefined, leadingComments: undefined, trailingComments: undefined, innerComments: undefined, extra: undefined, id: [Node], superClass: null, body: [Node] } ] }
vue3的sfc的style模块多了一个 parseCssVars 的功能,支持在css中使用响应式的变量,用过v-bind去绑定。
示例见:vue-next/packages/compiler-sfc/__tests__/cssVars.spec.ts
2.compileStyle.ts 编译style模块
vue3 内置了对各种css语法的预处理器,详情见:vue-next/packages/compiler-sfc/src/stylePreprocessors.ts,主要包括:
export type PreprocessLang = 'less' | 'sass' | 'scss' | 'styl' | 'stylus'
3.createVNode
用法:
const message = { setup() { const num = ref(1) return { num } }, template: `<div> <div>{{num}}</div> <div>这是一个弹窗</div> </div>` } // 初始化组件生成vdom const vm = createVNode(message) // 创建容器,也可以用已经存在的 const container = document.createElement('div') //render通过patch 变成dom render(vm, container) // 弹窗挂到任何你想去的地方 document.body.appendChild(container.firstElementChild)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通