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',
});