vite项目vue的编译原理

vite项目vue的编译原理
Dans Roh如何从Vue文件编译成JS文件
在vite.config.ts中会加载一个plugin-vue插件,这个插件会注册一些钩子。
1 | return { |
这里需要重点注意buildStart和transform钩子。
执行yarn dev的时候buildStart钩子会执行
打开网站页面的时候,加载模块的时候,transform钩子会执行
- 在transform中,会执行一个transformMain的方法
关于transformMain
- 首先会通过createDescriptor创建descriptor
1
2
3
4
5
6
7
8
9
10
11 function createDescriptor (filename, source, { root, isProduction, sourceMap, compiler, template }, hmr = false) {
const { descriptor, errors } = compiler.parse(source, {
filename,
sourceMap,
templateParseOptions: template?.compilerOptions
});
const normalizedPath = normalizePath$1(path.relative(root, filename));
descriptor.id = getHash(normalizedPath + (isProduction ? source : ""));
(hmr ? hmrCache : cache).set(filename, descriptor);
return { descriptor, errors };
}上面是createDescriptor,可以看到它接受一个参数compiler,而这个就是在buildStart时,注册的vue核心编译方法。通过compiler.parse()将.vue文件,编译为静态语法树(AST)。
传入的source是文件字符串,返回的是AST以及其他属性
- 之后会通过genScriptCode、genTemplateCode、genStyleCode分别处理生成js文件代码,render函数,css文件代码。其中genScriptCode、genTemplateCode都是通过compiler的一些核心方法进行转化的。
以下是render函数
值的一提的是 stylesCode的值
1
2 '
import "/Users/dansroh/Documents/study/vue-core-study/src/App.vue?vue&type=style&index=0&scoped=7a7a37b1&lang.css" '可以看到,stylesCode的值并不是css代码。而是通过imort 导入的一条条语句。
为什么会是这样呢?
原因需要回到transform中。
首先明确一点,每次加载模块,都会执行一次transform。
当stylesCode编译完成,执行import的时候,就会重新回到transform。
以下是transform完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 transform (code, id, opt) {
const ssr = opt?.ssr === true;
const { filename, query } = parseVueRequest(id);
if (query.raw || query.url) {
return;
}
if (!filter.value(filename) && !query.vue) {
return;
}
if (!query.vue) {
return transformMain(
code,
filename,
options.value,
this,
ssr,
customElementFilter.value(filename)
);
} else {
const descriptor = query.src ? getSrcDescriptor(filename, query) || getTempSrcDescriptor(filename, query) : getDescriptor(filename, options.value);
if (query.type === "template") {
return transformTemplateAsModule(
code,
descriptor,
options.value,
this,
ssr,
customElementFilter.value(filename)
);
} else if (query.type === "style") {
return transformStyle(
code,
descriptor,
Number(query.index || 0),
options.value,
this,
filename
);
}
}
}可以看到在执行过程中,有通过query.vue和query.type来判断执行的方法。
所以样式文件最终会执行到transformStyle方法中。我们再来看transformStyle内部实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 async function transformStyle (code, descriptor, index, options, pluginContext, filename) {
const block = descriptor.styles[ index ];
const result = await options.compiler.compileStyleAsync({
...options.style,
filename: descriptor.filename,
id: `data-v-${descriptor.id}`,
isProd: options.isProduction,
source: code,
scoped: block.scoped,
...options.cssDevSourcemap ? {
postcssOptions: {
map: {
from: filename,
inline: false,
annotation: false
}
}
} : {}
});
if (result.errors.length) {
result.errors.forEach((error) => {
if (error.line && error.column) {
error.loc = {
file: descriptor.filename,
line: error.line + block.loc.start.line,
column: error.column
};
}
pluginContext.error(error);
});
return null;
}
const map = result.map ? await formatPostcssSourceMap(
// version property of result.map is declared as string
// but actually it is a number
result.map,
filename
) : { mappings: "" };
return {
code: result.code,
map
};
}可以看到此时,id以及被绑定为data-v。用于scope style的属性选择器。
Vue 文件编译流程总结
1. plugin-vue 插件核心流程
Vite 通过 plugin-vue 插件来处理 .vue 文件的编译,主要涉及两个关键钩子:
buildStart
: 服务启动时执行,初始化 Vue 编译器transform
: 模块加载时执行,负责代码转换
2. 编译主要步骤
2.1 解析阶段 (Parse)
- 通过
createDescriptor
方法将 .vue 文件解析成 AST - 使用 compiler.parse() 进行语法分析
- 生成包含 template、script、style 等信息的描述符
2.2 转换阶段 (Transform)
分别处理三个主要部分:
genScriptCode
: 生成 JS 代码genTemplateCode
: 生成 render 函数genStyleCode
: 处理样式代码
2.3 样式处理的特殊性
- 样式代码不会直接编译,而是通过 import 语句导入
- 当执行 import 时会触发新的 transform 钩子
- 通过 transformStyle 方法处理样式:
- 添加 scoped 样式的唯一标识 (data-v-xxx)
- 编译预处理器语法
- 生成最终的 CSS 代码
3. 关键特性
- 模块化处理:每个部分(template/script/style)独立处理
- 热更新支持:通过 handleHotUpdate 钩子实现
- Scoped CSS:自动添加唯一标识符确保样式隔离
4. 编译结果
- JS:包含组件逻辑和 render 函数
- CSS:经过处理的样式代码,支持 scoped
- Template:转换为 render 函数的模板代码
评论
匿名评论隐私政策
✅ 你无需删除空行,直接评论以获取最佳展示效果