node命令行工具之实现项目工程自动初始化的标准流程

BarrettKerwin 发布于8天前
0 条问题

一、目的

传统的前端项目初始流程一般是这样:

node命令行工具之实现项目工程自动初始化的标准流程

可以看出,传统的初始化步骤,花费的时间并不少。而且,人工操作的情况下, 总有改漏的情况出现 。这个缺点有时很致命。

甚至有马大哈,没有更新项目仓库地址,导致提交代码到旧仓库,这就很尴尬了。。。

基于这些情况,编写命令行工具(CLI)的目的就很明确:

  • 用于新项目工程的初始化
  • 利用工具进行初始化,可以节省项目初期的准备时间
  • 避免出现改漏的情况
  • 杜绝未更新项目版本仓库地址的问题

以下是新的流程示意图:

node命令行工具之实现项目工程自动初始化的标准流程

二、自动化流程分析

以下是自动化流程图:

node命令行工具之实现项目工程自动初始化的标准流程

从流程图可以得出两个重要的信息:

  • 配置信息
  • 模板文件

命令行工具的角色,是负责将两个信息进行融合,提供一个交互平台给用户。

三、工具准备

3.1 配置信息工具

配置信息的获得,需要靠和用户进行交互。由于程序员一般是用终端输入命令进行项目操作。所以,这里选择了两个工具进行支撑。

  • commander

借鉴Ruby commander理念实现的命令行执行补全解决方案

commander 可以接收命令行传入的参数

例子:

npg-cli --help

♫ ♫♬♪♫  npm-package-cli ♫ ♫♬♪♫
Usage: npg-cli [options]

Options:
  -V, --version  output the version number
  -h, --help     output usage information
  run testcli and edit the setting.
  • inquirer

常用交互式命令行用户界面的集合。

inquirer 用询问式的语句,与用户进行交互,接收参数

例子:

npg-cli

♫ ♫♬♪♫  npm-package-cli ♫ ♫♬♪♫
Follow the prompts to complete the project configuration.

? project name test
? version 1.0.0
? description

3.2 模板信息工具

前端的JavaScript 模板引擎,比如ejs,jade等。可以根据传入的参数,对模板标签进行替换,最终生成html。

如果把所有项目文件,不管文件后缀名,都看成是ejs模板,则可以在文件内容中使用ejs语法。

再根据配置信息进行替换,最终生成新文件。

其实,业界依据这个想法,已经有成熟的工具产生。

  • mem-fs

mem-fs 是对文件进行读取,存入内存中。

  • mem-fs-editor

mem-fs-editor 是对内存中的文件信息,使用ejs语法进行编译。最后调用 commit 方法输出最终文件。

3.3 提示信息工具

提示信息,除了 console.log ,还可以使用色彩更丰富的 chalk 。

这样,可以输出更直观、友好的提示。

3.4 文件操作

文件操作,有业界成熟的 shelljs 。

利用 shelljs ,可以在项目中简化以下步骤:

shelljs.copySync
shelljs.mkdir

四、实现

以下按我做的开源项目—— npm-package-cli 的创作过程进行分拆、讲解。

4.1 初始化

新建项目文件夹 npm-package-cli ,并在该文件夹下运行 npm init ,生成 package.json 。

项目结构如下:

npm-package-cli
        |-- package.json

4.2 生成全局指令

这里要生成的全局指令是 npg-cli 。

4.2.1 新建执行文件

新建文件夹 bin ,并在文件夹下新建名称为 cli 的shell脚本文件(注意:不能有后缀名)。

cli shell脚本文件内容如下:

#!/usr/bin/env node

console.log('hello world');

其中, #!/usr/bin/env node 是告诉编译器,以 node 的方式,运行代码。

并在 package.json 加入以下内容:

"bin": {
    "npg-cli": "bin/cli"
}

此时,项目结构如下:

npm-package-cli
        |-- bin
            |-- cli
        |-- package.json

4.2.2 链接指令到全局

链接指令有两种方式:

npm link
npm install -g

两种方式,都需要在 npm-package-cli 文件夹下运行,才能生效。

作用是把 npg-cli 指令,指向全局的 bin 文件下,实现软链。

4.2.3 运行

在任意文件夹下运行命令:

npg-cli

# 输出
hello world

到这里,一个基本的指令就算完成了,接下来是指令的工作内容细化。

4.3 初始化操作类Creation

Creation 的作用是整合所有操作,并提供接口给指令文件 cli 。

Creation 的结构如下:

class Creation{
  constructor(){
    // code
  }
  do(){
      // code
  }
  // other function
}

其中 do 方法暴露给脚本文件 cli 调用。

Creation 类放在 src/index.js 中。

此时,项目结构如下:

npm-package-cli
        |-- bin
            |-- cli
        |-- src
            |-- index.js
        |-- package.json

4.4 修改 cli 文件

#!/usr/bin/env node

const Creator = require('../src/index.js');

const project = new Creator();

project.do();

这样,只要实现好 do 方法,就可以完成 npg-cli 指令的运行了。

4.5 实现命令行参数读取

实现 npg-cli --help ,需要借助上文提到的工具 commander 。

新建 src/command.js 文件,文件内容如下:

const commander = require('commander');
const chalk = require('chalk');

const packageJson = require('../package.json');
const log = console.log;

function initCommand(){
    commander.version(packageJson.version)
        .on('--help', ()=>{
            log(chalk.green('  run testcli and edit the setting.'));
        })
        .parse(process.argv);
}

module.exports = initCommand;

此时,项目结构如下:

npm-package-cli
        |-- bin
            |-- cli
        |-- src
            |-- command.js
            |-- index.js
        |-- package.json

然后在 Creation.do 方法内执行 initCommand() 即可生效。

// src/index.js Creation
const initCommand = require('./command');

class Creation{
    // other code
    do(){
        initCommand();
    }
}

此时,运行 npg-cli --help 指令,就可以看到:

Usage: npg-cli [options]

Options:
  -V, --version  output the version number
  -h, --help     output usage information
  run testcli and edit the setting.

4.6 获取用户输入配置信息

要获取用户输入的信息,需要借助工具 inquirer 。

新建 src/setting.js 文件,文件内容如下:

const inquirer = require('inquirer');
const fse = require('fs-extra');

function initSetting(){
    let prompt = [
        {
            type: 'input',
            name: 'projectName',
            message: 'project name',
            validate(input){
                if(!input){
                    return 'project name is required.'
                }
                if(fse.existsSync(input)){
                    return 'project name of folder is exist.'
                }
                return true;
            }
        },
        // other prompt
    ];

    return inquirer.prompt(prompt);
}

module.exports = initSetting;

此时,项目结构如下:

npm-package-cli
        |-- bin
            |-- cli
        |-- src
            |-- command.js
            |-- index.js
            |-- setting.js
        |-- package.json

然后在 Creation.do 方法内执行 initSetting() 即可生效。

// src/index.js Creation
const initCommand = require('./command');
const initSetting = require('./setting');

class Creation{
    // other code
    do(){
        initCommand();
        initSetting().then(setting => {
            // 用户输入完成后,会得到全部输入信息的json数据 setting
        });
    }
}

这里, inquirer.prompt 方法装载好要收集的问题后,返回的是 Promise 对象。收集完成之后,要在 then 方法内拿到 配置信息 ,以便进行下一步模板替换的操作。

4.7 模板文件替换输出

模板文件替换,要用到工具 mem-fs 和 mem-fs-editor 。

文件操作,要用到工具 shelljs 。

新建 src/output.js 文件,文件内容如下(删除了部分代码,以下只是示例,完整项目看最后分享链接):

const chalk = require('chalk');
const fse = require('fs-extra');
const path = require('path');
const log = console.log;

function output(creation){
    return new Promise((resolve, reject)=>{
        // 拿到配置信息
        const setting = creation._setting;
        const {
            projectName
        } = setting;
        // 获取当前命令行执行环境所在文件夹
        const cwd = process.cwd();

        // 初始化文件夹path
        const projectPath = path.join(cwd, projectName);
        const projectResolve = getProjectResolve(projectPath);
        
        // 新建项目文件夹
        fse.mkdirSync(projectPath);

        // copy文件夹
        creation.copy('src', projectResolve('src'));
        // 根据配置信息,替换文件内容
        creation.copyTpl('package.json', projectResolve('package.json'), setting);

        // 将内存中的文件,输出到硬盘上
        creation._mfs.commit(() => {
            resolve(); 
        });
    });
}

module.exports = output;

output 方法的作用:

  • 新建项目文件夹
  • 把模板文件读取出来,根据配置信息,进行替换(调用的是 mem-fs-editor 的 copyTpl 方法)
  • 拷贝其他文件
  • 输出最终文件到硬盘上

这里最重要的一步,是调用 mem-fs-editor 的方法后,要执行 mem-fs-editor 的 commit 方法,输出内存中的文件到硬盘上。

在 Creation.do 方法中,调用 output 方法即可输出新项目文件。 打开 src/index.js 文件,文件内容增加如下方法:

// src/index.js Creation
const initCommand = require('./command');
const initSetting = require('./setting');
const output = require('./output');

class Creation{
    // other code
    do(){
        initCommand();
        initSetting().then(setting => {
            // 用户输入完成后,会得到全部输入信息的json数据 setting
            this._setting = Object.assign({}, this._setting, setting);
            // 输出文件
            output(this).then(res => {
                // 项目输出完成
            });
        });
    }
}

4.8 阶段小结

自动初始化一个项目的流程不外乎以下三点:

  • 读取用户配置
  • 读取模板文件
  • 根据配置,编译模板文件,输出最终文件

命令行工具,是对这三点的有效整合,串连成一个规范的流程。

五、发布npm包的注意点

命令行工具中,使用的第三方工具包,都需要用 --save 的方式安装。

体现在 package.json 的表现是 dependencies 字段:

"dependencies": {
    "chalk": "^2.4.2",
    "commander": "^3.0.0",
    "fs-extra": "^8.1.0",
    "inquirer": "^6.5.0",
    "mem-fs": "^1.1.3",
    "mem-fs-editor": "^6.0.0",
    "shelljs": "^0.8.3"
},

这样,其他用户在安装你发布的CLI工具时,才会自动安装这些依赖。

六、项目开源

我创作的 npm-package-cli ,是专门用于生成个人 npm package 项目的CLI工具。

生成的项目,囊括以下功能点:

coverage
CHANGELOG.md

CLI工具安装方式:

npm install -g npm-package-cli

开源仓库地址: https://github.com/wall-wxk/npm-package-cli

如果对你有所帮助,麻烦给个Star,你的肯定是我前进的动力~

查看原文: node命令行工具之实现项目工程自动初始化的标准流程

  • brownkoala
  • bigswan
  • beautifuldog
  • brownleopard
  • heavyrabbit
  • brownostrich
  • heavyfrog
  • yellowbutterfly
需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。