与脚手架大战:回合2

@youngwind 2016-03-20 15:39:50发表于 youngwind/blog 工程化

再接再厉

继回合1 #46 之后,开始准备回合2。这次我重新整理了一下思路。

  1. 我要实现的是一个简单的脚手架生成器,它只需要能够生成我指定的那两三种项目。
  2. 生成的方式与上一次思考的相同,也就是(拷贝文件→装包→构建→启动服务),而非大量地写文件。
  3. 终端交互采取inquirer,获取到参数之后传递给生成模块去处理
  4. 这次需要重点解决的问题有。
  5. 如何让shell认识我定义的命令?
  6. 如何优雅地调用shell的命令?

寻找方案

针对第一个难点,经人指点,我知道应该在package.json的bin字段中定义我这个命令。其实平常我们输入cd,ls这些命令,shell之所以认识,是因为它们在环境变量$path中能找到。但是全局安装的npm module并不在$path中啊。npm的解决方案是,对于全局安装的,有bin字段的包,**在安装的时候会主动建立一个链接,从$path指向该module的某个可执行文件。**就像如图所示的第二行箭头那样。
2016-03-20 11 34 47
其实这个bin字段还可以配置多个命令,详细的可以参考这里。ok,第一个难题可以解决了。
针对第二个难点,我搜索到了shell.js,关于shell.js,可以参考阮一峰老师的教程

开始敲代码

ok,解决方案敲定之后就开始撸起袖子敲代码了。
先写终端交互的cli.js,主要涉及inquirer.js

#!/usr/bin/env node
'use strict';

var inquirer = require("inquirer");
var lazySmart = require("./lazy-smart");
var shell = require("shelljs");

var questions = [

  // 项目名称
  {
    type: "input",
    name: "name",
    message: "input your project name",
    validate: function (value) {

      if (!value) {
        return "project name can not be null"
      }

      // 检查文件夹是否已存在
      var ls = shell.ls();
      if (ls.indexOf(value) !== -1) {
        return "File exists, please select another project name.."
      } else {
        return true;
      }
    }
  },

  // 选择项目架构类型
  {
    type: "list",
    name: "architecture",
    message: "select your project architecture",
    choices: ["ejs+gulp", "ejs+webpack"]
  },

  // git仓库名称
  {
    type: "input",
    name: "gitName",
    message: "input the repository name of git project.(make sure the repository is created and empty)",
    default: function (answer) {
      return answer.name;
    }
  },

  // git仓库所有者名称
  {
    type: "input",
    name: "gitOwner",
    message: "input the owner of git project.",
    default: function () {
      var userName = shell.exec('git config --global --get user.name').output;
      userName = userName.substring(0, userName.length - 1);
      return userName;
    }
  }
];

inquirer.prompt(questions, function (answers) {
  //把用户输入的参数传递给生成模块
  lazySmart.init(answers);
});

然后编写生成模块lazy-smart.js

// 调用执行命令行
var shell = require("shelljs");

// 支持在脚本中直接执行命令
require('shelljs/global');

// 解析获取命令行参数
var argv = require('yargs').argv;
// 初始化
exports.init = function (options) {

  exports.copy(options);
  exports.initGit(options);
  exports.install(options);
  exports.build(options);
  exports.run(options);
};
....

把功能模块划分好之后,剩下函数的编写就不难了,完整的代码在这里

至此,已经完成了第一套项目脚手架的搭建,之后第二,第三套就跟脚手架没啥关系了。通过这次自己手动写脚手架,主要有两个收获。

  1. 通过分析问题找准解决方案之后,敲代码才能效率高。
  2. 跟终端交互还是很有趣的。

遗留问题点:

  1. npm包的本地调试和发布流程不是非常顺畅,而且有个极其坑爹的.gitignore变成.npmignore的问题还不知道为啥。暂时找到的参考资料在这里
  2. shell.js的exce是串行执行的,当我想在在一个js文件调用多个不会结束自动结束的进程的时候容易出问题,比如先后开启npm run start 和gulp watch这些。有什么办法能够直接打开新终端窗口吗?或者结合child_process?这个可是每个都是并行执行的。