从@babel/core 源码分析(3) 我们可以看到loadFullConfig函数的作用。那么现在我们先回到最初的起点。
transform-file.ts
const transformFileRunner = gensync(function* (
filename: string,
opts?: InputOptions,
): Handler<FileResult | null> {
const options = { ...opts, filename };
const config: ResolvedConfig | null = yield* loadConfig(options); // 加载配置
if (config === null) return null;
const code = yield* fs.readFile(filename, "utf8");
return yield* run(config, code);
});
可以看出,当我们获取了完整的配置之后,下一步就是读取文件内容fs.readFile。
最后把配置和文件内容传进 run 函数中,接下来我们就看看 run函数做了什么。
transformation/index.ts
run函数
export function* run(
config: ResolvedConfig,
code: string,
ast?: t.File | t.Program | null,
): Handler<FileResult> {
const file = yield* normalizeFile(
config.passes,
normalizeOptions(config),
code,
ast,
);
const opts = file.opts;
try {
yield* transformFile(file, config.passes);
} catch (e) {
e.message = `${opts.filename ?? "unknown file"}: ${e.message}`;
if (!e.code) {
e.code = "BABEL_TRANSFORM_ERROR";
}
throw e;
}
let outputCode, outputMap;
try {
if (opts.code !== false) {
({ outputCode, outputMap } = generateCode(config.passes, file));
}
} catch (e) {
e.message = `${opts.filename ?? "unknown file"}: ${e.message}`;
if (!e.code) {
e.code = "BABEL_GENERATE_ERROR";
}
throw e;
}
run函数接受了三个参数,一个是config、code和一个非必要的ast。
那么接下来我们一个一个函数来进行分析。
normalizeFile
const file = yield* normalizeFile(
config.passes,
normalizeOptions(config),
code,
ast,
);
normalizeFile接收了四个参数。先看看normalizeFile函数做了什么。
transformation/normalize-file.ts
export default function* normalizeFile(
pluginPasses: PluginPasses,
options: { [key: string]: any },
code: string,
ast?: t.File | t.Program | null,
): Handler<File> {
// 把code确定好是个字符串
code = `${code || ""}`;
// 判断是否有ast
if (ast) {
if (ast.type === "Program") {
ast = file(ast, [], []);
} else if (ast.type !== "File") {
throw new Error("AST root must be a Program or File node");
}
if (options.cloneInputAst) {
ast = cloneDeep(ast);
}
} else {
// 本例子没有ast,我们直接看这里
// 运行了parse函数,导入了 @babel/parser 的方法
// 把字符串解析成AST
// @ts-expect-error todo: use babel-types ast typings in Babel parser
ast = yield* parser(pluginPasses, options, code);
}
let inputMap = null;
// 这里判断配置中是否有 inputSourceMap,他的默认值是TRUE
if (options.inputSourceMap !== false) {
// If an explicit object is passed in, it overrides the processing of
// source maps that may be in the file itself.
// 如果inputSourceMap是个对象,他就会被视为源映射本身
// 这个的意思就是,如果没有配置inputSourceMap,那么就是以自身的文件内容生成sourceMap
// 但是如果配置了这个inputSourceMap的话,这个sourceMap就是以这个inputSourceMap来生成
if (typeof options.inputSourceMap === "object") {
inputMap = convertSourceMap.fromObject(options.inputSourceMap);
}
// 这里下面就是从各个渠道去获取inputMap
if (!inputMap) {
const lastComment = extractComments(INLINE_SOURCEMAP_REGEX, ast);
if (lastComment) {
try {
inputMap = convertSourceMap.fromComment(lastComment);
} catch (err) {
debug("discarding unknown inline input sourcemap", err);
}
}
}
if (!inputMap) {
const lastComment = extractComments(EXTERNAL_SOURCEMAP_REGEX, ast);
if (typeof options.filename === "string" && lastComment) {
try {
// when `lastComment` is non-null, EXTERNAL_SOURCEMAP_REGEX must have matches
const match: [string, string] = EXTERNAL_SOURCEMAP_REGEX.exec(
lastComment,
) as any;
const inputMapContent = fs.readFileSync(
path.resolve(path.dirname(options.filename), match[1]),
"utf8",
);
inputMap = convertSourceMap.fromJSON(inputMapContent);
} catch (err) {
debug("discarding unknown file input sourcemap", err);
}
} else if (lastComment) {
debug("discarding un-loadable file input sourcemap");
}
}
}
// 最后返回File 对象
return new File(options, {
code,
ast: ast as t.File,
inputMap,
});
}
总结:normalizeFile就是接收源文件的内容和相关配置,然后经过parser等处理对内容进行处理,把JS转换成AST,最后返回File对象。里面重点就是parse函数。
parse
import type { Handler } from "gensync";
import { parse } from "@babel/parser";
import { codeFrameColumns } from "@babel/code-frame";
import generateMissingPluginMessage from "./util/missing-plugin-helper";
import type { PluginPasses } from "../config";
// ReturnType获取函数返回的类型
// 所以这里ParseResult是parse函数返回的值的类型
export type ParseResult = ReturnType<typeof parse>;
export default function* parser(
pluginPasses: PluginPasses,
{ parserOpts, highlightCode = true, filename = "unknown" }: any,
code: string,
): Handler<ParseResult> {
try {
const results = [];
// to do: figure out what id pluginPasses
// 正常来说,@babel/parse已经足够AST转换的,
// 但还是存在要自定义的时候,这时候就可以自己写一个parse,通过插件的形式,
// 指定这个parser为babel的编译器(override 覆盖)
for (const plugins of pluginPasses) {
for (const plugin of plugins) {
const { parserOverride } = plugin;
if (parserOverride) {
const ast = parserOverride(code, parserOpts, parse);
if (ast !== undefined) results.push(ast);
}
}
}
if (results.length === 0) {
return parse(code, parserOpts);
} else if (results.length === 1) {
// @ts-expect-error - If we want to allow async parsers
yield* [];
if (typeof results[0].then === "function") {
throw new Error(
`You appear to be using an async parser plugin, ` +
`which your current version of Babel does not support. ` +
`If you're using a published plugin, you may need to upgrade ` +
`your @babel/core version.`,
);
}
return results[0];
}
// TODO: Add an error code
// 只能有一个插件编译器,不然多了话,babel不知道要执行哪一个
throw new Error("More than one plugin attempted to override parsing.");
} catch (err) {
if (err.code === "BABEL_PARSER_SOURCETYPE_MODULE_REQUIRED") {
err.message +=
"\nConsider renaming the file to '.mjs', or setting sourceType:module " +
"or sourceType:unambiguous in your Babel config for this file.";
// err.code will be changed to BABEL_PARSE_ERROR later.
}
const { loc, missingPlugin } = err;
if (loc) {
...
}
throw err;
}
}
transformFile
const opts = file.opts;
try {
yield* transformFile(file, config.passes);
} catch (e) {
e.message = `${opts.filename ?? "unknown file"}: ${e.message}`;
if (!e.code) {
e.code = "BABEL_TRANSFORM_ERROR";
}
throw e;
}
接下来分析transformFile函数
function* transformFile(file: File, pluginPasses: PluginPasses): Handler<void> {
for (const pluginPairs of pluginPasses) {
const passPairs: [Plugin, PluginPass][] = [];
const passes = [];
const visitors = [];
// loadBlockHoistPlugin 用于排序
for (const plugin of pluginPairs.concat([loadBlockHoistPlugin()])) {
const pass = new PluginPass(file, plugin.key, plugin.options);
passPairs.push([plugin, pass]);
passes.push(pass);
visitors.push(plugin.visitor);
}
// 执行所有插件的pre方法
for (const [plugin, pass] of passPairs) {
const fn = plugin.pre;
if (fn) {
// eslint-disable-next-line @typescript-eslint/no-confusing-void-expression
const result = fn.call(pass, file);
// @ts-expect-error - If we want to support async .pre
yield* [];
if (isThenable(result)) {
throw new Error(
`You appear to be using an plugin with an async .pre, ` +
`which your current version of Babel does not support. ` +
`If you're using a published plugin, you may need to upgrade ` +
`your @babel/core version.`,
);
}
}
}
// merge all plugin visitors into a single visitor
// 调用了@@babel/traverse,把所有插件的visitors合并成一个visitor。
// 然后使用traverse遍历所有AST,在遍历过程中执行插件。
const visitor = traverse.visitors.merge(
visitors,
passes,
file.opts.wrapPluginVisitorMethod,
);
traverse(file.ast, visitor, file.scope);
// 最后执行所有插件的post方法
for (const [plugin, pass] of passPairs) {
const fn = plugin.post;
if (fn) {
// eslint-disable-next-line @typescript-eslint/no-confusing-void-expression
const result = fn.call(pass, file);
// @ts-expect-error - If we want to support async .post
yield* [];
if (isThenable(result)) {
throw new Error(
`You appear to be using an plugin with an async .post, ` +
`which your current version of Babel does not support. ` +
`If you're using a published plugin, you may need to upgrade ` +
`your @babel/core version.`,
);
}
}
}
}
}
总结:transformFile中最重要的就是插件。这一步就是根据插件修改AST的内容。插件使用visitor模式来决定特定节点该如何操作。babel将这个对AST树的遍历和节点的增删放在了 @babel/traverse。
然后我们继续往下看。
generateCode
let outputCode, outputMap;
try {
if (opts.code !== false) {
({ outputCode, outputMap } = generateCode(config.passes, file));
}
} catch (e) {
e.message = `${opts.filename ?? "unknown file"}: ${e.message}`;
if (!e.code) {
e.code = "BABEL_GENERATE_ERROR";
}
throw e;
}
调用了generateCode方法,往下看看此方法具体内容。
generate.ts
import convertSourceMap from "convert-source-map";
type SourceMap = any;
import generate from "@babel/generator";
import type File from "./file";
import mergeSourceMap from "./merge-map";
export default function generateCode(
pluginPasses: PluginPasses,
file: File,
): {
outputCode: string;
outputMap: SourceMap | null;
} {
const { opts, ast, code, inputMap } = file;
const { generatorOpts } = opts;
generatorOpts.inputSourceMap = inputMap?.toObject();
// 遍历插件的generatorOverride方法,但是这个方法只能是一个,因为最终的转换结果只能有一个。
// 如果没有generatorOverride方法,
// 就使用 @babel/generator 提供的generate方法
const results = [];
for (const plugins of pluginPasses) {
for (const plugin of plugins) {
const { generatorOverride } = plugin;
if (generatorOverride) {
const result = generatorOverride(ast, generatorOpts, code, generate);
if (result !== undefined) results.push(result);
}
}
}
let result;
if (results.length === 0) {
result = generate(ast, generatorOpts, code);
} else if (results.length === 1) {
result = results[0];
if (typeof result.then === "function") {
throw new Error(
`You appear to be using an async codegen plugin, ` +
`which your current version of Babel does not support. ` +
`If you're using a published plugin, ` +
`you may need to upgrade your @babel/core version.`,
);
}
} else {
throw new Error("More than one plugin attempted to override codegen.");
}
// Decoded maps are faster to merge, so we attempt to get use the decodedMap
// first. But to preserve backwards compat with older Generator, we'll fall
// back to the encoded map.
let { code: outputCode, decodedMap: outputMap = result.map } = result;
// For backwards compat.
if (result.__mergedMap) {
/**
* @see mergeSourceMap
*/
outputMap = { ...result.map };
} else {
if (outputMap) {
if (inputMap) {
// mergeSourceMap returns an encoded map
outputMap = mergeSourceMap(
inputMap.toObject(),
outputMap,
generatorOpts.sourceFileName,
);
} else {
// We cannot output a decoded map, so retrieve the encoded form. Because
// the decoded form is free, it's fine to prioritize decoded first.
outputMap = result.map;
}
}
}
// 把最后生成的sourceMap 根据配置写进相关的位置
if (opts.sourceMaps === "inline" || opts.sourceMaps === "both") {
outputCode += "\n" + convertSourceMap.fromObject(outputMap).toComment();
}
if (opts.sourceMaps === "inline") {
outputMap = null;
}
return { outputCode, outputMap };
}
总结: 最后这一步,就是把AST重新生成code。他会去遍历插件是否有generate方法,如果有,就用插件的。如果没有,就使用@babel/generator提供的。同时还会生成sourceMap相关代码。
到这里,其实@babel/core就分析的差不多了。