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