之前的几章可以看出,Egg.js的项目目录app/router.js中的业务代码,都是依靠egg-core模块中的加载器EggLoader来实现的。
前几章的讲解源码业务代码都集中在路由文件中,实际项目中的业务代码不只有路由文件,还有其他纵向分层和横向分层的源码文件,例如controller、service、middleware等。Egg.js是通过加载器将这些分层的项目业务代码、中间件和插件代码大部分功能注入到 this 、this.app或this.ctx对象里面。
Egg.js的加载内容,从实现内容可以分成两类,一类是加载项目业务代码文件,另一类是加载增强系统能力的代码文件或模块。
- 业务代码文件
- 项目业务配置
config - 项目路由层
router - 项目控制层
controller - 项目业务层
service - 其他业务代码
- 项目业务配置
- 系统增强代码文件或模块
- 系统中间件
middleware - 系统插件
plugin - 其他系统增强
- 系统中间件
然而所有完整的加载器流程很复杂,这一章是讲最简单加载器的实现,就拿controller加载的实现来分析最简加载器。
- 应用进程启动
- 启动应用加载器
- 加载
controller- 加载
controller目录下所有文件 - 将加载后的内容挂载在
this.app上
- 加载
- 加载
demo传送门 https://github.com/chenshenhai/eggjs-note/tree/master/demo/001-mini-loader
demo启动
cd 001-mini-loader
npm i
# 启动demo
npm run startEggApplication初始化构建时候,就通过 this.loader.load() 方法触发了应用进程加载器 AppWorkerLoader加载所有内容方法
demo/001-mini-loader/lib/egg/index.js
class EggApplication extends EggCore {
constructor(options) {
// ....
this.loader.loadAll();
// ...
}
// ...
}AppWorkerLoader 继承了Egg.js的内置基础加载器EggLoader类,并且定义和实现了触发加载所有内容的方法loadAll(),其中触发了继承自EggLoader类的controller加载方法 this.loadRouter()
demo/001-mini-loader/lib/egg/index.js
class AppWorkerLoader extends EggLoader {
loadAll() {
this.loadController();
this.loadRouter();
}
}基础加载器EggLoader类,所有挂载在该类上的混合方法,例如this.loadController从egg-core/lib/loader/mixin/controller.js 挂载到 EggLoader上的,都可以调用该类的方法,其中controller就是调用了 EggLoader.loadToApp(...)方法,将项目目录app/controller/*.js的js文件都加载起来,挂载在this.app对象里面
demo/001-mini-loader/lib/egg-core/lib/loader/mixin/controller.js
module.exports = {
// ...
loadController(opt) {
opt = Object.assign({
directory: path.join(this.options.baseDir, 'app/controller'),
}, opt);
const controllerBase = opt.directory;
this.loadToApp(controllerBase, 'controller', opt);
},
// ...
};demo/001-mini-loader/lib/egg-core/lib/loader/egg_loader.js
const FileLoader = require('./file_loader');
class EggLoader {
constructor(options) {
this.options = options;
this.app = this.options.app;
}
// ...
loadToApp(directory, property, opt) {
const target = this.app[property] = {};
opt = Object.assign({}, {
directory,
target,
}, opt);
new FileLoader(opt).load();
}
// ...
}
const loaders = [
// ...
require('./mixin/controller'),
];
for (const loader of loaders) {
Object.assign(EggLoader.prototype, loader);
}EggLoader.loadFile(...) 的具体实现是通过 FileLoader 类来实现的。
具体实现是输入 controller的相对地址和挂载的目标对象this.app.controller。
FileLoader 会根据 controller的相对地址生成绝对地址来获取 app/controller 目录下的所有js文件,这里考虑到代码最简实现,只读取 app/controller 目录下第一层级子目录,先不考虑文件名格式规范,也不考虑文件export出的内容,默认都是JSON对象,对象属性都是Async Function。
demo/001-mini-loader/lib/egg-core/lib/loader/file_loader.js
class FileLoader {
constructor(options) {
this.options = options
}
load() {
const items = this.parse();
const target = this.options.target;
for (const item of items) {
target[item.properties[0]] = item.exports;
}
return target;
}
parse() {
const files = this.options.match || [ '**/*.js' ];
const directory = this.options.directory;
const items = [];
const filepaths = globby.sync(files, { cwd: directory });
for (const filepath of filepaths) {
const fullpath = path.join(directory, filepath);
if (!fs.statSync(fullpath).isFile()) continue;
const properties = [ filepath.substring(0, filepath.lastIndexOf('.')).split('/').pop() ];
const exports = require(fullpath);
if (exports == null) continue;
items.push({ fullpath, properties, exports });
}
return items;
}
}- Egg.js 加载器都是 以
EggLoader类为基础的 - 文件加载和挂载核心是
FileLoader类 - 并不是所有加载和挂载都用到
FileLoaderrouter的加载就很特殊,是单独的硬编码的写入到this和this.app中
app/controller层是挂载到this.app, 但是并不代发所有内容都挂载到this.app中,有些是直接挂载到this或者this.ctx中