You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
add(name,target){assert(name,`${name} is required`);if(!(target.prototypeinstanceofCommonBin)){assert(fs.existsSync(target)&&fs.statSync(target).isFile(),`${target} is not a file.`);debug('[%s] add command `%s` from `%s`',this.constructor.name,name,target);target=require(target);assert(target.prototypeinstanceofCommonBin,'command class should be sub class of common-bin');}this[COMMANDS].set(name,target);}
*[DISPATCH](){// define --help and --version by defaultthis.yargs// .reset().completion().help().version().wrap(120).alias('h','help').alias('v','version').group(['help','version'],'Global Options:');// get parsed argument without handling helper and versionconstparsed=yieldthis[PARSE](this.rawArgv);constcommandName=parsed._[0];//获取命令行参数if(parsed.version&&this.version){console.log(this.version);return;}// if sub command existif(this[COMMANDS].has(commandName)){constCommand=this[COMMANDS].get(commandName);constrawArgv=this.rawArgv.slice();rawArgv.splice(rawArgv.indexOf(commandName),1);debug('[%s] dispatch to subcommand `%s` -> `%s` with %j',this.constructor.name,commandName,Command.name,rawArgv);constcommand=newCommand(rawArgv);yieldcommand[DISPATCH]();return;}// register command for printingfor(const[name,Command]ofthis[COMMANDS].entries()){this.yargs.command(name,Command.prototype.description||'');}debug('[%s] exec run command',this.constructor.name);constcontext=this.context;// print completion for bashif(context.argv.AUTO_COMPLETIONS){// slice to remove `--AUTO_COMPLETIONS=` which we appendthis.yargs.getCompletion(this.rawArgv.slice(1),completions=>{// console.log('%s', completions)completions.forEach(x=>console.log(x));});}else{// handle by self//对不同类型的函数进行调用(generator/promise)yieldthis.helper.callFn(this.run,[context],this);}}
*run(context){constproxyPort=context.argv.proxy;context.argv.proxy=undefined;consteggArgs=yieldthis.formatArgs(context);//省略部分// start eggconstchild=cp.fork(this.serverBin,eggArgs,options);// start debug proxyconstproxy=newInspectorProxy({port: proxyPort});// proxy to new workerchild.on('message',msg=>{if(msg&&msg.action==='debug'&&msg.from==='app'){const{ debugPort, pid }=msg.data;debug(`recieve new worker#${pid} debugPort: ${debugPort}`);proxy.start({ debugPort }).then(()=>{console.log(chalk.yellow(`Debug Proxy online, now you could attach to ${proxyPort} without worry about reload.`));if(newDebugger)console.log(chalk.yellow(`DevTools → ${proxy.url}`));});}});child.on('exit',()=>proxy.end());}
formatTestArgs({ argv, debug }){//省略// collect requireletrequireArr=testArgv.require||testArgv.r||[];/* istanbul ignore next */if(!Array.isArray(requireArr))requireArr=[requireArr];// clean mocha stack, inspired by https://github.com/rstacruz/mocha-clean// [mocha built-in](https://github.com/mochajs/mocha/blob/master/lib/utils.js#L738) don't work with `[npminstall](https://github.com/cnpm/npminstall)`, so we will override it.if(!testArgv.fullTrace)requireArr.unshift(require.resolve('../mocha-clean'));requireArr.push(require.resolve('co-mocha'));if(requireArr.includes('intelli-espower-loader')){console.warn('[egg-bin] don\'t need to manually require `intelli-espower-loader` anymore');}else{requireArr.push(require.resolve('intelli-espower-loader'));}testArgv.require=requireArr;// collect test filesletfiles=testArgv._.slice();if(!files.length){files=[process.env.TESTS||'test/**/*.test.js'];}// expand glob and skip node_modules and fixturesfiles=globby.sync(files.concat('!test/**/{fixtures, node_modules}/**/*.test.js'));// auto add setup file as the first test fileconstsetupFile=path.join(process.cwd(),'test/.setup.js');if(fs.existsSync(setupFile)){files.unshift(setupFile);}testArgv._=files;// remove aliastestArgv.$0=undefined;testArgv.r=undefined;testArgv.t=undefined;testArgv.g=undefined;returnthis.helper.unparseArgv(testArgv);}
下面就
egg-bin
源码分析一些东西(针对的是 4.3.0 的版本)egg-bin
如何工作的在本地运行
egg
项目的时候,我们往往会根据不同的场景(调试,测试等)来选择不同的命令(egg-bin dev、egg-bin debug
)启动项目,从而达到我们需要的效果,但是egg-bin
是如何让命令运作起来的呢?比如在命令行中回车下面的命令:
开始进入
node_modules/egg-bin/bin/egg-bin.js
文件,文件代码比较简单:其中,
Command
对应的是node_modules/egg-bin/bin/egg-bin.js
中的EggBin
这个对象。首先理清一下egg-bin
中对应的几个对象之间的关系,如下图:其中最后导出的
EggBin
对象以及DevCommand、AutodCommand、TestCommand、PkgFilesCommand
继承于egg-bin/lib/command.js
里面导出的Command
对象,而egg-bin/lib/command.js
里面导出的Command
又是继承于第三方库(其实也是egg
核心contributors
开发的)common-bin,而common-bin
中导出的CommonBin
对象又有一个yargs
属性,该属性是目前比较流行的命令行工具yargs。DebugCommand
和CovCommand
则分别继承自DevCommand
和TestCommand
。进入
index.js
文件源代码,该文件只是定义了EggBin
这个对象,并且将一些sub command
挂载到EggBin
这个导出对象中,有如下几个子命令:common-bin
的基础命令对象接着就是执行
new Command().start()
这一行,首先会先去执行EggBin
构造函数中的内容:获取命令参数
由于上面的继承关系,第一行就会直接执行到
Common-bin/command.js
中的构造函数中的参数获取:此时
this.rawArgv
的值如下:load
配置文件获取到这个参数之后就会直接将该参数传给
yargs
并将yargs
对象赋给自己的一个yargs
属性然后就开始
load
命令行文件了,通过追踪,也可以发现最后执行的也是common-bin
中的load
成员函数,该函数要求参数是所需要获取的命令文件的绝对路径,其中common-bin/command.js
中的load
源码如下:其中
files
文件的值为:然后将
files
进行遍历,执行下面的addCommand
的操作:其中要求参数
target
也是某个子命令对应的文件的绝对路径。在进行条件判断之后直接使用set
将该命令挂载在this[COMMANDS]
变量中。遍历完成后this[COMMANDS]
的值如下所示:执行
start()
最重要的
start
操作,追根溯源也是执行的common-bin
里面的start()
,start()
里面主要是使用co
包了一个generator
函数,并且在generator
函数中执行了this[DISPATCH]
,然后,重头戏来了,this[DISPATCH]
的源码如下:首先会去执行
yargs
中一些方法,这里common-bin
只是保留了yargs
中一些对自己有用的方法,比如completion()、wrap()、alias()
等,具体关于yargs
的API
可以移步这里。接着是执行this[PARSE]
将rawArgv
进行处理,处理后的parse
对象结构如下:接着就是对获取到的命令进行校验,如果存在
this[COMMAND]
对象中就执行。在当前例子中也就是去执行DevCommand
,而由于DevCommand
最终也是继承于common-bin
的,然后执行yield command[DISPATCH]();
又是递归开始执行this[DISPATCH]
了,直到所有的子命令递归完毕,才会去使用helper
(common-bin
中支持异步的关键所在)类继续执行每个command
文件中的* run()
函数。egg-bin中的子命令文件
dev.js
作为在
egg
项目中本地开发最为重要的开发命令,dev.js
无疑肩负着比较重要的职责。在dev.js
中,主要是定义了一些默认端口号,以及入口命令等。* run()
的源码如下:主要是对当前的上下文参数进行转化并对端口进行了一些处理,然后就开始调用
helper
的forkNode
来执行入口命令,其中this.serverBin
的值为:/Users/uc/Project/egg-example/node_modules/egg-bin/lib/start-cluster
,下面的事情可以移步这里进行了解debug.js
由上分析可知,
DebugCommand
继承于DevCommand
,所以在constructor
的时候就会去执行dev
中的一些options
,而且在debug.js
中的* run()
函数中直接调用的是dev.js
中的formatArgs()
参数处理。关键源码(有删减)如下:此处首先是开启
egg
,做的是和dev
里面一样的东西。然后则是实例化InspectorProxy
进行debug
操作,在命令行打印出一行devtools
的地址。test.js
这个命令主要是用来运行
egg
项目中的*test
文件的,也就是跑我们自己写的测试用例,关于如何写单元测试,可以移步单元测试,在这个文件,* run()
形式也和上面类似,然后调用this.formatTestArgs()
,formatTestArgs
源码如下(有删减):代码里面的英文注释很清楚了,就是将单元测试的一些库
push
进requireArr
中,requireArr
的值如下:其中
mocha-clean
是清除上一次mocha
遗留的堆栈了,后面两个就是egg
选用的测试框架和断言库了。然后就是加载
egg
项目中除掉node_modules
和fixtures
里面的test
文件,即项目层面的*.test.js
,后面也就是开启进程来进行单元测试。cov.js
cov.js
是用来测试代码覆盖率的。其中CovCommand
继承自TestCommand
,在cov
的* run()
中主要定义了字段,比如excludes、nycCli、coverageDir、outputDir
等,根据英文命名也知道是什么意思了。然后继续执行getCovArgs
是对参数的一些处理,源码也很简单,就不贴出来了,在getCovArgs
中将上面test.js
中的参数一起concat
进来了,最后返回的covArgs
的样子是这样的:然后又是开启进程了。
autod.js
和pkgfiles.js
这两个比较简单,这里就不赘述了
总结
整个
egg-bin
看下来,还是很厉害的,涉及的都是我之前没听过或者听过但是没用过的高大尚的东西,比如commander.js,yargs,mocha,co-mocha,power-assert,istanbuljs,nyc
,所以分析起来还是比较吃力的。肯定也有很多分析不到位的地方或者有很多厉害的功能被一笔带过了,还望大神们指出~The text was updated successfully, but these errors were encountered: