1.项目文件package.json中添加script:{start:"egg-bin dev"}语句,执行npm start命令将调用egg-bin包的启动命令。
2.egg-bin包下bin文件夹添加egg-bin.js文件,同时package.json中添加bin:{"egg-bin":"bin/egg-bin.js"}语句,可使命令行执行脚本时无需使用node file形式。
#!/usr/bin/env node 'use strict'; const run = require('common-bin').run; run(require('../lib/program'));
3."lib/program.js"模块,通过common-bin机制实现为单个Program,待执行的程序,可用于添加命令。common-bin将解析命令行参数,获取正在执行的命令名,并调用相应的脚本文件,如dev命令将调用"lib/dev_command.js"脚本并执行。
'use strict'; const path = require('path'); const BaseProgram = require('common-bin').Program; // 注册版本号和子命令,如egg-bin dev执行dev_command.js脚本文件 // __dirname执行中的当前文件路径 class Program extends BaseProgram { constructor() { super(); this.version = require('../package.json').version; this.addCommand('dev', path.join(__dirname, 'dev_command.js')); this.addCommand('debug', path.join(__dirname, 'debug_command.js')); this.addCommand('test', path.join(__dirname, 'test_command.js')); if (process.platform === 'win32') { this.addCommand('cov', path.join(__dirname, 'test_command.js')); } else { this.addCommand('cov', path.join(__dirname, 'cov_command.js')); } } } module.exports = Program;
4."lib/dev_command.js"模块,作为命令行键入dev时待执行的脚本文件,基于“egg-bin”包下的"lib/command.js"模块实现。dev_command模块的主要意义是向待执行的start-cluster模块注入配置项。start-cluster模块调用egg框架或其他基于egg实现的node框架的startCluster方法,以启动服务。
'use strict'; const debug = require('debug')('egg-bin:dev'); const Command = require('./command'); class DevCommand extends Command { // 参数cwd为当前用户脚本所在目录,将作为start-cluster模块启动时的baseDir参数传入egg或其他基于egg实现的框架中 // args为命令行键入的剩余参数 * run(cwd, args) { // 命令行参数只允许"--debug"或"--inspect" const execArgv = args ? args.filter(str => str.indexOf('--debug') === 0 || str.indexOf('--inspect') === 0) : []; // 获取egg模块路径 const eggPath = this.getFrameworkOrEggPath(cwd); // 获取start-cluster模块执行时携带的参数 args = yield this.helper.formatArgs(cwd, args, { eggPath }); // 环境变量 const options = { env: Object.assign({}, process.env), execArgv, }; options.env.NODE_ENV = options.env.NODE_ENV || 'development'; debug('%s %j %j, %j', this.helper.serverBin, args, execArgv, options.env.NODE_ENV); yield this.helper.checkDeps(); // this.helper.serverBin即start-cluster模块的文件路径 // this.helper.forkNode(filepath)调用执行路径为filepath的文件,由commin-bin包提供该功能 yield this.helper.forkNode(this.helper.serverBin, args, options); } help() { return 'local env start'; } // 获取egg模块路径 getFrameworkOrEggPath(cwd) { return this.utils.getFrameworkOrEggPath(cwd); } } module.exports = DevCommand;
4.1 command.js模块,基于commin-bin的同名模块,构建egg-bin自有command类的基类,最主要的不同时改造helper属性和添加utils属性。
'use strict'; const utils = require('egg-utils'); const BaseCommand = require('common-bin').Command; const helper = require('./helper'); // 创建Command命令的基类,在require('common-bin').Command的基础上修改helper、utils属性 class Command extends BaseCommand { constructor() { super(); this.helper = Object.assign({}, this.helper, helper); } run(/* cwd, args */) { throw new Error('Must impl this method'); } help() { throw new Error('Must impl this method'); } get utils() { return utils; } } module.exports = Command;
4.2 helper.js为command类注入工具方法,dev命令所用的是formatArgs方法为startCluster指令注入参数,同时helper.serverBin指向egg-bin包下用于启动服务的start-cluster模块。
'use strict'; const fs = require('fs'); const path = require('path'); const glob = require('glob'); const detect = require('detect-port'); const debug = require('debug')('egg-bin'); // 默认端口 exports.defaultPort = 7001; // "egg-bin"的"start-cluster"模块的所在目录 exports.serverBin = path.join(__dirname, 'start-cluster'); exports.getTestFiles = () => { const files = process.env.TESTS || 'test/**/*.test.js'; const base = process.cwd(); return glob.sync(files, { cwd: base, }).map(file => { return path.join(base, file); }); }; exports.getTestSetupFile = () => { const setupFile = path.join(process.cwd(), 'test/.setup.js'); if (fs.existsSync(setupFile)) { return setupFile; } return null; }; exports.formatTestArgs = args => { const newArgs = [ '--timeout', process.env.TEST_TIMEOUT || '30000', '--require', require.resolve('thunk-mocha'), ]; if (process.env.TEST_REPORTER) { newArgs.push('--reporter'); newArgs.push(process.env.TEST_REPORTER); } if (args.indexOf('intelli-espower-loader') !== -1) { console.warn('[egg-bin] don\'t need to manually require `intelli-espower-loader` anymore'); } else { // should be require before args newArgs.push('--require'); newArgs.push(require.resolve('intelli-espower-loader')); } // auto add setup file as the first test file const setupFile = exports.getTestSetupFile(); if (setupFile) { newArgs.push(setupFile); } return newArgs.concat(exports.getTestFiles()).concat(args); }; // TODO: add egg-dependencies // const checkDeps = require('egg-dependencies'); exports.checkDeps = function* () { return true; }; // 获取start-cluster模块执行时携带的参数 exports.formatArgs = function* (cwd, args, options) { options = options || {}; // start-cluster模块设定参数的解析形式,当前方法中传入参数baseDir为cwd,cluster为1,eggPath为options.eggPath args.push('--baseDir'); args.push(cwd); args.push('--cluster'); args.push('1'); if (options.eggPath) { args.push(`--eggPath=${options.eggPath}`); } // auto detect available port if (args.indexOf('-p') === -1 && args.indexOf('--port') === -1) { debug('detect available port'); // 通过co的机制将co内部的onFulfilled函数传入detect函数作为回调resolve,detect内部赋予其参数realPort,即端口 // 生成器next方法执行时,常量port也即赋值为确切的端口 const port = yield detect(exports.defaultPort); if (port !== exports.defaultPort) { args.push('-p', port); console.warn(`[egg-bin] server port ${exports.defaultPort} is in use, now using port ${port}\n`); } debug(`use available port ${port}`); } return args; };
5.start-cluster执行egg或其他基于egg实现的框架的startCluster方法,以启动服务。
#!/usr/bin/env node 'use strict'; const debug = require('debug')('egg-bin:start-cluster'); const assert = require('assert'); const commander = require('commander'); // 使用commander模块解析进程的参数 commander .option('--eggPath [eggPath]') .option('--baseDir [baseDir]') .option('-p, --port [port]') .option('--cluster [workers]') .allowUnknownOption(true) .parse(process.argv); // 用户的启动目录 const baseDir = commander.baseDir; const workers = commander.cluster ? Number(commander.cluster) : 1; // 端口 const port = commander.port; // egg模块或其他基于egg实现的框架的路径 const customEgg = commander.eggPath; assert(customEgg, 'eggPath required, missing any egg frameworks?'); const options = { baseDir, workers, port, customEgg, }; debug('eggPath:%s options:%j', customEgg, options); // 调用egg或其他基于egg实现的框架的startCluster方法,启动服务;options设置端口,用户代码所在目录等 require(customEgg).startCluster(options);
附注:
commander模块用于单纯解析命令行参数。
co模块将生成器函数的下一条next方法作为上一段执行代码返回值Promise的回调函数,同时生成器函数的next方法获得Promise成功时回调函数赋予的参数。