babel查找并加载plugin和preset

发布时间:2024年01月05日

根据配置找到相应的 plugin 和 preset

项目的配置文件 .babel.config._ 或者 .babelrc._

module.exports=(api)=>{
  api.cache(true);
  return{
    presets :[
      [
        "@babel/env",
        {
          "useBuiltIns": 'entry',
          "corejs": "3.33.3"
        }
      ]
    ],
    plugins:[]
  }
}

根据命令 npx babel src/index.js --out-file index.compiled.js 经过**@babel/cli**定位到 @babel/core 下的 transform-file.ts 文件

preset和plugin的大体加载流程
presets和plugins加载流程

transform-file.ts

import loadConfig from "./config/index.ts";

const transformFileRunner = gensync(function* (
  filename: string,
  opts?: InputOptions,
): Handler<FileResult | null> {
  const options = { ...opts, filename };

  // 1. 加载项目的配置文件,解析presets,加载所有的plugins
  /**
   * {
   * 	externalDependencies:[],
   * 	options:{},
   * 	passes:[[..plugins]]
   * }
   */
  const config: ResolvedConfig | null = yield* loadConfig(options);
  if (config === null) return null;

  const code = yield* fs.readFile(filename, "utf8");
  return yield* run(config, code);
});

loadConfig 方法:加载项目的插件和预设,其中 options 对象:

const options={
  filename:'src/index.js'
  sourceFileName:'src/index.js'
}

loadConfig 方法来自 full.ts 的 loadFullConfig 方法

full.ts

import loadPrivatePartialConfig from "./partial.ts";
export default gensync(function* loadFullConfig(
  inputOpts: unknown,
): Handler<ResolvedConfig | null> {

  // 1. result 存在 options ={plugins,presets},数组中每个plugin和preset存在value属性值 为其库的导出函数
  const result = yield* loadPrivatePartialConfig(inputOpts);
  const { options, context, fileHandling } = result;
  ...
  const { plugins, presets } = options;
  ...
  // 没有进行加载的 presets的Descriptors
  const presetsDescriptors = presets.map(toDescriptor);
  const initialPluginsDescriptors = plugins.map(toDescriptor);
  const pluginDescriptorsByPass: Array<Array<UnloadedDescriptor<PluginAPI>>> = [
    [],
  ];
  const passes: Array<Array<Plugin>> = [];
  ...

  const externalDependencies: DeepArray<string> = [];

  // 递归其中的所有preset 包括其中嵌套的presets,统计presets中的所有plugins
  const ignored = yield* enhanceError(
    context,

    // 递归加载 preset,统计出所有的 plugins
    function* recursePresetDescriptors(
      rawPresets: Array<UnloadedDescriptor<PresetAPI>>,
      pluginDescriptorsPass: Array<UnloadedDescriptor<PluginAPI>>,
    ): Handler<true | void> {
      const presets: Array<{
        preset: ConfigChain | null;
        pass: Array<UnloadedDescriptor<PluginAPI>>;
      }> = [];

      for (let i = 0; i < rawPresets.length; i++) {
        const descriptor = rawPresets[i];
        if (descriptor.options !== false) {
          try {
            // eslint-disable-next-line no-var
            // 加载解析预设,返回配置对象{chain:{plugins:[..],presets:[...]}}
            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);

          // 预设 通常按照相反的顺序执行,从后往前执行。但是指定自己的通行,他们会运行在预设之后
          if (descriptor.ownPass) {
            presets.push({ preset: preset.chain, pass: [] });
          } else {
            // 预设数组,每个预设 都包括preset属性,其为对象包括该预设的解析后的  plugins 和 presets
            /**
             *  presets=[
             *    {
             *      preset:{
             *        plugins:[...],
             *        presets:[...]
             *      }
             *      pass:[]
             *    }
             *  ]
             */
            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;

          // 记录本预设的 所有plugin,放在 pluginDescriptorsByPass[0]中
          pass.push(...preset.plugins);
          // 传入 递归recursePresetDescriptors方法中
          const ignored = yield* recursePresetDescriptors(preset.presets, pass);
          if (ignored) return true;

          preset.options.forEach(opts => {
            mergeOptions(optionDefaults, opts);
          });
        }
      }
    },
  )(presetsDescriptors, pluginDescriptorsByPass[0]);

  ...

  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 = 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);
        }
      }
    }
  })();

  return {
    options: opts,
    passes: passes,
    externalDependencies: freezeDeepArray(externalDependencies),
  };
});

/*******************************preset加载执行*******************************/
// 生成一个配置对象,该对象将作为新嵌套配置的根
// 通过预设descriptor,加载其中的插件和预设,生成配置对象{chain,externalDependencies},其中chain为查找的关键字,通过buildPresetChain方法 还会记载其中的 presets和plugins。
// 通过 recursePresetDescriptors 方法 一直调用并生成配置对象
function* loadPresetDescriptor(
  descriptor: UnloadedDescriptor<PresetAPI>,
  context: Context.FullPreset,
): Handler<{
  chain: ConfigChain | null;
  externalDependencies: ReadonlyDeepArray<string>;
}> {

  const preset = instantiatePreset(
    // 执行 descriptor 的value函数(即 预设库的加载)
    // 以 @babel/env 为例,即执行export default方法 返回其中的plugins等参数
    yield* presetDescriptorLoader(descriptor, context),
  );
  validatePreset(preset, context, descriptor);
  return {
    // 进一步 加载预设解析后 存在的plugins 和 presets
    chain: yield* buildPresetChain(preset, context),
    externalDependencies: preset.externalDependencies,
  };
}

/*******************************plugin加载执行*******************************/

// 返回一个plugin对象{visitor,pre,post}
function* loadPluginDescriptor(descriptor, context) {
  if (descriptor.value instanceof _plugin.default) {
    if (descriptor.options) {
      throw new Error("Passed options to an existing Plugin instance will not work.");
    }
    return descriptor.value;
  }
  return yield* instantiatePlugin(yield* pluginDescriptorLoader(descriptor, context), context);
}

loadPrivatePartialConfig 来自 partial.ts 的导出函数

partial.ts

import { buildRootChain } from "./config-chain.ts";

export default function* loadPrivatePartialConfig(
  inputOpts: unknown,
): Handler<PrivPartialConfig | null> {

  ...
  // 同 loadConfig方法的参数 options 一致
  const args = inputOpts ? validate("arguments", inputOpts) : {};
  ...

  // 经过上面省略的步骤 获取项目的基本路径
  const context: ConfigContext = {
    filename, // 需要执行转换的文件路径,即:src/index.js 的绝对路径
    cwd: absoluteCwd, // 脚本文件所在的路径,即:项目的根路径 本项目babelSimpleDemo
    root: absoluteRootDir,// 项目根路径,即:babelSimpleDemo
    envName,
    caller,
    showConfig: showConfigPath === filename,// false
  };

  // 根据 文件路径信息 加载配置链
  const configChain = yield* buildRootChain(args, context);
  ...
  const options: NormalizedOptions = {
    ...merged,
    targets: resolveTargets(merged, absoluteRootDir),
    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 =>
      // 构建 configItem 有value属性
      createItemFromDescriptor(descriptor),
    ),
    presets: configChain.presets.map(descriptor =>
      // 构建 configItem 有value属性
      createItemFromDescriptor(descriptor),
    ),
  };

  return {
    options,
    context,
    fileHandling: configChain.fileHandling,
    ignore: configChain.ignore,
    babelrc: configChain.babelrc,
    config: configChain.config,
    files: configChain.files,
  };
}

buildRootChain 来自 config-chain.ts 文件

config-chain.ts

加载 plugins 和 presets,有两种方式查找,可以同时存在

  • 通过命令行或者代码直接传入
    npx babel --plugins @babel/plugin-transform-arrow-functions  src/index.js --out-file index.compiled.js
    
  • 通过配置文件 babel.config.* 和.babelrc.
    见文件顶部配置代码
    
export function* buildRootChain(
  opts: ValidatedOptions,
  context: ConfigContext,
): Handler<RootConfigChain | null> {
  let configReport, babelRcReport;
  const programmaticLogger = new ConfigPrinter();

  // 通过代码调用或者命令行直接指定的plugins和presets配置
  // 返回 plugin 和 preset 的库的导出函数export.modules  以及 browsersList
  // 命令行 npx babel --plugins @babel/plugin-transform-arrow-functions  src/index.js --out-file index.compiled.js
  const programmaticChain = yield* loadProgrammaticChain(
    {
      options: opts,
      dirname: context.cwd,
    },
    context,
    undefined,
    programmaticLogger,
  );
  if (!programmaticChain) return null;
  const programmaticReport = yield* programmaticLogger.output();

  let configFile;
  // 命令行或者代码程序中 是否指定了 configFile,本案例中没有指定
  if (typeof opts.configFile === "string") {
    configFile = yield* loadConfig(
      opts.configFile,
      context.cwd,
      context.envName,
      context.caller,
    );
  } else if (opts.configFile !== false) {
    // 加载根目录配置,即加载 babel.config.[js|cjs|mjs|json|cts]
    configFile = yield* findRootConfig(
      context.root,
      context.envName,
      context.caller,
    );
  }

  // 该测试项目 使用的是 .babelrc.js,所以 上面的 configFile 不存在,省略下面代码
  ...

  let ignoreFile, babelrcFile;
  let isIgnored = false;
  const fileChain = emptyChain();
  // resolve all .babelrc files
  if (
    (babelrc === true || babelrc === undefined) &&
    typeof context.filename === "string"
  ) {

    // 根据filename 逐级向上查找路径并返回
    // 主要是找到包的元数据(路径,package.js信息),以便来查找问价下面的所有的 .babelrc.* 配置文件
    const pkgData = yield* findPackageData(context.filename);

    if (
      pkgData &&
      babelrcLoadEnabled(context, pkgData, babelrcRoots, babelrcRootsDirectory)
    ) {

      // 内部 根据pkgData.directories数组 循环查找配置 babelrc.*
      //(见configuration.ts中findRelativeConfig 和 loadOneConfig)

      /**
       * babelrcFile 为:
       *  {
       *       filepath,
       *       dirname,
       *       options:{
       *         plugins[],
       *         presets:[['@babel/env',{core-js:3.xx,useBuiltIns:'xx'}]]
       *       }
       *   }
       *
       */
      ({ ignore: ignoreFile, config: babelrcFile } = yield* findRelativeConfig(
        pkgData,
        context.envName,
        context.caller,
      ));

      ...

      if (babelrcFile && !isIgnored) {
        // 有效的配置文件 和 babelrcFile 一样
        const validatedFile = validateBabelrcFile(babelrcFile);
        const babelrcLogger = new ConfigPrinter();

        // result 为: {files,options,plugins,presets},
        // 其中包含plugin 和 preset 库的 导出函数  以及 browsersList
        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);
      }
    }
  }

  // Insert file chain in front so programmatic options have priority
  // over configuration file chain items.
  const chain = mergeChain(
    mergeChain(mergeChain(emptyChain(), configFileChain), fileChain),
    programmaticChain,
  );

  // dedupDescriptors 去重
  // 返回包括:各种plugin 和 preset的库的 导出函数 (通过 require 加载模块得到的函数)
  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,
  };
}

/*********************************** 1. 来自 full.ts的调用,主要是加载
解析后的预设中存在的presets 和 plugins***************************** */
export function* buildPresetChain(
  arg: PresetInstance,
  context: any,
): Handler<ConfigChain | null> {
  const chain = yield* buildPresetChainWalker(arg, context);
  if (!chain) return null;

  return {
    plugins: dedupDescriptors(chain.plugins),
    presets: dedupDescriptors(chain.presets),
    options: chain.options.map(o => normalizeOptions(o)),
    files: new Set(),
  };
}

export const buildPresetChainWalker = makeChainWalker<PresetInstance>({
  root: preset => loadPresetDescriptors(preset),
  env: (preset, envName) => loadPresetEnvDescriptors(preset)(envName),
  overrides: (preset, index) => loadPresetOverridesDescriptors(preset)(index),
  overridesEnv: (preset, index, envName) =>
    loadPresetOverridesEnvDescriptors(preset)(index)(envName),
  createLogger: () => () => {}, // Currently we don't support logging how preset is expanded
});

const loadPresetDescriptors = makeWeakCacheSync((preset: PresetInstance) =>
  buildRootDescriptors(preset, preset.alias, createUncachedDescriptors),
);

/***********************************代码或者命令行传入的plugins 和presets***************************** */
// 内部调用 root方法 -- buildRootDescriptors-- 最终执行的是 传入的 createCachedDescriptors
const loadProgrammaticChain = makeChainWalker({
  root: input => buildRootDescriptors(input, "base", createCachedDescriptors),
  ...
});

// 同 loadConfig方法的参数 options 一致,其中plugins和presets来自 代码传入或者命令行
function buildRootDescriptors(
  { dirname, options }: Partial<ValidatedFile>,
  alias: string,
  descriptors: (
    dirname: string,
    options: ValidatedOptions,
    alias: string,
  ) => OptionsAndDescriptors,
) {
  return descriptors(dirname, options, alias);
}

/***********************************配置文件查找的的plugins 和presets***************************** */

function* loadFileChain(
  input: ValidatedFile,
  context: ConfigContext,
  files: Set<ConfigFile>,
  baseLogger: ConfigPrinter,
) {
  const chain = yield* loadFileChainWalker(input, context, files, baseLogger);
  chain?.files.add(input.filepath);

  return chain;
}

const loadFileChainWalker = makeChainWalker<ValidatedFile>({
  root: file => loadFileDescriptors(file),
  env: (file, envName) => loadFileEnvDescriptors(file)(envName),
  overrides: (file, index) => loadFileOverridesDescriptors(file)(index),
  overridesEnv: (file, index, envName) =>
    loadFileOverridesEnvDescriptors(file)(index)(envName),
  createLogger: (file, context, baseLogger) =>
    buildFileLogger(file.filepath, context, baseLogger),
});

const loadFileDescriptors = makeWeakCacheSync((file: ValidatedFile) =>

  // 其中 file 就是 configFile/ValidatedFile
  buildRootDescriptors(file, file.filepath, createUncachedDescriptors),
);

其中 代码和命令行执行的加载 plugins 和 presets 以及 配置文件执行加载 plugins 和 presets
最终执行都为 buildRootDescriptors(注:createCachedDescriptorscreateUncachedDescriptors

import {
  createCachedDescriptors,
  createUncachedDescriptors,
} from "./config-descriptors.ts";

其中都 引用了 files下plugins.ts 的 loadPreset 和loadPreset方法
export {
  loadPlugin,
  loadPreset,
  resolvePlugin,
  resolvePreset,
} from "./plugins.ts";

config-chain.ts 文件中的 loadConfig、findRootConfig、findPackageData、findRelativeConfig 方法来自 files 文件夹
注:files.ts相关配置文件加载

插件加载顺序:

  • 插件在 Presets 前运行。
  • 插件顺序从前往后排列。
  • Preset 顺序是颠倒的(从后往前)。
{
  "plugins": ["transform-decorators-legacy", "transform-class-properties"]
}

先执行 transform-decorators-legacy ,在执行 transform-class-properties

{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}

将按如下顺序执行: 首先是 @babel/preset-react,然后是 @babel/preset-env。

文章来源:https://blog.csdn.net/GRY_YJ/article/details/135402182
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。