前言
咱们每天写的vue代码都是写在vue文件中,可是浏览器却只认识html、css、js等文件类型。所以这个时分就需求一个东西将vue文件转换为浏览器能够认识的js文件,想必你第一时间就想到了webpack或者vite。可是webpack和vite自身是没有才能处理vue文件的,其实实践背后生效的是vue-loader和@vitejs/plugin-vue。本文以@vitejs/plugin-vue举例,经过debug的方式带你一步一步的搞清楚vue文件是怎样编译为js文件的,看不懂你来打我。
举个比如
这个是我的源代码App.vue文件:
<template>
<h1 class="msg">{{ msg }}</h1>
</template>
<script setup lang="ts">
import { ref } from "vue";
const msg = ref("hello word");
</script>
<style scoped>
.msg {
color: red;
font-weight: bold;
}
</style>
这个比如很简单,在setup中界说了msg变量,然后在template中将msg渲染出来。
下面这个是我从network中找到的编译后的js文件,现已精简过了:
import {
createElementBlock as _createElementBlock,
defineComponent as _defineComponent,
openBlock as _openBlock,
toDisplayString as _toDisplayString,
ref,
} from "/node_modules/.vite/deps/vue.js?v=23bfe016";
import "/src/App.vue?vue&type=style&index=0&scoped=7a7a37b1&lang.css";
const _sfc_main = _defineComponent({
__name: "App",
setup(__props, { expose: __expose }) {
__expose();
const msg = ref("hello word");
const __returned__ = { msg };
return __returned__;
},
});
const _hoisted_1 = { class: "msg" };
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return (
_openBlock(),
_createElementBlock(
"h1",
_hoisted_1,
_toDisplayString($setup.msg),
1
/* TEXT */
)
);
}
__sfc__.render = render;
export default _sfc_main;
编译后的js代码中咱们能够看到首要有三部分,想必你也猜到了这三部分刚好对应vue文件的那三块。
-
_sfc_main目标的setup方法对应vue文件中的<script setup lang="ts">模块。 -
_sfc_render函数对应vue文件中的<template>模块。 -
import "/src/App.vue?vue&type=style&index=0&scoped=7a7a37b1&lang.css";对应vue文件中的<style scoped>模块。
debug搞清楚怎样将vue文件编译为js文件
咱们应该都知道,前端代码运行环境首要有两个,node端和浏览器端,别离对应咱们了解的编译时和运行时。浏览器显着是不认识vue文件的,所以vue文件编译成js这一进程肯定不是在运行时的浏览器端。很显着这一进程是在编译时的node端。
要在node端打断点,咱们需求发动一个debug 终端。这儿以vscode举例,首先咱们需求打开终端,然后点击终端中的+号旁边的下拉箭头,在下拉中点击Javascript Debug Terminal就能够发动一个debug终端。

假设vue文件编译为js文件是一个毛线团,那么他的线头一定是vite.config.ts文件中运用@vitejs/plugin-vue的当地。经过这个线头开始debug咱们就能够整理清楚完好的作业流程。

vuePlugin函数
咱们给上方图片的vue函数打了一个断点,然后在debug终端上面履行yarn dev,咱们看到断点现已停留在了vue函数这儿。然后点击step into,断点走到了@vitejs/plugin-vue库中的一个vuePlugin函数中。咱们看到vuePlugin函数中的内容代码大概是这样的:
function vuePlugin(rawOptions = {}) {
const options = shallowRef({
compiler: null,
// 省掉...
});
return {
name: "vite:vue",
handleHotUpdate(ctx) {
// ...
},
config(config) {
// ..
},
configResolved(config) {
// ..
},
configureServer(server) {
// ..
},
buildStart() {
// ..
},
async resolveId(id) {
// ..
},
load(id, opt) {
// ..
},
transform(code, id, opt) {
// ..
}
};
}
@vitejs/plugin-vue是作为一个plugins插件在vite中运用,vuePlugin函数回来的目标中的buildStart、transform方法便是对应的插件钩子函数。vite会在对应的时分调用这些插件的钩子函数,比如当vite服务器发动时就会调用插件里边的buildStart等函数,当vite解析每个模块时就会调用transform等函数。更多vite钩子相关内容检查官网。
咱们这儿首要看buildStart和transform两个钩子函数,别离是服务器发动时调用和解析每个模块时调用。给这两个钩子函数打上断点。

然后点击Continue(F5),vite服务发动后就会走到buildStart钩子函数中打的断点。咱们能够看到buildStart钩子函数的代码是这样的:
buildStart() {
const compiler = options.value.compiler = options.value.compiler || resolveCompiler(options.value.root);
}
将鼠标放到options.value.compiler上面咱们看到此刻options.value.compiler的值为null,所以代码会走到resolveCompiler函数中,点击Step Into(F11)走到resolveCompiler函数中。看到resolveCompiler函数代码如下:
function resolveCompiler(root) {
const compiler = tryResolveCompiler(root) || tryResolveCompiler();
return compiler;
}
function tryResolveCompiler(root) {
const vueMeta = tryRequire("vue/package.json", root);
if (vueMeta && vueMeta.version.split(".")[0] >= 3) {
return tryRequire("vue/compiler-sfc", root);
}
}
在resolveCompiler函数中调用了tryResolveCompiler函数,在tryResolveCompiler函数中判别当时项目是否是vue3.x版本,然后将vue/compiler-sfc包回来。所以经过初始化后options.value.compiler的值便是vue的底层库vue/compiler-sfc,记住这个后边会用。
然后点击Continue(F5)放掉断点,在浏览器中打开对应的页面,比如:http://localhost:5173/ 。此刻vite将会编译这个页面要用到的一切文件,就会走到transform钩子函数断点中了。由于解析每个文件都会走到transform钩子函数中,可是咱们只重视App.vue文件是怎样解析的,所以为了便利咱们直接在transform函数中添加了下面这段代码,而且删掉了本来在transform钩子函数中打的断点,这样就只要解析到App.vue文件的时分才会走到断点中去。

经过debug咱们发现解析App.vue文件时transform函数实践便是履行了transformMain函数,至于transformStyle函数后边讲解析style的时分会讲:
transform(code, id, opt) {
const { filename, query } = parseVueRequest(id);
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 === "style") {
return transformStyle(
code,
descriptor,
Number(query.index || 0),
options.value,
this,
filename
);
}
}
}
transformMain函数
持续debug断点走进transformMain函数,发现transformMain函数中代码逻辑很明晰。依照次序别离是:
- 依据源代码code字符串调用
createDescriptor函数生成一个descriptor目标。 - 调用
genScriptCode函数传入第一步生成的descriptor目标将<script setup>模块编译为浏览器可履行的js代码。 - 调用
genTemplateCode函数传入第一步生成的descriptor目标将<template>模块编译为render函数。 - 调用
genStyleCode函数传入第一步生成的descriptor目标将<style scoped>模块编译为类似这样的import句子,import "/src/App.vue?vue&type=style&index=0&scoped=7a7a37b1&lang.css";。
createDescriptor函数
咱们先来看看createDescriptor函数,将断点走到createDescriptor(filename, code, options)这一行代码,能够看到传入的filename便是App.vue的文件路径,code便是App.vue中咱们写的源代码。

debug走进createDescriptor函数,看到createDescriptor函数的代码如下:
function createDescriptor(filename, source, { root, isProduction, sourceMap, compiler, template }, hmr = false) {
const { descriptor, errors } = compiler.parse(source, {
filename,
sourceMap,
templateParseOptions: template?.compilerOptions
});
const normalizedPath = slash(path.normalize(path.relative(root, filename)));
descriptor.id = getHash(normalizedPath + (isProduction ? source : ""));
return { descriptor, errors };
}
这个compiler是不是觉得有点了解?compiler是调用createDescriptor函数时传入的第三个参数解构而来,而第三个参数便是options。还记得咱们之前在vite发动时调用了buildStart钩子函数,然后将vue底层包vue/compiler-sfc赋值给options的compiler特点。那这儿的compiler.parse其实便是调用的vue/compiler-sfc包露出出来的parse函数,这是一个vue露出出来的底层的API,这篇文章咱们不会对底层API进行源码解析,经过检查parse函数的输入和输出根本就能够搞清楚parse函数的效果。下面这个是parse函数的类型界说:
export function parse(
source: string,
options: SFCParseOptions = {},
): SFCParseResult {}
从上面咱们能够看到parse函数接纳两个参数,第一个参数为vue文件的源代码,在咱们这儿便是App.vue中的code字符串,第二个参数是一些options选项。
咱们再来看看parse函数的回来值SFCParseResult,首要有类型为SFCDescriptor的descriptor特点需求重视。
export interface SFCParseResult {
descriptor: SFCDescriptor
errors: (CompilerError | SyntaxError)[]
}
export interface SFCDescriptor {
filename: string
source: string
template: SFCTemplateBlock | null
script: SFCScriptBlock | null
scriptSetup: SFCScriptBlock | null
styles: SFCStyleBlock[]
customBlocks: SFCBlock[]
cssVars: string[]
slotted: boolean
shouldForceReload: (prevImports: Record<string, ImportBinding>) => boolean
}
细心看看SFCDescriptor类型,其中的template特点便是App.vue文件对应的template标签中的内容,里边包含了由App.vue文件中的template模块编译成的AST笼统语法树和原始的template中的代码。

咱们再来看script和scriptSetup特点,由于vue文件中能够写多个script标签,scriptSetup对应的便是有setup的script标签,script对应的便是没有setup对应的script标签。咱们这个场景中只要scriptSetup特点,里边相同包含了App.vue中的script模块中的内容。

咱们再来看看styles特点,这儿的styles特点是一个数组,是由于咱们能够在vue文件中写多个style模块,里边相同包含了App.vue中的style模块中的内容。

所以这一步履行createDescriptor函数生成的descriptor目标中首要有三个特点,template特点包含了App.vue文件中的template模块code字符串和AST笼统语法树,scriptSetup特点包含了App.vue文件中的<script setup>模块的code字符串,styles特点包含了App.vue文件中<style>模块中的code字符串。createDescriptor函数的履行流程图如下:

genScriptCode函数
咱们再来看genScriptCode函数是怎样将<script setup>模块编译成可履行的js代码,相同将断点走到调用genScriptCode函数的当地,genScriptCode函数首要接纳咱们上一步生成的descriptor目标,调用genScriptCode函数后会将编译后的script模块代码赋值给scriptCode变量。
const { code: scriptCode, map: scriptMap } = await genScriptCode(
descriptor,
options,
pluginContext,
ssr,
customElement
);
将断点走到genScriptCode函数内部,在genScriptCode函数中首要便是这行代码: const script = resolveScript(descriptor, options, ssr, customElement);。将第一步生成的descriptor目标作为参数传给resolveScript函数,回来值便是编译后的js代码,genScriptCode函数的代码简化后如下:
async function genScriptCode(descriptor, options, pluginContext, ssr, customElement) {
let scriptCode = `const ${scriptIdentifier} = {}`;
const script = resolveScript(descriptor, options, ssr, customElement);
if (script) {
scriptCode = script.content;
map = script.map;
}
return {
code: scriptCode,
map
};
}
咱们持续将断点走到resolveScript函数内部,发现resolveScript中的代码其实也很简单,简化后的代码如下:
function resolveScript(descriptor, options, ssr, customElement) {
let resolved = null;
resolved = options.compiler.compileScript(descriptor, {
...options.script,
id: descriptor.id,
isProd: options.isProduction,
inlineTemplate: isUseInlineTemplate(descriptor, !options.devServer),
templateOptions: resolveTemplateCompilerOptions(descriptor, options, ssr),
sourceMap: options.sourceMap,
genDefaultAs: canInlineMain(descriptor, options) ? scriptIdentifier : void 0,
customElement
});
return resolved;
}
这儿的options.compiler咱们前面第一步的时分现已解说过了,options.compiler目标实践便是vue底层包vue/compiler-sfc露出的目标,这儿的options.compiler.compileScript()其实便是调用的vue/compiler-sfc包露出出来的compileScript函数,相同也是一个vue露出出来的底层的API,后边咱们的剖析defineOptions等文章时会去深入剖析compileScript函数,这篇文章咱们不会去读compileScript函数的源码。经过检查compileScript函数的输入和输出根本就能够搞清楚compileScript函数的效果。下面这个是compileScript函数的类型界说:
export function compileScript(
sfc: SFCDescriptor,
options: SFCScriptCompileOptions,
): SFCScriptBlock{}
这个函数的入参是一个SFCDescriptor目标,便是咱们第一步调用生成createDescriptor函数生成的descriptor目标,第二个参数是一些options选项。咱们再来看回来值SFCScriptBlock类型:
export interface SFCScriptBlock extends SFCBlock {
type: 'script'
setup?: string | boolean
bindings?: BindingMetadata
imports?: Record<string, ImportBinding>
scriptAst?: import('@babel/types').Statement[]
scriptSetupAst?: import('@babel/types').Statement[]
warnings?: string[]
/**
* Fully resolved dependency file paths (unix slashes) with imported types
* used in macros, used for HMR cache busting in @vitejs/plugin-vue and
* vue-loader.
*/
deps?: string[]
}
export interface SFCBlock {
type: string
content: string
attrs: Record<string, string | true>
loc: SourceLocation
map?: RawSourceMap
lang?: string
src?: string
}
回来值类型中首要有scriptAst、scriptSetupAst、content这三个特点,scriptAst为编译不带setup特点的script标签生成的AST笼统语法树。scriptSetupAst为编译带setup特点的script标签生成的AST笼统语法树,content为vue文件中的script模块编译后生成的浏览器可履行的js代码。下面这个是履行vue/compiler-sfc的compileScript函数回来结果:

持续将断点走回genScriptCode函数,现在逻辑就很明晰了。这儿的script目标便是调用vue/compiler-sfc的compileScript函数回来目标,scriptCode便是script目标的content特点 ,也便是将vue文件中的script模块经过编译后生成浏览器可直接履行的js代码code字符串。
async function genScriptCode(descriptor, options, pluginContext, ssr, customElement) {
let scriptCode = `const ${scriptIdentifier} = {}`;
const script = resolveScript(descriptor, options, ssr, customElement);
if (script) {
scriptCode = script.content;
map = script.map;
}
return {
code: scriptCode,
map
};
}
genTemplateCode函数
咱们再来看genTemplateCode函数是怎样将template模块编译成render函数的,相同将断点走到调用genTemplateCode函数的当地,genTemplateCode函数首要接纳咱们上一步生成的descriptor目标,调用genTemplateCode函数后会将编译后的template模块代码赋值给templateCode变量。
({ code: templateCode, map: templateMap } = await genTemplateCode(
descriptor,
options,
pluginContext,
ssr,
customElement
))
相同将断点走到genTemplateCode函数内部,在genTemplateCode函数中首要便是回来transformTemplateInMain函数的回来值,genTemplateCode函数的代码简化后如下:
async function genTemplateCode(descriptor, options, pluginContext, ssr, customElement) {
const template = descriptor.template;
return transformTemplateInMain(
template.content,
descriptor,
options,
pluginContext,
ssr,
customElement
);
}
咱们持续将断点走进transformTemplateInMain函数,发现这儿也首要是调用compile函数,代码如下:
function transformTemplateInMain(code, descriptor, options, pluginContext, ssr, customElement) {
const result = compile(
code,
descriptor,
options,
pluginContext,
ssr,
customElement
);
return {
...result,
code: result.code.replace(
/nexport (function|const) (render|ssrRender)/,
"n$1 _sfc_$2"
)
};
}
同理将断点走进到compile函数内部,咱们看到compile函数的代码是下面这样的:
function compile(code, descriptor, options, pluginContext, ssr, customElement) {
const result = options.compiler.compileTemplate({
...resolveTemplateCompilerOptions(descriptor, options, ssr),
source: code
});
return result;
}
相同这儿也用到了options.compiler,调用options.compiler.compileTemplate()其实便是调用的vue/compiler-sfc包露出出来的compileTemplate函数,这也是一个vue露出出来的底层的API。不过这儿和前面不同的是compileTemplate接纳的不是descriptor目标,而是一个SFCTemplateCompileOptions类型的目标,所以这儿需求调用resolveTemplateCompilerOptions函数将参数转换成SFCTemplateCompileOptions类型的目标。这篇文章咱们不会对底层API进行解析。经过检查compileTemplate函数的输入和输出根本就能够搞清楚compileTemplate函数的效果。下面这个是compileTemplate函数的类型界说:
export function compileTemplate(
options: SFCTemplateCompileOptions,
): SFCTemplateCompileResults {}
入参options首要便是需求编译的template中的源代码和对应的AST笼统语法树。咱们来看看回来值SFCTemplateCompileResults,这儿面的code便是编译后的render函数字符串。
export interface SFCTemplateCompileResults {
code: string
ast?: RootNode
preamble?: string
source: string
tips: string[]
errors: (string | CompilerError)[]
map?: RawSourceMap
}
genStyleCode函数
咱们再来看最终一个genStyleCode函数,相同将断点走到调用genStyleCode的当地。一样的接纳descriptor目标。代码如下:
const stylesCode = await genStyleCode(
descriptor,
pluginContext,
customElement,
attachedProps
);
咱们将断点走进genStyleCode函数内部,发现和前面genScriptCode和genTemplateCode函数有点不一样,下面这个是我简化后的genStyleCode函数代码:
async function genStyleCode(descriptor, pluginContext, customElement, attachedProps) {
let stylesCode = ``;
if (descriptor.styles.length) {
for (let i = 0; i < descriptor.styles.length; i++) {
const style = descriptor.styles[i];
const src = style.src || descriptor.filename;
const attrsQuery = attrsToQuery(style.attrs, "css");
const srcQuery = style.src ? style.scoped ? `&src=${descriptor.id}` : "&src=true" : "";
const directQuery = customElement ? `&inline` : ``;
const scopedQuery = style.scoped ? `&scoped=${descriptor.id}` : ``;
const query = `?vue&type=style&index=${i}${srcQuery}${directQuery}${scopedQuery}`;
const styleRequest = src + query + attrsQuery;
stylesCode += `
import ${JSON.stringify(styleRequest)}`;
}
}
return stylesCode;
}
咱们前面讲过由于vue文件中可能会有多个style标签,所以descriptor目标的styles特点是一个数组。遍历descriptor.styles数组,咱们发现for循环内全部都是一堆赋值操作,没有调用vue/compiler-sfc包露出出来的任何API。将断点走到 return stylesCode;,看看stylesCode到底是什么东西?

stylesCode居然变成了一条import句子,而且import的仍是当时App.vue文件,仅仅多了几个query别离是:vue、type、index、scoped、lang。再来回忆一下前面讲的@vitejs/plugin-vue的transform钩子函数,当vite解析每个模块时就会调用transform等函数。所以当代码运行到这行import句子的时分会再次走到transform钩子函数中。咱们再来看看transform钩子函数的代码:
transform(code, id, opt) {
const { filename, query } = parseVueRequest(id);
if (!query.vue) {
// 省掉
} else {
const descriptor = query.src ? getSrcDescriptor(filename, query) || getTempSrcDescriptor(filename, query) : getDescriptor(filename, options.value);
if (query.type === "style") {
return transformStyle(
code,
descriptor,
Number(query.index || 0),
options.value,
this,
filename
);
}
}
}
当query中有vue字段,而且query中type字段值为style时就会履行transformStyle函数,咱们给transformStyle函数打个断点。当履行上面那条import句子时就会走到断点中,咱们进到transformStyle中看看。
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
}
}
} : {}
});
return {
code: result.code,
map
};
}
transformStyle函数的实现咱们看着就很了解了,和前面处理template和script一样都是调用的vue/compiler-sfc包露出出来的compileStyleAsync函数,这也是一个vue露出出来的底层的API。相同咱们不会对底层API进行解析。经过检查compileStyleAsync函数的输入和输出根本就能够搞清楚compileStyleAsync函数的效果。
export function compileStyleAsync(
options: SFCAsyncStyleCompileOptions,
): Promise<SFCStyleCompileResults> {}
咱们先来看看SFCAsyncStyleCompileOptions入参:
interface SFCAsyncStyleCompileOptions extends SFCStyleCompileOptions {
isAsync?: boolean
modules?: boolean
modulesOptions?: CSSModulesOptions
}
interface SFCStyleCompileOptions {
source: string
filename: string
id: string
scoped?: boolean
trim?: boolean
isProd?: boolean
inMap?: RawSourceMap
preprocessLang?: PreprocessLang
preprocessOptions?: any
preprocessCustomRequire?: (id: string) => any
postcssOptions?: any
postcssPlugins?: any[]
map?: RawSourceMap
}
入参首要重视几个字段,source字段为style标签中的css原始代码。scoped字段为style标签中是否有scoped attribute。id字段为咱们在调查 DOM 结构时看到的 data-v-xxxxx。这个是debug时入参截图:

再来看看回来值SFCStyleCompileResults目标,首要便是code特点,这个是经过编译后的css字符串,现已加上了data-v-xxxxx。
interface SFCStyleCompileResults {
code: string
map: RawSourceMap | undefined
rawResult: Result | LazyResult | undefined
errors: Error[]
modules?: Record<string, string>
dependencies: Set<string>
}
这个是debug时compileStyleAsync函数回来值的截图:

transformMain函数简化后的代码
现在咱们能够来看transformMain函数简化后的代码:
async function transformMain(code, filename, options, pluginContext, ssr, customElement) {
const { descriptor, errors } = createDescriptor(filename, code, options);
const { code: scriptCode, map: scriptMap } = await genScriptCode(
descriptor,
options,
pluginContext,
ssr,
customElement
);
let templateCode = "";
({ code: templateCode, map: templateMap } = await genTemplateCode(
descriptor,
options,
pluginContext,
ssr,
customElement
));
const stylesCode = await genStyleCode(
descriptor,
pluginContext,
customElement,
attachedProps
);
const output = [
scriptCode,
templateCode,
stylesCode
];
let resolvedCode = output.join("n");
return {
code: resolvedCode,
map: resolvedMap || {
mappings: ""
},
meta: {
vite: {
lang: descriptor.script?.lang || descriptor.scriptSetup?.lang || "js"
}
}
};
}
transformMain函数中的代码履行主流程,其实便是对应了一个vue文件编译成js文件的流程。
首先调用createDescriptor函数将一个vue文件解析为一个descriptor目标。
然后以descriptor目标为参数调用genScriptCode函数,将vue文件中的<script>模块代码编译成浏览器可履行的js代码code字符串,赋值给scriptCode变量。
接着以descriptor目标为参数调用genTemplateCode函数,将vue文件中的<template>模块代码编译成render函数code字符串,赋值给templateCode变量。
然后以descriptor目标为参数调用genStyleCode函数,将vue文件中的<style>模块代码编译成了import句子code字符串,比如:import "/src/App.vue?vue&type=style&index=0&scoped=7a7a37b1&lang.css";,赋值给stylesCode变量。
然后将scriptCode、templateCode、stylesCode运用换行符n拼接起来得到resolvedCode,这个resolvedCode便是一个vue文件编译成js文件的代码code字符串。这个是debug时resolvedCode变量值的截图:

总结
这篇文章经过debug的方式一步一步的带你了解vue文件编译成js文件的完好流程,下面是一个完好的流程图。假如文字太小看不清,能够将图片保存下来或者扩大看:

@vitejs/plugin-vue-jsx库中有个叫transform的钩子函数,每当vite加载模块的时分就会触发这个钩子函数。所以当import一个vue文件的时分,就会走到@vitejs/plugin-vue-jsx中的transform钩子函数中,在transform钩子函数中首要调用了transformMain函数。
第一次解析这个vue文件时,在transform钩子函数中首要调用了transformMain函数。在transformMain函数中首要调用了4个函数,别离是:createDescriptor、genScriptCode、genTemplateCode、genStyleCode。
createDescriptor接纳的参数为当时vue文件代码code字符串,回来值为一个descriptor目标。目标中首要有四个特点template、scriptSetup、script、styles。
-
descriptor.template.ast便是由vue文件中的template模块生成的AST笼统语法树。 -
descriptor.template.content便是vue文件中的template模块的代码字符串。 -
scriptSetup和script的区别是别离对应的是vue文件中有setup特点的<script>模块和无setup特点的<script>模块。descriptor.scriptSetup.content便是vue文件中的<script setup>模块的代码字符串。
genScriptCode函数为底层调用vue/compiler-sfc的compileScript函数,依据第一步的descriptor目标将vue文件的<script setup>模块转换为浏览器可直接履行的js代码。
genTemplateCode函数为底层调用vue/compiler-sfc的compileTemplate函数,依据第一步的descriptor目标将vue文件的<template>模块转换为render函数。
genStyleCode函数为将vue文件的style模块转换为import "/src/App.vue?vue&type=style&index=0&scoped=7a7a37b1&lang.css";样子的import句子。
然后运用换行符n将genScriptCode函数、genTemplateCode函数、genStyleCode函数的回来值拼接起来赋值给变量resolvedCode,这个resolvedCode便是vue文件编译成js文件的code字符串。
当浏览器履行到import "/src/App.vue?vue&type=style&index=0&scoped=7a7a37b1&lang.css";句子时,触发了加载模块操作,再次触发了@vitejs/plugin-vue-jsx中的transform钩子函数。此刻由于有了type=style的query,所以在transform函数中会履行transformStyle函数,在transformStyle函数中相同也是调用vue/compiler-sfc的compileStyleAsync函数,依据第一步的descriptor目标将vue文件的<style>模块转换为编译后的css代码code字符串,至此编译style部分也讲完了。
重视大众号:前端欧阳,解锁我更多vue干货文章,而且能够免费向我咨询vue相关问题。




