EggJS源码阅读-启动流程与代码实现
egg是阿里开源的一个框架,为企业级框架和应用而生,相较于express和koa,有更加严格的目录结构和规范。不同的团队可以基于egg,根据自己的需求封装出适合团队业务的更上层框架。本文对 egg.js 源码进行一个简要的入门阅读和解析。
egg如何启动
根据package.json
中的script
命令,可以看到执行的直接是egg-bin dev
的命令。找到egg-bin
文件夹中的dev.js
,会看到里面会去执行外层的start-cluster
文件:
1 | require(options.framework).startCluster(options); |
此处的options.framework
其实指的就是egg
框架,然后调用了egg-cluster
包中的startCluster
方法,egg正式迈出了启动的第一步。
[源码流程]
egg
框架采用了master-agent-worder
的集群模式,如果所示,官方文档中也对于这三种进程的启动和通信给出了较为详细的说明:
- Master 启动后先 fork Agent 进程
- Agent 初始化成功后,通过 IPC 通道通知 Master
- Master 再 fork 多个 App Worker
- App Worker 初始化成功,通知 Master
- 所有的进程初始化成功后,Master 通知 Agent 和 Worker 应用启动成功
我们尝试从源码中大致将这个流程实现出来:
1 | // egg-cluster/lib/master.js |
[源码流程]
Agent与AppWorker的实现
AgentWorker与AppWorker进程的启动
在启动AgentWorker
和AppWorker
时,会分别加载agent_worker.js
和app_worker.js
两个文件并创建进程,其中agent_worker.js
中会创建Agent
类的实例,而app_worker.js
中会创建Application
类的实例。
两种进程在启动时都会调用this.loader.load()
方法来加载自己相应的一些插件和自定义的扩展。
基于egg_loader
实现了AppWorkerLoader
和 AgentWorkerLoader
,上层框架基于这两个类来扩展,Loader
的扩展只能在框架进行。
1 | import { AppWorkerLoader } from 'egg' |
Agent如何实现
Agent
对象在egg-cluster
创建环节中被创建出来,继承自egg.Agent
对象,该对象继承EggApplication
,且loader
为./lib/loader/agent_worker_loader.js
文件,继承自egg-core.eggLoader
对象,整体继承链如上图。
1 | // egg/lib/agent.js |
Agent
类的实现中,主要是实例化了EggApplication
,调用了this.loader.load()
方法来加载各种文件和配置。
1 | // egg/lib/egg.js |
在EggApplication
类的实现中,我们可以看到是继承自EggCore
类,在父类构建好之后,会调用this.loader.loadConfig()
方法,该方法的loader
实例实际指向了AgentWorkerLoader(agent_worker_loader.js)
:
1 | // egg/lib/loader/agent_worker_loader.js |
loadPlugin
方法会加载三种插件:
eggPlugins
,从eggjs框架配置的插件,也就是egg/config/plugins.js
文件中egg框架自带的插件;appPlugins
,每个应用自己配置的插件,也就是myproject/config/plugins.js
,用户可以自定义配置一些特殊的插件;customPlugins
,应用启动命令中参数EGG_PLUGINS值所代表的插件;
最后会将这三种插件都挂在app实例上:this.plugins = enablePlugins
;
loadConfig
方法会加载三种配置:
appConfig
,每个应用自己独有的配置,其中会按顺序加载两个配置,一个是默认配置config.default
,另一个是当前环境的配置config.${this.serverEnv}
,也就是myproject/config
下的一些配置文件加载- 加载自定义添加的
plugin
插件的配置 - 加载框架
egg
的配置,即egg/config
- 重新加载应用
app
的配置,即myproject/config
下的
最后将合并的配置挂载在app
实例上this.config = target
;
Application如何实现
上面提到,AppWorker
在实例化的过程中,会调用this.loader.load()
。进入具体这个Application
所对应的loader.load
方法,可以发现Application
的实现比Agent
的实现多调用了很多加载的方法:
this.loadApplicationExtend();
,该方法的调用会给应用加载扩展方法,加载路径为app\extend\application.js
, 会将对应的对象挂载在app 应用上。this.loadRequestExtend();
,加载app\extend\request.js
this.loadResponseExtend();
,加载app\extend\response.js
this.loadContextExtend();
,加载app\extend\context.js
this.loadHelperExtend();
,加载app\extend\helper.js
this.loadService()
this.loadMiddleware()
this.loadController()
this.loadRouter()
总结
egg启动服务集群,采用了
master-agent-worker
模式,AgentWorker
和AppWorker
都由Application(egg/lib/applicaton.js) -> EggApplication(egg/lib/egg.js) -> EggCore(egg-core/lib/egg.js) -> KoaApplication(koa)原型链进行继承Agent
和Application
在实例化的过程中,都会调用相应的Loader去加载自己所需的插件和配置,且加载顺序严格按照插件plugin-框架framework-应用application这样一个顺序