紧接着上一篇 @babel/core 源码分析(1) 在上一篇我们可以知道,在full.ts
里面的loadFullConfig方法中,首先调用了loadPrivatePartialConfig
方法。那么我们可以进去到partial.ts
文件中,定位到loadPrivatePartialConfig方法。
loadPrivatePartialConfig方法
js
export default function* loadPrivatePartialConfig(
inputOpts: unknown,
): Handler<PrivPartialConfig | null> {
// 校验参数合法性
if (
inputOpts != null &&
(typeof inputOpts !== "object" || Array.isArray(inputOpts))
) {
throw new Error("Babel options must be an object, null, or undefined");
}
const args = inputOpts ? validate("arguments", inputOpts) : {};
const {
envName = getEnv(),
cwd = ".",
root: rootDir = ".",
rootMode = "root",
caller,
cloneInputAst = true,
} = args;
const absoluteCwd = path.resolve(cwd); //解析为绝对路径
const absoluteRootDir = resolveRootMode(
path.resolve(absoluteCwd, rootDir),
rootMode,
);
const filename =
typeof args.filename === "string"
? path.resolve(cwd, args.filename)
: undefined;
// 获取根据 BABEL_SHOW_CONFIG_FOR 配置的路径文件,在buildRootChain方法中打印生效的配置
const showConfigPath = yield* resolveShowConfigPath(absoluteCwd);
// 定义配置上下文
const context: ConfigContext = {
filename,
cwd: absoluteCwd,
root: absoluteRootDir,
envName,
caller,
showConfig: showConfigPath === filename,
};
// 把整理好的参数传进buildRootChain,获取根配置链
const configChain = yield* buildRootChain(args, context);
if (!configChain) return null;
// 下面都是格式化配置链
const merged: ValidatedOptions = {
assumptions: {},
};
configChain.options.forEach(opts => {
mergeOptions(merged as any, opts);
});
const options: NormalizedOptions = {
...merged,
targets: resolveTargets(merged, absoluteRootDir),
// Tack the passes onto the object itself so that, if this object is
// passed back to Babel a second time, it will be in the right structure
// to not change behavior.
cloneInputAst,
babelrc: false,
configFile: false,
browserslistConfigFile: false,
passPerPreset: false,
envName: context.envName,
cwd: context.cwd,
root: context.root,
rootMode: "root",
filename:
typeof context.filename === "string" ? context.filename : undefined,
plugins: configChain.plugins.map(descriptor =>
createItemFromDescriptor(descriptor),
),
presets: configChain.presets.map(descriptor =>
createItemFromDescriptor(descriptor),
),
};
return {
options,
context,
fileHandling: configChain.fileHandling,
ignore: configChain.ignore,
babelrc: configChain.babelrc,
config: configChain.config,
files: configChain.files,
};
}
可以看出,loadPrivatePartialConfig方法主要调用了两个方法,一个是resolveShowConfigPath ,一个是buildRootChain。
resolveShowConfigPath
路径: src/files/configurations.ts
ts
export function* resolveShowConfigPath(
dirname: string,
): Handler<string | null> {
// 获取环境变量 BABEL_SHOW_CONFIG_FOR
const targetPath = process.env.BABEL_SHOW_CONFIG_FOR;
if (targetPath != null) {
const absolutePath = path.resolve(dirname, targetPath);
const stats = yield* fs.stat(absolutePath);
if (!stats.isFile()) {
throw new Error(
`${absolutePath}: BABEL_SHOW_CONFIG_FOR must refer to a regular file, directories are not supported.`,
);
}
// 返回绝对路径
return absolutePath;
}
return null;
}
这个方法很好理解,就是根据环境变量中传入的BABEL_SHOW_CONFIG_FOR,来返回展示配置的路径。
buildRootChain
路径: src/config/config-chain.ts
ts
export function* buildRootChain(
opts: ValidatedOptions,
context: ConfigContext,
): Handler<RootConfigChain | null> {
let configReport, babelRcReport;
const programmaticLogger = new ConfigPrinter();
/**
* loadProgrammaticChain 不是在预设或者配置文件。是直接指定或者babel-loader的配置
*/
const programmaticChain = yield* loadProgrammaticChain(
{
options: opts,
dirname: context.cwd,
},
context,
undefined,
programmaticLogger,
);
if (!programmaticChain) return null;
const programmaticReport = yield* programmaticLogger.output();
let configFile;
if (typeof opts.configFile === "string") {
configFile = yield* loadConfig(
opts.configFile,
context.cwd,
context.envName,
context.caller,
);
} else if (opts.configFile !== false) {
configFile = yield* findRootConfig(
context.root,
context.envName,
context.caller,
);
}
let { babelrc, babelrcRoots } = opts;
let babelrcRootsDirectory = context.cwd;
const configFileChain = emptyChain();
const configFileLogger = new ConfigPrinter();
if (configFile) {
// 判断configFile的有效性
const validatedFile = validateConfigFile(configFile);
const result = yield* loadFileChain(
validatedFile,
context,
undefined,
configFileLogger,
);
if (!result) return null;
configReport = yield* configFileLogger.output();
// Allow config files to toggle `.babelrc` resolution on and off and
// specify where the roots are.
if (babelrc === undefined) {
babelrc = validatedFile.options.babelrc;
}
if (babelrcRoots === undefined) {
babelrcRootsDirectory = validatedFile.dirname;
babelrcRoots = validatedFile.options.babelrcRoots;
}
mergeChain(configFileChain, result);
}
let ignoreFile, babelrcFile;
let isIgnored = false;
const fileChain = emptyChain();
// resolve all .babelrc files
if (
(babelrc === true || babelrc === undefined) &&
typeof context.filename === "string"
) {
const pkgData = yield* findPackageData(context.filename);
if (
pkgData &&
babelrcLoadEnabled(context, pkgData, babelrcRoots, babelrcRootsDirectory)
) {
({ ignore: ignoreFile, config: babelrcFile } = yield* findRelativeConfig(
pkgData,
context.envName,
context.caller,
));
if (ignoreFile) {
fileChain.files.add(ignoreFile.filepath);
}
if (
ignoreFile &&
shouldIgnore(context, ignoreFile.ignore, null, ignoreFile.dirname)
) {
isIgnored = true;
}
if (babelrcFile && !isIgnored) {
const validatedFile = validateBabelrcFile(babelrcFile);
const babelrcLogger = new ConfigPrinter();
const result = yield* loadFileChain(
validatedFile,
context,
undefined,
babelrcLogger,
);
if (!result) {
isIgnored = true;
} else {
babelRcReport = yield* babelrcLogger.output();
mergeChain(fileChain, result);
}
}
if (babelrcFile && isIgnored) {
fileChain.files.add(babelrcFile.filepath);
}
}
}
if (context.showConfig) {
console.log(
`Babel configs on "${context.filename}" (ascending priority):\n` +
// print config by the order of ascending priority
[configReport, babelRcReport, programmaticReport]
.filter(x => !!x)
.join("\n\n") +
"\n-----End Babel configs-----",
);
}
// Insert file chain in front so programmatic options have priority
// over configuration file chain items.
const chain = mergeChain(
mergeChain(mergeChain(emptyChain(), configFileChain), fileChain),
programmaticChain,
);
return {
plugins: isIgnored ? [] : dedupDescriptors(chain.plugins),
presets: isIgnored ? [] : dedupDescriptors(chain.presets),
options: isIgnored ? [] : chain.options.map(o => normalizeOptions(o)),
fileHandling: isIgnored ? "ignored" : "transpile",
ignore: ignoreFile || undefined,
babelrc: babelrcFile || undefined,
config: configFile || undefined,
files: chain.files,
};
}
buildRootChian方法比较长,我们分成一块一块去看。首先看第一段
ts
const programmaticChain = yield* loadProgrammaticChain(
{
options: opts,
dirname: context.cwd,
},
context,
undefined,
programmaticLogger,
);
获取直接命令行指定或者代码调用的plugins和preset配置。 It's when you specify them directly to Babel (babel.transformSync(code, programmaticOptions)
or to the Babel integration you are using (e.g. babel-loader
, which can pass them to its internal babel.transform
call). 参考github.com/babel/babel...
ts
let configFile;
if (typeof opts.configFile === "string") {
configFile = yield* loadConfig(
opts.configFile,
context.cwd,
context.envName,
context.caller,
);
} else if (opts.configFile !== false) {
configFile = yield* findRootConfig(
context.root,
context.envName,
context.caller,
);
}
根据opts.configFile去判定运行,有的话就获取configFile指定的配置文件,无的话就从根文件寻找babel.config.js,babel.config.cjs,babel.config.mjs,"babel.config.json","babel.config.cts"文件,然后把获取的内容写进configFile,configtFile包含了filepath、dirname、option等配置。
ts
// 返回有效的ConfigFile格式
const validatedFile = validateConfigFile(configFile);
const result = yield* loadFileChain(
validatedFile,
context,
undefined,
configFileLogger,
);
根据ConfigFile地址,加载ConfigFile的配置。
ts
let ignoreFile, babelrcFile;
let isIgnored = false;
const fileChain = emptyChain();
// resolve all .babelrc files
if (
(babelrc === true || babelrc === undefined) &&
typeof context.filename === "string"
) {
const pkgData = yield* findPackageData(context.filename);
if (
pkgData &&
babelrcLoadEnabled(context, pkgData, babelrcRoots, babelrcRootsDirectory)
) {
({ ignore: ignoreFile, config: babelrcFile } = yield* findRelativeConfig(
pkgData,
context.envName,
context.caller,
));
if (ignoreFile) {
fileChain.files.add(ignoreFile.filepath);
}
if (
ignoreFile &&
shouldIgnore(context, ignoreFile.ignore, null, ignoreFile.dirname)
) {
isIgnored = true;
}
if (babelrcFile && !isIgnored) {
const validatedFile = validateBabelrcFile(babelrcFile);
const babelrcLogger = new ConfigPrinter();
const result = yield* loadFileChain(
validatedFile,
context,
undefined,
babelrcLogger,
);
if (!result) {
isIgnored = true;
} else {
babelRcReport = yield* babelrcLogger.output();
mergeChain(fileChain, result);
}
}
if (babelrcFile && isIgnored) {
fileChain.files.add(babelrcFile.filepath);
}
}
}
findPackageData方法主要是为了找到该文件的元数据。文件的路径、package.json的路径等。 findRelativeConfig方法主要是寻找 ".babelrc",".babelrc.js",".babelrc.cjs", ".babelrc.mjs", ".babelrc.json", ".babelrc.cts"等文件。 然后通过validateBabelrcFile方法获取有效的babelrc地址,再通过loadFileChain获取babelrc的配置。
ts
const chain = mergeChain(
mergeChain(mergeChain(emptyChain(), configFileChain), fileChain),
programmaticChain,
);
return {
plugins: isIgnored ? [] : dedupDescriptors(chain.plugins),
presets: isIgnored ? [] : dedupDescriptors(chain.presets),
options: isIgnored ? [] : chain.options.map(o => normalizeOptions(o)),
fileHandling: isIgnored ? "ignored" : "transpile",
ignore: ignoreFile || undefined,
babelrc: babelrcFile || undefined,
config: configFile || undefined,
files: chain.files,
};
最后合并configFileChain、fileChain(babelrc)和programmaticChain。再通过dedupDescriptors方法去重后返回最后的配置。