@babel/core 源码分析 (1)

261 阅读5分钟

接上一章@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的目录主要分为几个主要部分:

  1. config:主要是处理babel加载配置的问题。
  2. errors:处理错误。
  3. parse:引入@babel/parse包来生成AST。
  4. tools:工具文件
  5. transformation:引入@babel/traverse来转换
  6. index.ts: 入口文件,导出各种api
  7. parse.ts: 定义parse等类型,引入parse,导出运行parse 的 syncasyncerrBack等api。
  8. transform-ast.ts: 定义TransformFromAst等类型,引入 transform ,导出运行transformFromAst的 syncasyncerrBack等api,参数为AST。
  9. transform-file-browser.ts: 替换掉在浏览器环境下运行不支持的函数,并抛出错误。
  10. transform-file.ts: 定义transformFileType等类型,引入transform,导出transformFile的 syncasyncerrBack等api,参数为File。
  11. transform.ts:定义Transform等类型,引入transform,导出Transform的 syncasyncerrBack等api,参数为Code。

上一章最后说到,他最后会调用@babel/core的transformFile函数。
为什么是调用这个呢?
因为我们的启动的命令是babel src --out-dir lib,传入的参数是File,所以会调用transformFile函数。 其实transform-ast.tstransform-file.tstransform.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 image.png 这一part字数太多,让我们看@babel/core 源码分析(2)吧。