前言:最近在写node后台,使用了egg,不了解框架的运行过程,更多的是在搬砖配置,心里没底!
今天翻出源代码,在此记录一下
启动命令行:egg-bin dev
为了看清运行过程,使用node调试:
进入项目运行 node --inspect-brk=6666 ./node_modules/egg-bin/bin/egg-bin.js dev 代码如下
// egg-bin/bin/egg-bin.js
const Command = require('..');new Command().start();复制代码
require('..')引入模块egg-bin/index.js EggBin
'use strict';
const path = require('path');
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'));
}
}
module.exports = exports = EggBin;
exports.Command = Command;
exports.CovCommand = require('./lib/cmd/cov');
exports.DevCommand = require('./lib/cmd/dev');
exports.TestCommand = require('./lib/cmd/test');
exports.DebugCommand = require('./lib/cmd/debug');
exports.PkgfilesCommand = require('./lib/cmd/pkgfiles');复制代码
查看代码 EggBin extends Command, Command继承Common-bin
接下来创建对象new Command().start();,先初始化顶级父类CommonBin,获取命令行参数dev
创建Map集合
class CommonBin {
constructor(rawArgv) {
this.rawArgv = rawArgv || process.argv.slice(2);
this.yargs = yargs(this.rawArgv);
this.parserOptions = {
execArgv: false,
removeAlias: false,
removeCamelCase: false,
};
this[COMMANDS] = new Map();
}
...
}复制代码
接下来是初始化父类,初始化一些参数
class Command extends CommonBin {
constructor(rawArgv) {
super(rawArgv);
this.parserOptions = {
execArgv: true,
removeAlias: true,
};
this.options = {
typescript: {
description: 'whether enable typescript support, will load `ts-node/register` etc',
type: 'boolean',
alias: 'ts',
default: undefined,
},
};
}
...
}复制代码
接下来是初始化EggBin,重点看下load方法,load方法继承顶级父类CommonBin
class EggBin extends Command {
constructor(rawArgv) {
super(rawArgv);
this.load(path.join(__dirname, 'lib/cmd'));
}
}复制代码
查看CommonBin下的load方法,读取lib/cmd文件目录,把上面流程图相关类 AutodCommand,CovCommand,DebugCommand,DevCommand,PkgfilesCommand,TestCommand使用require加载并存储在Map集合中
load(fullPath) {
const files = fs.readdirSync(fullPath);
const names = [];
for (const file of files) {
if (path.extname(file) === '.js') {
const name = path.basename(file).replace(/\.js$/, '');
names.push(name);
this.add(name, path.join(fullPath, file));
}
}
}
add(name, target) {
if (!(target.prototype instanceof CommonBin)) {
target = require(target);
}
this[COMMANDS].set(name, target);
}复制代码
这就初始化完毕了,接着运行new Command().start(),中的start方法,继承顶级父类CommonBin
其中co是第三方模块是Generator 函数的自动执行器
start() {
co(function* () {
yield this[DISPATCH]();
}.bind(this)).catch(this.errorHandler.bind(this));
}
* [DISPATCH]() {
const parsed = yield this[PARSE](this.rawArgv);
const commandName = parsed._[0];
if (this[COMMANDS].has(commandName)) {
const Command = this[COMMANDS].get(commandName);
const rawArgv = this.rawArgv.slice();
rawArgv.splice(rawArgv.indexOf(commandName), 1);
const command = new Command(rawArgv);
yield command[DISPATCH]();
return;
}
const context = this.context;
yield this.helper.callFn(this.run, [ context ], this);
}复制代码
调用 DISPATCH方法,根据命令行传递的参数dev,判断之前存储在Map集合中有没有对应的模块
找到DevCommand模块,初始化DevCommand模块,配置默认端口7001,加载真正的启动文件serverBin
class DevCommand extends Command {
constructor(rawArgv) {
super(rawArgv);
this.defaultPort = 7001;
this.serverBin = path.join(__dirname, '../start-cluster');
};
}
...
}复制代码
初始化完成DevCommand 又调用了自己的DISPATCH方法,这次commandName=undifind
直接走this.helper.callFn
* [DISPATCH]() {
const parsed = yield this[PARSE](this.rawArgv);
const commandName = parsed._[0];
if (this[COMMANDS].has(commandName)) {
const Command = this[COMMANDS].get(commandName);
const rawArgv = this.rawArgv.slice();
rawArgv.splice(rawArgv.indexOf(commandName), 1);
const command = new Command(rawArgv);
yield command[DISPATCH]();
return;
}
const context = this.context;
yield this.helper.callFn(this.run, [ context ], this);
}复制代码
callFn是顶级父类CommonBin下的helper中的方法,判断run方法是generatorFunction,运行run
callFn = function* (fn, args = [], thisArg) {
if (!is.function(fn)) return;
if (is.generatorFunction(fn)) {
return yield fn.apply(thisArg, args);
}
const r = fn.apply(thisArg, args);
if (is.promise(r)) {
return yield r;
}
return r;
};复制代码
其中参数run 方法来自子类DevCommand,开始forkNode创建子进程
* run(context) {
const devArgs = yield this.formatArgs(context);
const env = {
NODE_ENV: 'development',
EGG_MASTER_CLOSE_TIMEOUT: 1000,
};
const options = {
execArgv: context.execArgv,
env: Object.assign(env, context.env),
};
yield this.helper.forkNode(this.serverBin, devArgs, options);
}复制代码
运行CommonBin下的helper中的方法forkNode,创建子进程,./node_modules/egg-bin/lib/start-cluster"
forkNode = (modulePath, args = [], options = {}) => {
const proc = cp.fork(modulePath, args, options);
gracefull(proc);
};复制代码
fork子进程./node_modules/egg-bin/lib/start-cluster
require(options.framework) 加载egg ./node_modules/egg"
const options = JSON.parse(process.argv[2]);
require(options.framework).startCluster(options);复制代码
查看Egg
'use strict';
exports.startCluster = require('egg-cluster').startCluster;
exports.Application = require('./lib/application');
exports.Agent = require('./lib/agent');
exports.AppWorkerLoader = require('./lib/loader').AppWorkerLoader;
exports.AgentWorkerLoader = require('./lib/loader').AgentWorkerLoader;
exports.Controller = require('./lib/core/base_context_class');
exports.Service = require('./lib/core/base_context_class');
exports.Subscription = require('./lib/core/base_context_class');
exports.BaseContextClass = require('./lib/core/base_context_class');复制代码
startCluster(options) 方法来自于require('egg-cluster')模块,查看
exports.startCluster = function(options, callback) {
new Master(options).ready(callback);
};复制代码