转载自知乎网络,原文链接:https://zhuanlan.zhihu.com/p/47178799
egg-core 是什么
应用、框架、插件之间的关系
在学习 egg-core 是什么之前,我们先了解一下关于 Egg 框架中应用、框架、插件这三个概念及其之间的关系:
- 一个应用必须指定一个框架才能运行起来,根据需要我们可以给一个应用配置多个不同的插件;
- 插件只完成特定独立的功能,实现即插即拔的效果;
- 框架是一个启动器,必须有它才能运行起来。框架还是一个封装器,它可以在已有框架的基础上进行封装,框架也可以配置插件,其中 Egg,EggCore 都是框架;
- 在框架的基础上还可以扩展出新的框架,也就是说框架是可以无限级继承的,有点像类的继承;
- 框架、应用、插件的关于 service/controller/config/middleware 的目录结构配置基本相同,称之为加载单元(loadUnit),包括后面源码分析中的 getLoadUnits 函数都是为了获取这个结构;
1 | # 加载单元的目录结构如下图,其中插件和框架没有 controller 和 router.js |
egg-core 的主要工作
Egg.js 的大部分核心代码实现都在 egg-core 库 中,egg-core 主要 export 四个对象:
- EggCore 类:继承于 Koa ,做一些初始化工作, EggCore 中最主要的一个属性是 loader ,也就是 egg-core 的导出的第二个类 EggLoader 的实例
- EggLoader 类:整个框架目录结构(controller,service,middleware,extend,router.js)的加载和初始化工作都在该类中实现的,主要提供了几个 load 函数(loadPlugin,loadConfig,loadMiddleware,loadService,loadController,loadRouter 等),这些函数会根据指定目录结构下文件输出形式不同进行适配,最终挂载输出内容。
- BaseContextClass 类:这个类主要是为了我们在使用框架开发时,在 controller 和 service 作为基类使用,只有继承了该类,我们才可以通过 this.ctx 获取到当前请求的上下文对象
- utils 对象:提供几个主要的函数,包括转换成中间件函数 middleware ,根据不同类型文件获取文件导出内容函数 loadFile 等
所以 egg-core 做的主要事情就是根据 loadUnit 的目录结构规范,将目录结构中的 config,controller,service,middleware,plugin,router 等文件 load 到 app 或者 context 上,开发人员只要按照这套约定规范,就可以很方便进行开发,以下是 egg-core 的 exports 对象源码:
1 | // egg-core 源码 -> 导出的数据结构 |
EggCore 的具体实现源码学习
EggCore 类源码学习
EggCore 类是算是上文提到的框架范畴,它从 Koa 类继承而来,并做了一些初始化工作,其中有三个主要属性是:
- loader :这个对象是 EggLoader 的实例,定义了多个 load 函数,用于对 loadUnit 目录下的文件进行加载,后面后专门讲这个类的是实现;
- router :是 EggRouter 类的实例,从 KoaRouter 继承而来,用于 Egg 框架的路由管理和分发,这个类的实现在后面的 loadRouter 函数会有说明
- lifecycle :这个属性用于 app 的生命周期管理,由于和整个文件加载逻辑关系不大,所以这里不作说明
1 | // egg-core 源码 -> EggCore 类的部分实现 |
EggLoader 类源码学习
如果说 EggCore 是 Egg 框架的精华所在,那么 EggLoader 可以说是 EggCore 的精华所在,下面我们主要从 EggLoader 的实现细节开始学习 EggCore 这个库:
EggLoader 首先对 app 中的一些基本信息(pkg/eggPaths/serverEnv/appInfo/serverScope/baseDir 等)进行整理,并且定义一些基础共用函数(getEggPaths/getTypeFiles/getLoadUnits/loadFile),所有的这些基础准备都是为了后面介绍的几个 load 函数作准备,我们下面看一下其基础部分的实现:
1 | // egg-core源码 -> EggLoader 中基本属性和基本函数的实现 |
各个 loader 函数的实现源码分析
上文中只是介绍了 EggLoader 中的一些基本属性和函数,那么如何将 loadUnits 中的不同类型的文件分别加载进来呢,eggCore 中每一种类型(service/controller 等)的文件加载都在一个独立的文件里实现。比如我们加载 controller 文件可以通过 ‘./mixin/controller’ 目录下的 loadController 完成,加载 service 文件可以通过 ‘./mixin/service’ 下的 loadService 函数完成,然后将这些方法挂载 EggLoader 的原型上,这样就可以直接在 EggLoader 的实例上使用
1 | // egg-core 源码 -> 混入不同目录文件的加载方法到 EggLoader 的原型上 |
我们按照上述 loaders 中定义的元素顺序,对各个 load 函数的源码实现进行一一分析:
loadPlugin 函数
插件是一个迷你的应用,没有包含 router.js 和 controller 文件夹,我们上文也提到,应用和框架里都可以包含插件,而且还可以通过环境变量和初始化参数传入,关于插件初始化的几个参数:
- enable: 是否开启插件
- env: 选择插件在哪些环境运行
- path: 插件的所在路径
- package: 和 path 只能设置其中一个,根据 package 名称去 node_modules 里查询 plugin ,后面源码里有详细说明
1 | // egg-core 源码 -> loadPlugin 函数部分源码 |
loadConfig 函数
配置信息的管理对于一个应用来说非常重要,我们需要对不同的部署环境的配置进行管理,Egg 就是针对环境加载不同的配置文件,然后将配置挂载在 app 上,
加载 config 的逻辑相对简单,就是按照顺序加载所有 loadUnits 目录下的 config 文件内容,进行合并,最后将 config 信息挂载在 this 对象上,整个加载函数请看下面源码:
1 | // egg-core 源码 -> loadConfig 函数分析 |
loadExtend 相关函数
这里的 loadExtend 是一个笼统的概念,其实是针对 Koa 中的 app.response,app.respond,app.context 以及 app 本身进行扩展,同样是根据所有 loadUnits 下的配置顺序进行加载
下面看一下 loadExtend 这个函数的实现,一个通用的加载函数:
1 | // egg-core -> loadExtend 函数实现 |