umi源码-04run方法收集插件和插件集

193 阅读1分钟

umi 是一个基于插件和插件机的架构,所有模块和功能通过插件的方式组合在一起,架构清晰、可插拔、扩展性好。类似有插件或者插件集的代码组织方法还有很多项目在用,比如 babel、webpack、egg等等。

插件入口

代码比较简单,引入一些实参

  • cwd 根目录(命令执行所在的目录)
  • pkg 根 pacKage.json
  • plugins是插件,包含必须的插件 generatePlugin 和从外部引入的插件 plugins。这个在《02.从一个命令说起 umi dev》的 service中有解释
  • presets 是插件集,包含在《02.从一个命令说起 umi dev》的 service中引入的 @umijs/preset-umiservicePlugin
const { plugins, presets } = Plugin.getPluginsAndPresets({
  cwd: this.cwd,
  pkg,
  plugins: [require.resolve('./generatePlugin')].concat(
    this.opts.plugins || [],
  ),
  presets: [require.resolve('./servicePlugin')].concat(
    this.opts.presets || [],
  ),
  userConfig: this.userConfig,
  prefix,
});

插件的核心方法

稍微复杂一点,但是也很好理解。主要是获取分散在各处的插件或者插件机,然后实例化。

static getPluginsAndPresets(opts: {
                            cwd: string;
pkg: any;
userConfig: any;
plugins?: string[];
presets?: string[];
prefix: string;
}) {
  function get(type: 'plugin' | 'preset') {
    const types = `${type}s` as 'plugins' | 'presets';
    return [
      // opts getPluginsAndPresets实参传进来的 presets 或者 plugins
      ...(opts[types] || []),
      // env 环境变量中传进来的 presets 或者 plugins 例如:umi_plugins=xxx
      ...(process.env[`${opts.prefix}_${types}`.toUpperCase()] || '')
      .split(',')
      .filter(Boolean),
      // dependencies
      // ...Object.keys(opts.pkg.devDependencies || {})
      //   .concat(Object.keys(opts.pkg.dependencies || {}))
      //   .filter(Plugin.isPluginOrPreset.bind(null, type)),
      // user config .umrc.ts 中的 plugins:[] 或者 presets: [],
      ...(opts.userConfig[types] || []),
    ].map((path) => {
      assert(
        typeof path === 'string',
        `Invalid plugin ${path}, it must be string.`,
      );
      let resolved;
      // 这一步是为了获取插件所在的路径
      try {
        resolved = resolve.sync(path, {
          basedir: opts.cwd,
          extensions: ['.tsx', '.ts', '.mjs', '.jsx', '.js'],
        });
      } catch (_e) {
        throw new Error(`Invalid plugin ${path}, can not be resolved.`);
      }

      return new Plugin({
        path: resolved,
        type,
        cwd: opts.cwd,
      });
    });
  }

  return {
    presets: get('preset'),
    plugins: get('plugin'),
  };
}

我们看返回值 return {presets: get('preset'), plugins: get('plugin'),},重点就是get方法,通过get 方法处理插件和插件机。最终通过 new Plugin() 方法实例化插件。

我们先做一个简单的总结,稍后再看 new Plugin() 综合《02.从一个命令说起 umi dev》的 service中和本篇介绍,umi中的插件来源有以下几个方面

  • .umirc.ts中的 plugins或者 presets
  • 项目根目录下的 plugins.js 或者 plugins.ts 文件
  • 命令行运行时获取的命令 例如:umi_plugins=xxx 或者 umi_presets=xxx
  • 还有generatePlugin``@umijs/preset-umiservicePlugin插件,是umi自身需要的插件或者插件集,用户无法手动配置。

new Plugin()

首先我们要知道new 一个对象时,对象中的 constructor方法会在 new 的时候执行,初始化一些信息。这里面有个 this.apply 方法。初始化的时候获取了插件对应的代码内容。__esModule 是一个市面上统一遵守的模块化打包标识,为了解决 Commonjs 的 module.exports 与 ESM 对应问题。默认使用 esModule。apply返回的 ret.default 或者 ret 其实就是插件带具体代码。因为umi的插件都是方法,所以执行 this.apply() 的返回值,我们可以理解为是一个函数,函数的内容就是插件本身。后面会用到这个 apply方法,这个后面再说。

this.apply = () => {
  register.register({
    implementor: esbuild,
    exts: ['.ts', '.mjs'],
  });
  register.clearFiles();
  let ret;
  try {
    ret = require(this.path);
  } catch (e: any) {
    throw new Error(
      `Register ${this.type} ${this.path} failed, since ${e.message}`,
    );
  } finally {
    register.restore();
  }
  // use the default member for es modules
  return ret.__esModule ? ret.default : ret;
};