解析vue-simple-compiler
import fs from 'fs';
import { compileTemplate } from 'vue/compiler-sfc'
import { compile } from 'vue-simple-compiler'
fs.readFile('index.vue', (err, data) => {
const {js, css} = compile(data.toString(), {
filename: 'foo.vue',
autoImportCss: true,
autoResolveImports: true,
isProd: true
})
fs.writeFile('index2.js', js.code, () => {})
})
开始打断点,分析compile方法做了什么。
进入到compile方法,首先出现的是一个这个东西。
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
这就是一个给__createBinding
赋值的操作,现在断点,走到这来了,应该是调用了__createBinding
这个函数。我们现在看一下,__createBinding
是在哪,被谁调用的。
我看了一下,下面讲解的,是如何使用模块化的东西,不是今天的重点,我就不再探究了。
接下来,直接上正式代码。
const compile = (source, options) => {
const context = (0, context_1.createContext)(source, options);
// get the code structure
const { descriptor, errors: mainCompilerErrors } = (0, compiler_sfc_1.parse)(source, {
filename: context.filename,
});
if (mainCompilerErrors.length) {
return getErrorResult(mainCompilerErrors, context.destFilename);
}
// get the features
(0, context_1.resolveFeatures)(descriptor, context);
const { result: scriptResult, errors: scriptErrors } = (0, script_1.resolveScript)(descriptor, context);
const { result: templateResult, errors: templateErrors } = (0, template_1.resolveTemplate)(descriptor, context);
const { files: cssFiles, importList: cssImportList, errors: styleErrors, } = (0, style_1.resolveStyles)(descriptor, context);
const errors = [
...mainCompilerErrors,
...(scriptErrors ?? []),
...(templateErrors ?? []),
...(styleErrors ?? []),
];
if (errors.length ||
!scriptResult ||
!templateResult ||
!cssFiles ||
!cssImportList) {
return getErrorResult(errors, context.destFilename);
}
// No source map update technically.
const jsCode = context.options?.autoResolveImports
? (0, transform_1.resolveImports)(scriptResult.code, context.options)
: scriptResult.code;
const initialSourceMap = {
version: '3',
file: context.destFilename,
sources: [context.filename],
names: [],
mappings: '',
sourcesContent: [source],
};
// assemble the final code
const finalTransformedResult = (0, map_1.bundleSourceMap)([
{ code: cssImportList.join('\n'), sourceMap: initialSourceMap },
{ code: jsCode, sourceMap: scriptResult.sourceMap },
{ code: templateResult.code, sourceMap: templateResult.sourceMap },
{ code: context.addedCodeList.join('\n') },
{
code: context.addedProps
.map(([key, value]) => `${constants_1.COMP_ID}.${key} = ${value}`)
.join('\n'),
},
{ code: `export default ${constants_1.COMP_ID}` },
]);
return {
js: {
filename: context.destFilename,
code: finalTransformedResult.code,
sourceMap: finalTransformedResult.sourceMap,
},
css: cssFiles,
externalJs: context.externalJsList,
externalCss: context.externalCssList,
errors: [],
};
};
这段代码代码有点长,我们慢慢看。但是可以明确的就是,我们在代码里面调用的compile方法,就是这个地方定义的。
const context = (0, context_1.createContext)(source, options);
第一行,调用了一个函数,叫做createContext
,看名字,有点像使用状态机的方式去处理我给到库的代码的。很明显,这个函数会创建一个context,具体这个context长啥样,我们来看看。
{
isProd: true,
hmr: false,
root: "@anonymous",
filename: "foo.vue",
fullpath: "@anonymous\\foo.vue",
id: "3f328616",
destFilename: "foo.vue.js",
options: {
filename: "foo.vue",
autoImportCss: true,
autoResolveImports: true,
isProd: true,
},
features: {
},
addedProps: [
],
addedCodeList: [
],
externalJsList: [
],
externalCssList: [
],
bindingMetadata: undefined,
}
看到这,从属性看不出来,他是否使用了状态机,但是我猜,大概率是没有使用状态机的,但是至于他为什么要定义一个context,这个地方我很疑惑,对于业务来说,这个地方并不重要。但是,从算法角度,这个地方究竟有什么用,我很好奇。
那就带着你们研究一下吧。
我看了一下,compile函数中,出现了17次context,要么创建时被赋值,要么是作为参数传给一个函数,要么作为结果的一部分返回回来。所以我猜想,他的作用应该是存储编译的参数和编译的部分结果。
那么,我要验证一下,所以,我会在每次context作为函数入参时,在函数调用后,看看context参数有什么变化从而了解这个context里面发生了哪些变化,从而了解他的作用。
我一路跟了下来,context没有任何变化,还是那些值。所以,他应该就是一个编译时,提供参数的作用。
OK,这一行就先到这里了。
下面看,
const { descriptor, errors: mainCompilerErrors } = (0, compiler_sfc_1.parse)(source, {
filename: context.filename,
});
这一行代码,作者写了注释,就是// get the code structure
。获取代码结构的。所谓代码结构,是什么东西?这个需要具体看一下parse
函数的作用。
function parse$2(source, options = {}) {
const sourceKey = genCacheKey(source, options);
const cache = parseCache$1.get(sourceKey);
if (cache) {
return cache;
}
const {
sourceMap = true,
filename = DEFAULT_FILENAME,
sourceRoot = "",
pad = false,
ignoreEmpty = true,
compiler = CompilerDOM__namespace,
templateParseOptions = {},
parseExpressions = true
} = options;
const descriptor = {
filename,
source,
template: null,
script: null,
scriptSetup: null,
styles: [],
customBlocks: [],
cssVars: [],
slotted: false,
shouldForceReload: (prevImports) => hmrShouldReload(prevImports, descriptor)
};
const errors = [];
const ast = compiler.parse(source, {
parseMode: "sfc",
prefixIdentifiers: parseExpressions,
...templateParseOptions,
onError: (e) => {
errors.push(e);
}
});
ast.children.forEach((node) => {
if (node.type !== 1) {
return;
}
if (ignoreEmpty && node.tag !== "template" && isEmpty(node) && !hasSrc(node)) {
return;
}
switch (node.tag) {
case "template":
if (!descriptor.template) {
const templateBlock = descriptor.template = createBlock(
node,
source,
false
);
if (!templateBlock.attrs.src) {
templateBlock.ast = compilerCore.createRoot(node.children, source);
}
if (templateBlock.attrs.functional) {
const err = new SyntaxError(
`<template functional> is no longer supported in Vue 3, since functional components no longer have significant performance difference from stateful ones. Just use a normal <template> instead.`
);
err.loc = node.props.find(
(p) => p.type === 6 && p.name === "functional"
).loc;
errors.push(err);
}
} else {
errors.push(createDuplicateBlockError(node));
}
break;
case "script":
const scriptBlock = createBlock(node, source, pad);
const isSetup = !!scriptBlock.attrs.setup;
if (isSetup && !descriptor.scriptSetup) {
descriptor.scriptSetup = scriptBlock;
break;
}
if (!isSetup && !descriptor.script) {
descriptor.script = scriptBlock;
break;
}
errors.push(createDuplicateBlockError(node, isSetup));
break;
case "style":
const styleBlock = createBlock(node, source, pad);
if (styleBlock.attrs.vars) {
errors.push(
new SyntaxError(
`<style vars> has been replaced by a new proposal: https://github.com/vuejs/rfcs/pull/231`
)
);
}
descriptor.styles.push(styleBlock);
break;
default:
descriptor.customBlocks.push(createBlock(node, source, pad));
break;
}
});
if (!descriptor.template && !descriptor.script && !descriptor.scriptSetup) {
errors.push(
new SyntaxError(
`At least one <template> or <script> is required in a single file component.`
)
);
}
if (descriptor.scriptSetup) {
if (descriptor.scriptSetup.src) {
errors.push(
new SyntaxError(
`<script setup> cannot use the "src" attribute because its syntax will be ambiguous outside of the component.`
)
);
descriptor.scriptSetup = null;
}
if (descriptor.script && descriptor.script.src) {
errors.push(
new SyntaxError(
`<script> cannot use the "src" attribute when <script setup> is also present because they must be processed together.`
)
);
descriptor.script = null;
}
}
let templateColumnOffset = 0;
if (descriptor.template && (descriptor.template.lang === "pug" || descriptor.template.lang === "jade")) {
[descriptor.template.content, templateColumnOffset] = dedent(
descriptor.template.content
);
}
if (sourceMap) {
const genMap = (block, columnOffset = 0) => {
if (block && !block.src) {
block.map = generateSourceMap(
filename,
source,
block.content,
sourceRoot,
!pad || block.type === "template" ? block.loc.start.line - 1 : 0,
columnOffset
);
}
};
genMap(descriptor.template, templateColumnOffset);
genMap(descriptor.script);
descriptor.styles.forEach((s) => genMap(s));
descriptor.customBlocks.forEach((s) => genMap(s));
}
descriptor.cssVars = parseCssVars(descriptor);
const slottedRE = /(?:::v-|:)slotted\(/;
descriptor.slotted = descriptor.styles.some(
(s) => s.scoped && slottedRE.test(s.content)
);
const result = {
descriptor,
errors
};
parseCache$1.set(sourceKey, result);
return result;
}
我还以为作者是多么的高大上,github的follower有七千多,竟然没有自己原生实现,反而用了vue的compile-sfc包,让我太失望了。
不过,只要他的compile函数的结果正确,那么我还是看一下呗。毕竟文章已经写这么长了。
parse
函数首先就把我给吓着了。竟然调用了一个函数叫做,genCacheKey
。
const sourceKey = genCacheKey(source, options);
他为什么要先生成一个缓存键呢?第一个问题是,什么是缓存键,第二个问题,缓存键有什么用?
待我休息一会,继续再战。
继续战斗。
看了一下,这个缓存键,好像没什么用,不对,我得再看看,看他在什么其他地方有什么用处。
const sourceKey = genCacheKey(source, options);
const cache = parseCache$1.get(sourceKey);
if (cache) {
return cache;
}
看来我一开始搞错了,这三行要一起看,就是用来缓存parse的结果的。
去买了一个西瓜,剪了一个头发,继续开始。
const ast = compiler.parse(source, {
parseMode: "sfc",
prefixIdentifiers: parseExpressions,
...templateParseOptions,
onError: (e) => {
errors.push(e);
}
});
接下来,就是重头戏了。它调用parse
方法,把source字符串转换成为ast。下面,我们看看这个parse方法如何实现的。
function parse(template, options = {}) {
return compilerCore.baseParse(template, shared.extend({}, parserOptions, options));
}
parse
方法直接是这样的一个形式,那么我们应该去看看baseParse
函数里面做了什么。
function baseParse(input, options) {
reset();
currentInput = input;
currentOptions = shared.extend({}, defaultParserOptions);
if (options) {
let key;
for (key in options) {
if (options[key] != null) {
currentOptions[key] = options[key];
}
}
}
{
if (currentOptions.decodeEntities) {
console.warn(
`[@vue/compiler-core] decodeEntities option is passed but will be ignored in non-browser builds.`
);
}
}
tokenizer.mode = currentOptions.parseMode === "html" ? 1 : currentOptions.parseMode === "sfc" ? 2 : 0;
tokenizer.inXML = currentOptions.ns === 1 || currentOptions.ns === 2;
const delimiters = options && options.delimiters;
if (delimiters) {
tokenizer.delimiterOpen = toCharCodes(delimiters[0]);
tokenizer.delimiterClose = toCharCodes(delimiters[1]);
}
const root = currentRoot = createRoot([], input);
tokenizer.parse(currentInput);
root.loc = getLoc(0, input.length);
root.children = condenseWhitespace(root.children);
currentRoot = null;
return root;
}
现在我们来看看,baseParse
函数做了什么?所以,这个地方就是这样的,调用parse
函数,parse
实际上里面调用了baseParse
函数,现在baseParse
函数才是重点。
他在parse之前,首先要reset
一下,为什么要reset
一下,我们看看reset
里面做了什么。
function reset() {
tokenizer.reset();
currentOpenTag = null;
currentProp = null;
currentAttrValue = "";
currentAttrStartIndex = -1;
currentAttrEndIndex = -1;
stack.length = 0;
}
这就是reset函数,
看了一下,tokenizer.reset()
,可以猜到,baseParse
使用了状态机,对字符串进行解析,生成ast。状态机这比较复杂,里面会定义各种状态参数,然后在解析的时候,会根据遇到字符串的种类,去改变状态参数,从而完成对字符串中解析到的特定种类的字符串的解析。
那么回到baseParse
函数,
比较重要的是这三行代码,
const root = currentRoot = createRoot([], input);
tokenizer.parse(currentInput);
return root;
三行代码一起看,就可以知道,他先创建了一个ast的根,然后解析这个模板,最后返回这个根。
如何解析的,我们先不管,因为打算,后面讲算法的时候,再讲算法。
这样,
const { descriptor, errors: mainCompilerErrors } = (0, compiler_sfc_1.parse)(source, { filename: context.filename, });
这段代码就讲完了,最后总结一下,就是他最后,生成了一个ast。
但是,我之所写这篇文章,主要不是为了关注template到ast的生成,我主要想关注,ast到代码的生成。因为这会涉及到后面,我去看vue-demi,包括tinyvue是如何磨平框架之间的差异性,调用同一套api的。
(0, context_1.resolveFeatures)(descriptor, context);
这个地方,注释写着// get the features
,到底,他要获取什么特征呢?
走进代码,看看去。
看了一下,没有找到,跟template相关的东西,那么,就往下走把。
const { result: templateResult, errors: templateErrors } = (0, template_1.resolveTemplate)(descriptor, context);
这一行代码,他在解析模板,所以,我先看看他解析出了什么内容。
看了一下,确实是我想要的东西,
import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("h1", null, "hello world"))
}
__sfc__.render = render
那么也就是说,resolveTemplate
就是我想找的函数,因此,直接看这个函数。
const resolveTemplate = (descriptor, context) => {
if (descriptor.template && !descriptor.scriptSetup) {
if (descriptor.template.lang && descriptor.template.lang !== 'html') {
return {
errors: [
new Error(`Unsupported template lang: ${descriptor.template.lang}`),
],
};
}
if (descriptor.template.src) {
return {
errors: [
new Error(`Unsupported external template: ${descriptor.template.src}.`),
],
};
}
const templateResult = (0, compiler_sfc_1.compileTemplate)({
...context.options.sfcTemplateCompilerOptions,
id: `data-v-${context.id}`,
filename: context.filename,
source: descriptor.template.content,
scoped: context.features.hasScoped,
compilerOptions: {
bindingMetadata: context.bindingMetadata,
...context.options.sfcTemplateCompilerOptions?.compilerOptions,
},
inMap: descriptor.template.map,
isProd: context.isProd,
});
if (templateResult.errors.length) {
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw templateResult.errors;
}
// No source map update technically.
const templateCode = `${templateResult.code.replace(/\nexport (function|const) (render|ssrRender)/, `\n$1 render`)}\n${constants_1.COMP_ID}.render = render`;
return {
result: {
code: templateCode,
sourceMap: templateResult.map,
},
};
}
return { result: { code: '' } };
};
这个函数,不长,只有四十几行。我们一行行看。
核心代码就在这,
const templateResult = (0, compiler_sfc_1.compileTemplate)({
...context.options.sfcTemplateCompilerOptions,
id: `data-v-${context.id}`,
filename: context.filename,
source: descriptor.template.content,
scoped: context.features.hasScoped,
compilerOptions: {
bindingMetadata: context.bindingMetadata,
...context.options.sfcTemplateCompilerOptions?.compilerOptions,
},
inMap: descriptor.template.map,
isProd: context.isProd,
});
我不得不吐槽一下,一个七千多follower的作者,却只是包装了一下别人的vue/compiler-sfc
,实在令人汗颜。
我们回到正题,compileTemplate
函数就长这个样子。
function compileTemplate(options) {
const { preprocessLang, preprocessCustomRequire } = options;
const preprocessor = preprocessLang ? preprocessCustomRequire ? preprocessCustomRequire(preprocessLang) : consolidate$1[preprocessLang] : false;
if (preprocessor) {
try {
return doCompileTemplate({
...options,
source: preprocess$1(options, preprocessor),
ast: void 0
// invalidate AST if template goes through preprocessor
});
} catch (e) {
return {
code: `export default function render() {}`,
source: options.source,
tips: [],
errors: [e]
};
}
} else if (preprocessLang) {
return {
code: `export default function render() {}`,
source: options.source,
tips: [
`Component ${options.filename} uses lang ${preprocessLang} for template. Please install the language preprocessor.`
],
errors: [
`Component ${options.filename} uses lang ${preprocessLang} for template, however it is not installed.`
]
};
} else {
return doCompileTemplate(options);
}
}
实际上,他执行的是这个函数,doCompileTemplate(options)
。我们看一下,这个函数。
function doCompileTemplate({
filename,
id,
scoped,
slotted,
inMap,
source,
ast: inAST,
ssr = false,
ssrCssVars,
isProd = false,
compiler,
compilerOptions = {},
transformAssetUrls
}) {
const errors = [];
const warnings = [];
let nodeTransforms = [];
if (shared.isObject(transformAssetUrls)) {
const assetOptions = normalizeOptions(transformAssetUrls);
nodeTransforms = [
createAssetUrlTransformWithOptions(assetOptions),
createSrcsetTransformWithOptions(assetOptions)
];
} else if (transformAssetUrls !== false) {
nodeTransforms = [transformAssetUrl, transformSrcset];
}
if (ssr && !ssrCssVars) {
warnOnce(
`compileTemplate is called with \`ssr: true\` but no corresponding \`cssVars\` option.`
);
}
if (!id) {
warnOnce(`compileTemplate now requires the \`id\` option.`);
id = "";
}
const shortId = id.replace(/^data-v-/, "");
const longId = `data-v-${shortId}`;
const defaultCompiler = ssr ? CompilerSSR__namespace : CompilerDOM__namespace;
compiler = compiler || defaultCompiler;
if (compiler !== defaultCompiler) {
inAST = void 0;
}
if (inAST == null ? void 0 : inAST.transformed) {
const newAST = (ssr ? CompilerDOM__namespace : compiler).parse(inAST.source, {
prefixIdentifiers: true,
...compilerOptions,
parseMode: "sfc",
onError: (e) => errors.push(e)
});
const template = newAST.children.find(
(node) => node.type === 1 && node.tag === "template"
);
inAST = compilerCore.createRoot(template.children, inAST.source);
}
let { code, ast, preamble, map } = compiler.compile(inAST || source, {
mode: "module",
prefixIdentifiers: true,
hoistStatic: true,
cacheHandlers: true,
ssrCssVars: ssr && ssrCssVars && ssrCssVars.length ? genCssVarsFromList(ssrCssVars, shortId, isProd, true) : "",
scopeId: scoped ? longId : void 0,
slotted,
sourceMap: true,
...compilerOptions,
hmr: !isProd,
nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []),
filename,
onError: (e) => errors.push(e),
onWarn: (w) => warnings.push(w)
});
if (inMap && !inAST) {
if (map) {
map = mapLines(inMap, map);
}
if (errors.length) {
patchErrors(errors, source, inMap);
}
}
const tips = warnings.map((w) => {
let msg = w.message;
if (w.loc) {
msg += `
${shared.generateCodeFrame(
(inAST == null ? void 0 : inAST.source) || source,
w.loc.start.offset,
w.loc.end.offset
)}`;
}
return msg;
});
return { code, ast, preamble, source, errors, tips, map };
}
然后,这里面,最终走了这个函数。
let { code, ast, preamble, map } = compiler.compile(inAST || source, {
mode: "module",
prefixIdentifiers: true,
hoistStatic: true,
cacheHandlers: true,
ssrCssVars: ssr && ssrCssVars && ssrCssVars.length ? genCssVarsFromList(ssrCssVars, shortId, isProd, true) : "",
scopeId: scoped ? longId : void 0,
slotted,
sourceMap: true,
...compilerOptions,
hmr: !isProd,
nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []),
filename,
onError: (e) => errors.push(e),
onWarn: (w) => warnings.push(w)
});
实际又走了这个函数。
function compile(src, options = {}) {
return compilerCore.baseCompile(
src,
shared.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: shared.extend(
{},
DOMDirectiveTransforms,
options.directiveTransforms || {}
),
transformHoist: stringifyStatic
})
);
}
我们看看,baseCompile
。
function baseCompile(source, options = {}) {
const onError = options.onError || defaultOnError;
const isModuleMode = options.mode === "module";
const prefixIdentifiers = options.prefixIdentifiers === true || isModuleMode;
if (!prefixIdentifiers && options.cacheHandlers) {
onError(createCompilerError(49));
}
if (options.scopeId && !isModuleMode) {
onError(createCompilerError(50));
}
const resolvedOptions = shared.extend({}, options, {
prefixIdentifiers
});
const ast = shared.isString(source) ? baseParse(source, resolvedOptions) : source;
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(prefixIdentifiers);
if (options.isTS) {
const { expressionPlugins } = options;
if (!expressionPlugins || !expressionPlugins.includes("typescript")) {
options.expressionPlugins = [...expressionPlugins || [], "typescript"];
}
}
transform(
ast,
shared.extend({}, resolvedOptions, {
nodeTransforms: [
...nodeTransforms,
...options.nodeTransforms || []
// user transforms
],
directiveTransforms: shared.extend(
{},
directiveTransforms,
options.directiveTransforms || {}
// user transforms
)
})
);
return generate(ast, resolvedOptions);
}
baseCompile
里面,最终又调了那个函数,还是直接就执行了。
发现他对ast做了一次转换
transform(
ast,
shared.extend({}, resolvedOptions, {
nodeTransforms: [
...nodeTransforms,
...options.nodeTransforms || []
// user transforms
],
directiveTransforms: shared.extend(
{},
directiveTransforms,
options.directiveTransforms || {}
// user transforms
)
})
);
return generate(ast, resolvedOptions);
最后就生成了结果。我们一个个来看,先看转换过程。
转换过程,没有什么重点,就忽略了。
接下来,看看generate
。
function generate(ast, options = {}) {
const context = createCodegenContext(ast, options);
if (options.onContextCreated) options.onContextCreated(context);
const {
mode,
push,
prefixIdentifiers,
indent,
deindent,
newline,
scopeId,
ssr
} = context;
const helpers = Array.from(ast.helpers);
const hasHelpers = helpers.length > 0;
const useWithBlock = !prefixIdentifiers && mode !== "module";
const genScopeId = scopeId != null && mode === "module";
const isSetupInlined = !!options.inline;
const preambleContext = isSetupInlined ? createCodegenContext(ast, options) : context;
if (mode === "module") {
genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined);
} else {
genFunctionPreamble(ast, preambleContext);
}
const functionName = ssr ? `ssrRender` : `render`;
const args = ssr ? ["_ctx", "_push", "_parent", "_attrs"] : ["_ctx", "_cache"];
if (options.bindingMetadata && !options.inline) {
args.push("$props", "$setup", "$data", "$options");
}
const signature = options.isTS ? args.map((arg) => `${arg}: any`).join(",") : args.join(", ");
if (isSetupInlined) {
push(`(${signature}) => {`);
} else {
push(`function ${functionName}(${signature}) {`);
}
indent();
if (useWithBlock) {
push(`with (_ctx) {`);
indent();
if (hasHelpers) {
push(
`const { ${helpers.map(aliasHelper).join(", ")} } = _Vue
`,
-1 /* End */
);
newline();
}
}
if (ast.components.length) {
genAssets(ast.components, "component", context);
if (ast.directives.length || ast.temps > 0) {
newline();
}
}
if (ast.directives.length) {
genAssets(ast.directives, "directive", context);
if (ast.temps > 0) {
newline();
}
}
if (ast.filters && ast.filters.length) {
newline();
genAssets(ast.filters, "filter", context);
newline();
}
if (ast.temps > 0) {
push(`let `);
for (let i = 0; i < ast.temps; i++) {
push(`${i > 0 ? `, ` : ``}_temp${i}`);
}
}
if (ast.components.length || ast.directives.length || ast.temps) {
push(`
`, 0 /* Start */);
newline();
}
if (!ssr) {
push(`return `);
}
if (ast.codegenNode) {
genNode(ast.codegenNode, context);
} else {
push(`null`);
}
if (useWithBlock) {
deindent();
push(`}`);
}
deindent();
push(`}`);
return {
ast,
code: context.code,
preamble: isSetupInlined ? preambleContext.code : ``,
map: context.map ? context.map.toJSON() : void 0
};
}
我看了一下,要会写这个东西,必须对于vue的编译后的使用,十分清楚,我目前还没这个功力。其实从generate
函数可以看到,操作就好似一个模板里面填内容。很多东西都是固定好的,只需要稍微转换一下,往里面填内容就可以了。
至此,整个解析过程就完成了。
可以看到,他其实并没有那么神秘。
现在解答,我一开始疑问的问题。就是他是怎么把ast转换成一个js文件的。
他通过一些固定格式,把格式设置成一个模板,然后,对ast中,template的内容该转换成什么,放在什么地方,做了一个转换。
转载自:https://juejin.cn/post/7393481473083457577