接上一章@babel/cli的源码分析后,接下来就是@babel/core的源码分析。
目录结构
先看看@babel/core的目录结构
- src
- config
- files
- helpers
- validation
- ...
- errors
- config-error.ts
- rewrite-stack-trance.ts
- gensync-utils
- async.ts
- fs.ts
- functional.ts
- parser
- util
- index.ts
- tools
- build-external-helpers.ts
- transformation
- file
- tuil
- block-hoist-plugin.ts
- index.ts
- normalize-file.ts
- normalize-opts.ts
- plugin-pass.ts
- vendor
- index.ts
- parse.ts
- transfrom-ast.ts
- transform-fils-browser.ts
- transform-file.ts
- transform.ts
可以看到,@babel/core的目录主要分为几个主要部分:
config:主要是处理babel加载配置的问题。errors:处理错误。parse:引入@babel/parse包来生成AST。tools:工具文件transformation:引入@babel/traverse来转换index.ts: 入口文件,导出各种apiparse.ts: 定义parse等类型,引入parse,导出运行parse 的sync、async、errBack等api。transform-ast.ts: 定义TransformFromAst等类型,引入 transform ,导出运行transformFromAst的sync、async、errBack等api,参数为AST。transform-file-browser.ts: 替换掉在浏览器环境下运行不支持的函数,并抛出错误。transform-file.ts: 定义transformFileType等类型,引入transform,导出transformFile的sync、async、errBack等api,参数为File。transform.ts:定义Transform等类型,引入transform,导出Transform的sync、async、errBack等api,参数为Code。
上一章最后说到,他最后会调用@babel/core的transformFile函数。
为什么是调用这个呢?
因为我们的启动的命令是babel src --out-dir lib,传入的参数是File,所以会调用transformFile函数。
其实transform-ast.ts、transform-file.ts、transform.ts逻辑都大同小异。
那么我们从transform-file.ts的transformFile作为切入点。来分析一下。
先看一下transform-file.ts的源码
transform-file.ts的源码
// https://www.npmjs.com/package/gensync
// gensync库封装generator函数,返回一个新的函数,其有不同的使用方式
import gensync, { type Handler } from "gensync";
// 获取配置
import loadConfig from "./config";
import type { InputOptions, ResolvedConfig } from "./config";
import { run } from "./transformation";
import type { FileResult, FileResultCallback } from "./transformation";
import * as fs from "./gensync-utils/fs";
type transformFileBrowserType = typeof import("./transform-file-browser");
type transformFileType = typeof import("./transform-file");
// Kind of gross, but essentially asserting that the exports of this module are the same as the
// exports of transform-file-browser, since this file may be replaced at bundle time with
// transform-file-browser.
({}) as any as transformFileBrowserType as transformFileType;
// 重点语句
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");
// 调用transformation的run函数
return yield* run(config, code);
});
// @ts-expect-error TS doesn't detect that this signature is compatible
// `.sync(...args)` - Returns the computed value, or throws.同步使用
// `.async(...args)` - Returns a promise for the computed value.异步使用,,封装成promise,在then方法中获取结果
// `.errback(...args, (err, result) => {})` - Calls the callback with the computed value, or error.传入回调函数使用
// 下面的代码都是这几种调用的实现方式。
export function transformFile(
filename: string,
callback: FileResultCallback,
): void;
export function transformFile(
filename: string,
opts: InputOptions | undefined | null,
callback: FileResultCallback,
): void;
export function transformFile(
...args: Parameters<typeof transformFileRunner.errback>
) {
transformFileRunner.errback(...args);
}
export function transformFileSync(
...args: Parameters<typeof transformFileRunner.sync>
) {
return transformFileRunner.sync(...args);
}
export function transformFileAsync(
...args: Parameters<typeof transformFileRunner.async>
) {
return transformFileRunner.async(...args);
}
通过transform-file.ts的源码我们能发现,他是先调用了config文件去获取配置,再进行下一步。那现在就先看看config里面做了什么。
config
其实config文件就是 index.ts入口文件 看看index.ts做了什么
import gensync, { type Handler, type Callback } from "gensync";
export type {
ResolvedConfig,
InputOptions,
PluginPasses,
Plugin,
} from "./full";
import type { PluginTarget } from "./validation/options";
import type {
PluginAPI as basePluginAPI,
PresetAPI as basePresetAPI,
} from "./helpers/config-api";
export type { PluginObject } from "./validation/plugins";
type PluginAPI = basePluginAPI & typeof import("..");
type PresetAPI = basePresetAPI & typeof import("..");
export type { PluginAPI, PresetAPI };
// todo: may need to refine PresetObject to be a subset of ValidatedOptions
export type {
CallerMetadata,
ValidatedOptions as PresetObject,
} from "./validation/options";
import loadFullConfig, { type ResolvedConfig } from "./full";
import { loadPartialConfig as loadPartialConfigRunner } from "./partial";
// 默认输出模块
export { loadFullConfig as default };
export type { PartialConfig } from "./partial";
import { createConfigItem as createConfigItemImpl } from "./item";
import type { ConfigItem } from "./item";
const loadOptionsRunner = gensync(function* (
opts: unknown,
): Handler<ResolvedConfig | null> {
const config = yield* loadFullConfig(opts);
// NOTE: We want to return "null" explicitly, while ?. alone returns undefined
return config?.options ?? null;
});
const createConfigItemRunner = gensync(createConfigItemImpl);
const maybeErrback =
<Arg, Return>(runner: gensync.Gensync<[Arg], Return>) =>
(argOrCallback: Arg | Callback<Return>, maybeCallback?: Callback<Return>) => {
let arg: Arg | undefined;
let callback: Callback<Return>;
if (maybeCallback === undefined && typeof argOrCallback === "function") {
callback = argOrCallback as Callback<Return>;
arg = undefined;
} else {
callback = maybeCallback;
arg = argOrCallback as Arg;
}
if (!callback) {
return runner.sync(arg);
}
runner.errback(arg, callback);
};
export const loadPartialConfig = maybeErrback(loadPartialConfigRunner);
export const loadPartialConfigSync = loadPartialConfigRunner.sync;
export const loadPartialConfigAsync = loadPartialConfigRunner.async;
export const loadOptions = maybeErrback(loadOptionsRunner);
export const loadOptionsSync = loadOptionsRunner.sync;
export const loadOptionsAsync = loadOptionsRunner.async;
export const createConfigItemSync = createConfigItemRunner.sync;
export const createConfigItemAsync = createConfigItemRunner.async;
export function createConfigItem(
target: PluginTarget,
options: Parameters<typeof createConfigItemImpl>[1],
callback?: (err: Error, val: ConfigItem | null) => void,
) {
if (callback !== undefined) {
createConfigItemRunner.errback(target, options, callback);
} else if (typeof options === "function") {
createConfigItemRunner.errback(target, undefined, callback);
} else {
return createConfigItemRunner.sync(target, options);
}
}
从源码可以看出,index.ts文件主要就是引入各模块,然后统一导出。
在transform-file.ts文件中import loadConfig from "./config";导入的是index.ts的默认模块,即是export { loadFullConfig as default };这一段。然后我们可以看到loadFullConfig是在 "./full"文件的,所以接下来我们要分析full.ts做了什么。
full.ts
// 获取全部配置
export default gensync(function* loadFullConfig(
inputOpts: unknown,
): Handler<ResolvedConfig | null> {
const result = yield* loadPrivatePartialConfig(inputOpts);
if (!result) {
return null;
}
const { options, context, fileHandling } = result;
if (fileHandling === "ignored") {
return null;
}
const optionDefaults = {};
const { plugins, presets } = options;
if (!plugins || !presets) {
throw new Error("Assertion failure - plugins and presets exist");
}
const presetContext: Context.FullPreset = {
...context,
targets: options.targets,
};
const toDescriptor = (item: PluginItem) => {
const desc = getItemDescriptor(item);
if (!desc) {
throw new Error("Assertion failure - must be config item");
}
return desc;
};
const presetsDescriptors = presets.map(toDescriptor);
const initialPluginsDescriptors = plugins.map(toDescriptor);
const pluginDescriptorsByPass: Array<Array<UnloadedDescriptor>> = [[]];
const passes: Array<Array<Plugin>> = [];
const externalDependencies: DeepArray<string> = [];
const ignored = yield* enhanceError(
context,
function* recursePresetDescriptors(
rawPresets: Array<UnloadedDescriptor>,
pluginDescriptorsPass: Array<UnloadedDescriptor>,
): Handler<true | void> {
const presets: Array<{
preset: ConfigChain | null;
pass: Array<UnloadedDescriptor>;
}> = [];
for (let i = 0; i < rawPresets.length; i++) {
const descriptor = rawPresets[i];
if (descriptor.options !== false) {
try {
// eslint-disable-next-line no-var
var preset = yield* loadPresetDescriptor(descriptor, presetContext);
} catch (e) {
if (e.code === "BABEL_UNKNOWN_OPTION") {
checkNoUnwrappedItemOptionPairs(rawPresets, i, "preset", e);
}
throw e;
}
externalDependencies.push(preset.externalDependencies);
// Presets normally run in reverse order, but if they
// have their own pass they run after the presets
// in the previous pass.
if (descriptor.ownPass) {
presets.push({ preset: preset.chain, pass: [] });
} else {
presets.unshift({
preset: preset.chain,
pass: pluginDescriptorsPass,
});
}
}
}
// resolve presets
if (presets.length > 0) {
// The passes are created in the same order as the preset list, but are inserted before any
// existing additional passes.
pluginDescriptorsByPass.splice(
1,
0,
...presets.map(o => o.pass).filter(p => p !== pluginDescriptorsPass),
);
for (const { preset, pass } of presets) {
if (!preset) return true;
pass.push(...preset.plugins);
const ignored = yield* recursePresetDescriptors(preset.presets, pass);
if (ignored) return true;
preset.options.forEach(opts => {
mergeOptions(optionDefaults, opts);
});
}
}
},
)(presetsDescriptors, pluginDescriptorsByPass[0]);
if (ignored) return null;
const opts: any = optionDefaults;
mergeOptions(opts, options);
const pluginContext: Context.FullPlugin = {
...presetContext,
assumptions: opts.assumptions ?? {},
};
yield* enhanceError(context, function* loadPluginDescriptors() {
pluginDescriptorsByPass[0].unshift(...initialPluginsDescriptors);
for (const descs of pluginDescriptorsByPass) {
const pass: Plugin[] = [];
passes.push(pass);
for (let i = 0; i < descs.length; i++) {
const descriptor: UnloadedDescriptor = descs[i];
if (descriptor.options !== false) {
try {
// eslint-disable-next-line no-var
var plugin = yield* loadPluginDescriptor(descriptor, pluginContext);
} catch (e) {
if (e.code === "BABEL_UNKNOWN_PLUGIN_PROPERTY") {
// print special message for `plugins: ["@babel/foo", { foo: "option" }]`
checkNoUnwrappedItemOptionPairs(descs, i, "plugin", e);
}
throw e;
}
pass.push(plugin);
externalDependencies.push(plugin.externalDependencies);
}
}
}
})();
opts.plugins = passes[0];
opts.presets = passes
.slice(1)
.filter(plugins => plugins.length > 0)
.map(plugins => ({ plugins }));
opts.passPerPreset = opts.presets.length > 0;
return {
options: opts,
passes: passes,
externalDependencies: freezeDeepArray(externalDependencies),
};
});
在full.ts文件中,我们可以直接看默认导出模块的函数 loadFullConfig
这个函数包含的内容较多,我们可以画思维导图去分析。
loadFullConfig
这一part字数太多,让我们看@babel/core 源码分析(2)吧。