umi renderer-react

542 阅读2分钟

PluginManager

代码位置/umi/src/client/plugin.ts,该方法交由浏览器运行

一个简单前端插件管理,使得使用方法和后台服务部分一致。例如我们在app.ts中export的方法都会注册到这里,用于干预运行时

// 一个简单前端插件管理,使得使用方法和后台服务部分一致
// 例如使用app.ts来干预运行时
export class PluginManager {
  // 指定插件hooks的key必须在validKeys范围内
  opts: { validKeys: string[] };
  // 存储校验成功后的hook
  hooks: {
    [key: string]: any;
  } = {};
  constructor(opts: { validKeys: string[] }) {
    this.opts = opts;
  }

  // 插入插件
  // {plugin.apply的key, plugin的apply[key]的所有}
  // apply[key]可以是数组,也可以直接返回属性
  register(plugin: IPlugin) {
    assert(plugin.apply, `plugin register failed, apply must supplied`);
    Object.keys(plugin.apply).forEach((key) => {
      // 必须在validKeys中注册过
      assert(
        this.opts.validKeys.indexOf(key) > -1,
        `register failed, invalid key ${key} ${
          plugin.path ? `from plugin ${plugin.path}` : ''
        }.`,
      );
      this.hooks[key] = (this.hooks[key] || []).concat(plugin.apply[key]);
    });
  }

  /**
   * 获取符合keyWithDot的插件apply集合
   * keyWithDot: a.b.c.d
   * 某插件 apply: {a:{b:{c:{d: function fn1(){}}}}}
   * 某插件 apply: {a:[{b:{c:{d: function fn2(){}}}}, {b:{c:{d: function fn3(){}}}}]}
   * 此时返回的是[fn1, fn2, fn3]
   */
  getHooks(keyWithDot: string) {
    const [key, ...memberKeys] = keyWithDot.split('.');
    let hooks = this.hooks[key] || [];
    if (memberKeys.length) {
      hooks = hooks
        .map((hook: any) => {
          try {
            let ret = hook;
            for (const memberKey of memberKeys) {
              ret = ret[memberKey];
            }
            return ret;
          } catch (e) {
            return null;
          }
        })
        .filter(Boolean);
    }
    return hooks;
  }

  // 依次执行hooks对应的方法
  // 并将返回结果合并之后传递给下一个hook
  // 可以异步执行
  applyPlugins({
    key,
    type,
    initialValue,
    args,
    async,
  }: {
    key: string;
    type: ApplyPluginsType;
    initialValue?: any;
    args?: object;
    async?: boolean;
  }) {
    const hooks = this.getHooks(key) || [];
    // ...
  }

  // 创建PluginManager,并初始化插件
  static create(opts: { validKeys: string[]; plugins: IPlugin[] }) {
    const pluginManager = new PluginManager({
      validKeys: opts.validKeys,
    });
    opts.plugins.forEach((plugin) => {
      pluginManager.register(plugin);
    });
    return pluginManager;
  }
}

模版代码分析

  • plugin.ts: /preset-umi/templates/plugin.tpl
  • umi.ts: /preset-umi/templates/umi.tpl

plugin

  • 通过外部的path(app.ts的文件路径)获取到app.ts文件内的内容,然后成初始化插件
  • getValidKeys预设插件的运行时配置,我们熟悉的有render、onRouteChange等
  • createPluginManager创建插件实例

umi

这块是前端入口文件,自执行render函数

  • 创建插件对象
  • 获取打平的路由列表,同时允许app.ts的patchRoutes中获取使用
  • render的初始化传入一个函数,允许外部在合适的时机才渲染app
  • 函数中初始化一些render配置,比如渲染根结点等
  • renderClient为packages/renderer-react的渲染函数,可以通过modifyRendererPath修改为renderer-vue

renderClient

渲染react应用,代码位置/renderer-react/src/browser.tsx

  • createClientRoutes: 创建带有层级的路由信息
  • patchClientRoutes: 允许渲染前修改路由内容,增删改等
  • 依次调用如下方法,可以在渲染组件上层再嵌套组件,如Provider
 for (const key of [
    // Lowest to the highest priority
    'innerProvider',
    'i18nProvider',
    'accessProvider',
    'dataflowProvider',
    'outerProvider',
    'rootContainer',
  ]) {
    rootContainer = opts.pluginManager.applyPlugins({
      type: 'modify',
      key: key,
      initialValue: rootContainer,
      args: {},
    });
  }
  • Browser中监听路由变化去加载对应的文件
  • 随后将Browser挂在到rootElement上