开发egg-helper插件目的
- 所有的工具函数维护在 app/util 文件内,在使用时需要手动require,如果多个文件使用,需要多个require,致使业务代码实现不优雅
- 在工具函数内部无法直接读取配置文件,通常是使用传参的方式
- Egg也提供Helper框架扩展,但是需将工具函数维护在 app/extend/helper.js 文件内,我更倾向于单独维护
开发经历
第一版
使用egg官方文档提供的loadToContext方法,将 app/helper 内所有文件挂载到ctx.helper对象
loadToContext官方文档,请戳这里
代码实现
// --------egg-helper/app.js -------
module.exports = app => {
const dir = app.loader.getLoadUnits().map(unit => {
return path.join(unit.path, 'app/helper');
});
app.loader.loadToContext(dir, 'helper', {
inject: app,
call: true,
});
};
...
缺点:
- 覆盖掉原有的ctx.helper对象(这里可以选择修改挂载属性名称来避免覆盖,但为了和egg保持一致,所以未选择此方案)
源码学习
打开egg-core工程,根据package.json找到入口文件
...
// -------- egg-core/index.js -------
module.exports = {
EggCore,
EggLoader,
BaseContextClass,
utils,
};
在index.js文件内,export出四个对象
EggCore:
egg核心类
- 继承于koa Application
- 初始化egg Application的对象方法和属性
// -------- egg-core/lib/egg.js -------
...
class EggCore extends KoaApplication {
constructor() {
...
const Loader = this[EGG_LOADER];
assert(Loader, "Symbol.for('egg#loader') is required");
this.loader = new Loader({ // 实例化loder,即EggLoader
baseDir: options.baseDir,
app: this,
plugins: options.plugins,
logger: this.console,
serverScope: options.serverScope,
});
const Controller = this.BaseContextClass;
this.Controller = Controller; // 定义Controller使用基类
const Service = this.BaseContextClass;
this.Service = Service; // 定义Service使用基类
...
}
}
...
EggLoader:
egg-core核心类
- 提供load方法,例如loadToContext、loadToApp
- 提供获取egg基础信息方法和属性,例如 getAppInfo
- 挂载 /lib/loader/mixin目录下定义的load函数,具体加载顺序在 egg/lib/loader/appworkerloader.js中定义
// -------- egg-core/lib/loader/egg_loader.js -------
class EggLoader {
...
// 将property挂载到ctx
loadToContext(directory, property, opt) {
opt = Object.assign({}, {
directory,
property,
inject: this.app,
}, opt);
const timingKey = `Load "${String(property)}" to Context`;
this.timing.start(timingKey);
new ContextLoader(opt).load(); // 实例化ContextLoader
this.timing.end(timingKey);
},
// 获取当前 应用/框架/插件下所有文件路径,返回路径数组
getLoadUnits() {
if (this.dirs) {
return this.dirs;
}
const dirs = this.dirs = [];
// 插入插件路径
if (this.orderPlugins) {
for (const plugin of this.orderPlugins) {
dirs.push({
path: plugin.path,
type: 'plugin',
});
}
}
// 插入框架路径
for (const eggPath of this.eggPaths) {
dirs.push({
path: eggPath,
type: 'framework',
});
}
// 插入当前应用路径
dirs.push({
path: this.options.baseDir,
type: 'app',
});
debug('Loaded dirs %j', dirs);
return dirs;
}
}
...
const loaders = [