前言:最近在寫node後臺,使用了egg,不瞭解框架的運行過程,更多的是在搬磚配置,內心沒底!javascript
今天翻出源代碼,在此記錄一下java
啓動命令行:egg-bin devnode
爲了看清運行過程,使用node調試:typescript
進入項目運行 node --inspect-brk=6666 ./node_modules/egg-bin/bin/egg-bin.js dev 代碼以下
promise
// egg-bin/bin/egg-bin.js
const Command = require('..');new Command().start();複製代碼
require('..')引入模塊egg-bin/index.js EggBin
bash
'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
app
接下來建立對象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();
}
...
}複製代碼
接下來是初始化父類,初始化一些參數ui
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);
};複製代碼