删除了不需要的目录,然后针对需要编译的文件调用了compileFile方法:
// varlet-cli/src/compiler/compileModule.ts
export async function compileFile(file: string) {
isSFC(file) && (await compileSFC(file))// 编译vue文件
isScript(file) && (await compileScriptFile(file))// 编译js文件
isLess(file) && (await compileLess(file))// 编译less文件
isDir(file) && (await compileDir(file))// 如果是目录则进行递归
}
分别处理三种文件,让我们一一来看。
编译Vue单文件
// varlet-cli/src/compiler/compileSFC.ts
import { parse } from '@vue/compiler-sfc'
export async function compileSFC(sfc: string) {
// 读取Vue单文件内容
const sources: string = await readFile(sfc, 'utf-8')
// 使用@vue/compiler-sfc包解析单文件
const { descriptor } = parse(sources, { sourceMap: false })
// 取出单文件的每部分内容
const { script, scriptSetup, template, styles } = descriptor
// Varlet暂时不支持setup语法
if (scriptSetup) {
logger.warning(
`
Varlet Cli does not support compiling script setup syntax
The error in ${sfc}`
)
return
}
// ...
}
使用@vue/compiler-sfc包来解析Vue单文件,parse方法可以解析出Vue单文件中的各个块,针对各个块,@vue/compiler-sfc包都提供了相应的编译方法,后续都会涉及到。
// varlet-cli/src/compiler/compileSFC.ts
import hash from 'hash-sum'
export async function compileSFC(sfc: string) {
// ...
// scoped
// 检查是否存在scoped作用域的样式块
const hasScope = styles.some((style) => style.scoped)
// 将单文件的内容进行hash生成id
const id = hash(sources)
// 生成样式的scopeId
const scopeId = hasScope ? `data-v-${id}` : ''
// ...
}
这一步主要是检查style块是否存在作用域块,存在的话会生成一个作用域id,作为css的作用域,防止和其他样式冲突,这两个id相关的编译方法需要用到。
// varlet-cli/src/compiler/compileSFC.ts
import { compileTemplate } from '@vue/compiler-sfc'
export async function compileSFC(sfc: string) {
// ...
if (script) {
// template
// 编译模板为渲染函数
const render =
template &&
compileTemplate({
id,
source: template.content,
filename: sfc,
compilerOptions: {
scopeId,
},
})
// 注入render函数
let { content } = script
if (render) {
const { code } = render
content = injectRender(content, code)
}
// ...
}
}
使用@vue/compiler-sfc包的compileTemplate方法将解析出的模板部分编译为渲染函数,然后调用injectRender方法将渲染函数注入到script中:
// varlet-cli/src/compiler/compileSFC.ts
const NORMAL_EXPORT_START_RE = /exports+defaults+{/
const DEFINE_EXPORT_START_RE = /exports+defaults+defineComponents*(s*{/
export function injectRender(script: string, render: string): string {
if (DEFINE_EXPORT_START_RE.test(script.trim())) {
return script.trim().replace(
DEFINE_EXPORT_START_RE,
`${render}
export default defineComponent({
render,
`
)
}
if (NORMAL_EXPORT_START_RE.test(script.trim())) {
return script.trim().replace(
NORMAL_EXPORT_START_RE,
`${render}
export default {
render,
`
)
}
return script
}
兼容两种导出方式,以一个小例子来看一下,比如生成的渲染函数为:
export function render(_ctx, _cache) {
// ...
}
script的内容为:
export default defineComponent({
name: 'VarButton',
// ...
})
注入render后script的内容变成了:
export function render(_ctx, _cache) {
// ...
}
export default defineComponent({
render,
name: 'VarButton',
/// ...
})
其实就是把渲染函数的内容和script的内容合并了,script其实就是组件的选项对象,所以同时也把组件的渲染函数添加到组件对象上。