egg.js插件分析

前言

以前對egg.js的總體設計有過本身的理解,在egg.js中方便的插件機制也是這個框架的一大亮點,本文主要就是從egg.js的插件開始,對node後臺中的插件機制作一些分析和總結。node

插件

在koa的項目中能夠發現有大量的中間件的使用,常見的中間件能夠用來作,鑑權,請求的參數合併,錯誤的統一處理等等。
中間件的特色大概總結一下就是:json

  • 會對請求進行處理,而且影響在請求上
  • 中間件有本身的加載順序,不一樣的順序可能會帶來不一樣的結果,整個koa造成了一個相似洋蔥圈的模型

這樣就會發現整個項目中確實有一些部分的功能不適合放到中間件中,好比與請求無關的功能,須要在初始化中就執行的功能,須要管理中間件功能的功能,這個時候就須要用到插件的功能了。
egg插件的特色:session

  • 它包含了 Service、中間件、配置、框架擴展等等。
  • 它沒有獨立的 Router 和 Controller。

基本上插件和一個獨立的應用沒有多大的區別。app

插件的加載

前面說到了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;
  }

運行的結果koa

[ { 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的通常寫法,也就是jsonp

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

這樣插件中的每一個app.js就都得已運行,而且運行順序也就很容易知道,是和getLoadUnits的運行結果是一致的。插件其他部分的運行原理也是相似的。this

總結

在瞭解了整個egg插件機制後,編寫一個插件其實就變得很容易了,或者說後面能夠從業務代碼中直接沉澱出一整個功能做爲一個插件。
egg的插件的加載幾乎是複用了整個loader,將插件的功能於本來app的業務功能實現瞭解耦,而又保持了一個egg微應用的總體結構,這塊的設計也是很值得學習的。spa

相關文章
相關標籤/搜索