为什么要研究 egg 源码
目前框架种类百花齐放,这是一件好事,促进 Node 本身的发展,开发者也有更多的选择。关于众多框架孰优孰劣我不想参与讨论。
egg 在这么多框架中,也有属于自己的大批使用者, 它解决了我实际生产问题,我曾得益于 egg, 我想去了解 egg,仅此而已。
框架 Stats
egg-bin 担任的角色
egg-bin
是整个框架的入口,负责开发环境下命令行的解析(启动、调试、测试)。本章主要从源码为大家解析 egg 的 dev 启动流程。
在初始化的 egg 项目 package.json
中可以看到:
"scripts": {
"dev": "egg-bin dev",
"debug": "egg-bin debug",
"test-local": "egg-bin test",
"cov": "egg-bin cov"
}
在 egg 项目 package.json
中看到 dev
、debug
、test-local
、cov
命令都是依赖 egg-bin 启动的。
egg-bin 源码解析
目录结构如下:
package.json
index.js
bin
├── egg-bin.js // 执行入口
├── ets.js
└── mocha.js
lib
├── cmd // 命令行解析目录
│ ├── autod.js
│ ├── cov.js // npm run cov 执行js
│ ├── debug.js
│ ├── dev.js // npm run dev 执行js
│ ├── local.js
│ ├── pkgfiles.js
│ └── test.js // npm run test 执行js
├── command.js
└── start-cluster
package.json 入口
在 egg-bin 模块的 package.json 中可以看到 bin 入口文件是 /bin/egg-bin.js
"bin": {
"egg-bin": "bin/egg-bin.js",
"mocha": "bin/mocha.js",
"ets": "bin/ets.js"
},
bin/egg-bin.js
#!/usr/bin/env node
'use strict';
const Command = require('..');
new Command().start();
Command 对象来自 index.js
, index.js
代码如下:
const Command = require('./lib/command');
class EggBin extends Command {
constructor(rawArgv) {
super(rawArgv);
this.usage = 'Usage: egg-bin [command] [options]';
this.load(path.join(__dirname, 'lib/cmd')); // 加载命令行参数对应的 js 文件
}
}
module.exports = exports = EggBin;
在 index.js
中我们看到 Command 对象就是 EggBin 类,该类又继承自 Command,关于命令行实现和处理都在该类上实现的。
common-bin 命令行解析
接下来进入 ./lib/command.js
:
'use strict';
const path = require('path');
const fs = require('fs');
const BaseCommand = require('common-bin');
class Command extends BaseCommand {
...
}
module.exports = Command;
从代码中可以看出 Command 类又继承自 BaseCommand, 由此发现 egg-bin
模块命令的实现使用的是 common-bin
公共模块。
这里和大家简单说明一下 common-bin 模块使用(git 仓库代码使用实例)。
前面在 EggBin 类 constructor
中调用了 this.load 方法
this.load(path.join(__dirname, 'lib/cmd'));
该方法加载了 lib/cmd
目录下的 js 文件:
-
autod.js
-
cov.js
-
debug.js
-
dev.js
-
pkgfiles.js
-
test.js
相当于定义了 autod
、cov
、debug
、dev
、pkgfiles
、test
命令,比如当我们执行 npm run dev
的时候,就会执行 dev.js
。
相当于在 egg-bin 模块目录执行了:
node ./bin/egg-bin.js dev
所以 Egg 启动 npm run dev
执行的是 dev.js
文件。
开启 master 进程
dev.js 主要包含三个功能
-
命令行参数设置(根目录、端口、进程数量..)
-
进程参数格式化(参数传递、端口探测是否可用)
-
进程启动
class DevCommand extends Command {
constructor(rawArgv) {
this.options = { // 命令行参数设置
...
};
}
* run(context) {
...
const task = this.helper.forkNode(this.serverBin, devArgs, options); // 进程启动
}
* formatArgs(context) { //进程启动参数格式化
...
const port = yield detect(this.defaultPort);
}
module.exports = DevCommand;
进程在启动之前的参数,也就是 formatArgs
方法的返回值,调式输出如下:
{
...
tscompiler: 'ts-node/register',
workers: 1, // 进程数量
baseDir: '/Users/***/study/eggStudy/egg-bin', // 当前进程根目录
port: 7001, //端口
framework: '/Users/***/study/eggStudy/egg-bin/node_modules/egg' //框架目录
}
启动进程是执行 this.helper.forkNode
, 最开始我以为 this.helper.forkNode
方法是 EggBin 类上面的方法,源码翻一遍也没找到,后来发现该方法是 common-bin
模块的方法,用来 fork 一个子进程。this.serverBin
参数就是执行文件,该参数就是 lib/start-cluster
文件。
start-cluster 文件:
require(options.framework).startCluster(options);
从 npm run dev
开始执行一系列代码后,暂时走进了 start-cluster
中的代码, egg-bin 模块的任务就结束了。
后续将为大家解析重点模块 —— Egg-cluster ! 希望大家点个赞支持一下 ❤️❤️❤️