1. 程式人生 > >Egg 學習筆記-原始碼分析1

Egg 學習筆記-原始碼分析1

1. Egg在呼叫controller/service資料夾下的模組時,不需要require,如何實現的?

在原生Node/Koa中,當我們需要呼叫其他模組時,需要require, 非常繁瑣。(java體系都是auto import)

但在Egg中,我們可以通過app.controller.檔名的形式直接呼叫。

猜想,是不是在app資料夾下任意寫一個資料夾xxx,再在xxx下寫一個yyy.js, 就可以實現app.xxx.yyy直接呼叫?實踐結果是No。

先說結論,app/加資料夾的方式是實現不了上述的目的的,但在controller/service資料夾裡巢狀資料夾,可以實現。這個在官方文件裡沒有直接寫明。

原理:

Egg在啟動worker程序時,會執行loadController, loadService等方法,遍歷解析app/controller, app/service資料夾下的所有js檔案,把匯出的模組進行掛載, 對應實現模組是egg-core。

egg-core/lib/loader/mixin/下面有如下的檔案:
controller.js,service,js,extend.js,middleware.js, 他們分別對應負責載入controller,service, extend等

controller.js,

opt = Object.assign({
  caseStyle: 'lower',
  directory: path.join(this.options.baseDir, 'app/controller'),
  initializer: (obj, opt) => {
  ...忽略其他程式碼...
  const controllerBase = opt.directory;

  this.loadToApp(controllerBase, 'controller', opt);
  this.options.logger.info('[egg:loader] Controller loaded: %s', controllerBase);
},

看到loadToApp的呼叫,傳參指定了app/controller資料夾

loadToApp最後會呼叫file_loader.js裡的load,關鍵幾行程式碼如下:

let files = this.options.match || [ '**/*.js' ];
const filepaths = globby.sync(files, { cwd: directory });
const properties = filepath.substring(0, filepath.lastIndexOf('.')).split('/');

上述程式碼實現了把巢狀的js檔案全部解析和掛載。註釋也很清晰:

檔案路徑app/service/foo/bar.js 會轉換為 service.foo.bar

需要注意的是,controller是loadToApp,也就是載入到應用級別, service是loadToContext, 也就是載入到請求級別的物件。所以在router裡是通過app.controller方式引用,而在controler裡使用service是this.ctx.service。這在Egg文件Loader一節裡也有說明,service是請求中首次訪問時才進行例項化,並快取下來。這裡是通過定義getter來實現

 Object.defineProperty(app.context, property, {
  get() {
    // distinguish property cache,
    // cache's lifecycle is the same with this context instance
    // e.x. ctx.service1 and ctx.service2 have different cache
    if (!this[CLASSLOADER]) {
      this[CLASSLOADER] = new Map();
    }
    const classLoader = this[CLASSLOADER];

    let instance = classLoader.get(property);
    if (!instance) {
      instance = getInstance(target, this);
      classLoader.set(property, instance);
    }
    return instance;
  },
});

另外,Egg-loader裡還學到使用mixin實現多繼承。

當使用egg-Sequelize實現ORM時,app/model下的js也可以被自動讀取.

查閱原始碼知道,這些其實是通過在外掛裡呼叫Egg提供的low-level API實現的

Sequelize也是類似的

function loadModel(app) {
    const modelDir = path.join(app.baseDir, 'app/model');
     app.loader.loadToApp(modelDir, MODELS, {
     inject: app,
     caseStyle: 'upper',
    ignore: 'index.js',
});