@babel/core 源码分析(2)

紧接着上一篇 @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方法去重后返回最后的配置。

相关推荐
吕彬-前端7 分钟前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱9 分钟前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai18 分钟前
uniapp
前端·javascript·vue.js·uni-app
bysking1 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓2 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_4112 小时前
无网络安装ionic和运行
前端·npm
理想不理想v2 小时前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云2 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:137971205872 小时前
web端手机录音
前端
齐 飞2 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb