egg-bin dev机制

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成功时回调函数赋予的参数。

猜你喜欢

转载自schifred.iteye.com/blog/2362049
Dev
egg