4

前言

之前对egg.js的整体设计有过自己的理解,在egg.js中方便的插件机制也是这个框架的一大亮点,本文主要就是从egg.js的插件开始,对node后台中的插件机制做一些分析和总结。

插件

在koa的项目中可以发现有大量的中间件的使用,常见的中间件可以用来做,鉴权,请求的参数合并,错误的统一处理等等。
中间件的特点大概总结一下就是:

  • 会对请求进行处理,并且影响在请求上
  • 中间件有自己的加载顺序,不同的顺序可能会带来不同的结果,整个koa形成了一个类似洋葱圈的模型

这样就会发现整个项目中确实有一些部分的功能不适合放到中间件中,比如与请求无关的功能,需要在初始化中就执行的功能,需要管理中间件功能的功能,这个时候就需要用到插件的功能了。
egg插件的特点:

  • 它包含了 Service、中间件、配置、框架扩展等等。
  • 它没有独立的 Router 和 Controller。

基本上插件和一个独立的应用没有多大的区别。

插件的加载

前面说到了egg的插件其实可以直接看作是一个小的应用,从目的上可以当作是一些公共功能的egg应用的抽象,那么这些插件究竟是如何被使用的呢?
首先是整个egg的应用的加载,可以从源码中看到分为了三个部分

/**
   * loadUnit is a directory that can be loaded by EggLoader, it has the same structure.
   *
   * The order of the loadUnits:
   *
   * 1. plugin
   * 2. framework
   * 3. app
   */
  getLoadUnits() {
    const dirs = this.dirs = [];

    if (this.orderPlugins) {
      for (const plugin of this.orderPlugins) {
        dirs.push({
          path: plugin.path,
          type: 'plugin',
        });
      }
    }

    // framework or egg path
    for (const eggPath of this.eggPaths) {
      dirs.push({
        path: eggPath,
        type: 'framework',
      });
    }

    // application
    dirs.push({
      path: this.options.baseDir,
      type: 'app',
    });
    
    return dirs;
  }

运行的结果

[ { path: '/Users/qitmac000458/Workspace/developer/node_modules/egg-session',
    type: 'plugin' },
  { path: '/Users/qitmac000458/Workspace/developer/node_modules/egg-security',
    type: 'plugin' },
  { path: '/Users/qitmac000458/Workspace/developer/node_modules/egg-jsonp',
    type: 'plugin' },
  { path: '/Users/qitmac000458/Workspace/developer/node_modules/egg-onerror',
    type: 'plugin' },
  { path: '/Users/qitmac000458/Workspace/developer/node_modules/egg-i18n',
    type: 'plugin' },
  { path: '/Users/qitmac000458/Workspace/developer/node_modules/egg-watcher',
    type: 'plugin' },
  { path: '/Users/qitmac000458/Workspace/developer/node_modules/egg-multipart',
    type: 'plugin' },
  { path: '/Users/qitmac000458/Workspace/developer/node_modules/egg-development',
    type: 'plugin' },
  { path: '/Users/qitmac000458/Workspace/developer/node_modules/egg-schedule',
    type: 'plugin' },
  { path: '/Users/qitmac000458/Workspace/developer/node_modules/egg-logrotator',
    type: 'plugin' },
  { path: '/Users/qitmac000458/Workspace/developer/node_modules/egg-static',
    type: 'plugin' },
  { path: '/Users/qitmac000458/Workspace/developer/node_modules/egg-view',
    type: 'plugin' },
  { path: '/Users/qitmac000458/Workspace/developer/node_modules/egg-sequelize',
    type: 'plugin' },
  { path: '/Users/qitmac000458/Workspace/developer/node_modules/egg-view-art',
    type: 'plugin' },
  { path: '/Users/qitmac000458/Workspace/developer/node_modules/egg-validate',
    type: 'plugin' },
  { path: '/Users/qitmac000458/Workspace/developer/node_modules/egg',
    type: 'framework' },
  { path: '/Users/qitmac000458/Workspace/developer', type: 'app' } ]

可以认为这每一个都是一个独立的app,之后就是如何把这些应用整合成一个app。那么简单的来看,只看app.js的整合吧

loadFile(filepath, ...inject) {
    if (!fs.existsSync(filepath)) {
      return null;
    }

    const ret = utils.loadFile(filepath);
    // function(arg1, args, ...) {}
    if (inject.length === 0) inject = [ this.app ];
    return isFunction(ret) ? ret(...inject) : ret;
 }
 loadCustomApp() {
    this.getLoadUnits()
      .forEach(unit => this.loadFile(path.join(unit.path, 'app.js')));
  },

再回忆一下app.js的一般写法,也就是

module.exports = app => {
  do something
};

这样插件中的每个app.js就都得已运行,并且运行顺序也就很容易知道,是和getLoadUnits的运行结果是一致的。插件其余部分的运行原理也是类似的。

总结

在了解了整个egg插件机制后,编写一个插件其实就变得很容易了,或者说后面可以从业务代码中直接沉淀出一整个功能作为一个插件。
egg的插件的加载几乎是复用了整个loader,将插件的功能于原本app的业务功能实现了解耦,而又保持了一个egg微应用的整体结构,这块的设计也是很值得学习的。


求实亭下
142 声望13 粉丝

有着深度学习技能的前端开发工程师