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


Posted in Javascript onAugust 12, 2019

一、目的

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

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,可以在项目中简化以下步骤:

一些项目文件,不需要修改,只用直接copy。可以使用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脚本文件(注意:不能有后缀名)。
clishell脚本文件内容如下:

#!/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-fsmem-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-editorcommit方法,输出内存中的文件到硬盘上。

在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工具。
生成的项目,囊括以下功能点:

  • 支持TypeScrpt
  • mocha+chai自动化测试,支持使用TypeScript编写测试用例支持测试覆盖率
  • coverage支持eslint,包括对TypeScript的lint检查
  • Git commit规范提交
  • Git版本自动打标签(standard-version),更新CHANGELOG.md
  • 输出的npm包

支持各种模块规范(AMD、CMD、CommonJS、ESModule)

CLI工具安装方式:

npm install -g npm-package-cli

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

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

总结

以上所述是小编给大家介绍的node命令行工具之实现项目工程自动初始化的标准流程,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Javascript 相关文章推荐
这段js代码得节约你多少时间
Dec 20 Javascript
使用js对select动态添加和删除OPTION示例代码
Aug 12 Javascript
JS实现title标题栏文字不间断滚动显示效果
Sep 07 Javascript
百度地图API之百度地图退拽标记点获取经纬度的实现代码
Jan 12 Javascript
JSONP跨域请求
Mar 02 Javascript
详解Vue组件实现tips的总结
Nov 01 Javascript
React diff算法的实现示例
Apr 20 Javascript
jQuery实现的点击标题文字切换字体效果示例【测试可用】
Apr 26 jQuery
javascript、php关键字搜索函数的使用方法
May 29 Javascript
Vue常用指令详解分析
Aug 19 Javascript
使用Three.js实现太阳系八大行星的自转公转示例代码
Apr 09 Javascript
JavaScript多种图形实现代码实例
Jun 28 Javascript
五分钟搞懂Vuex实用知识(小结)
Aug 12 #Javascript
no-vnc和node.js实现web远程桌面的完整步骤
Aug 11 #Javascript
Angular8基础应用之表单及其验证
Aug 11 #Javascript
浅谈javascript错误处理
Aug 11 #Javascript
axios异步提交表单数据的几种方法
Aug 11 #Javascript
node.js实现带进度条的多文件上传
Mar 27 #Javascript
基于Express框架使用POST传递Form数据
Aug 10 #Javascript
You might like
用sql命令修改数据表中的一个字段为非空(not null)的语句
2010/06/04 PHP
使用JSON实现数据的跨域传输的php代码
2011/12/20 PHP
在phpstudy集成环境下的nginx服务器下配置url重写
2019/12/02 PHP
HTML中Select不用Disabled实现ReadOnly的效果
2008/04/07 Javascript
javascript操作JSON的要领总结
2012/12/09 Javascript
js获取客户端外网ip的简单实例
2013/11/21 Javascript
javascript的parseFloat()方法精度问题探讨
2013/11/26 Javascript
js判断字符长度及中英文数字等
2014/03/19 Javascript
浅谈Javascript中匀速运动的停止条件
2014/12/19 Javascript
js 获取元素在页面上的偏移量的方法汇总
2015/04/13 Javascript
浅谈Sizzle的“编译原理”
2015/04/14 Javascript
Javascript中replace()小结
2015/09/30 Javascript
JS实现可自定义大小,可双击关闭的弹出层效果
2015/10/16 Javascript
jQuery移动页面开发中的触摸事件与虚拟鼠标事件简介
2015/12/03 Javascript
深入探秘jquery瀑布流的实现
2016/01/30 Javascript
Node.js开发者必须了解的4个JS要点
2016/02/21 Javascript
浅谈JS获取元素的N种方法及其动静态讨论
2017/08/25 Javascript
在react-router4中进行代码拆分的方法(基于webpack)
2018/03/08 Javascript
利用Bootstrap Multiselect实现下拉框多选功能
2019/04/08 Javascript
html+jQuery实现拖动滑块图片拼图验证码插件【移动端适用】
2019/09/10 jQuery
JavaScript中的函数式编程详解
2020/08/22 Javascript
[54:57]DOTA2-DPC中国联赛定级赛 Aster vs DLG BO3第二场 1月8日
2021/03/11 DOTA
[01:11:21]DOTA2-DPC中国联赛 正赛 VG vs Elephant BO3 第一场 3月6日
2021/03/11 DOTA
python使用urllib2提交http post请求的方法
2015/05/26 Python
深入解析Python中的变量和赋值运算符
2015/10/12 Python
Python中max函数用于二维列表的实例
2018/04/03 Python
解决python 输出是省略号的问题
2018/04/19 Python
基于Django ORM、一对一、一对多、多对多的全面讲解
2019/07/26 Python
Python使用itchat模块实现简单的微信控制电脑功能示例
2019/08/26 Python
python selenium 执行完毕关闭chromedriver进程示例
2019/11/15 Python
pymysql之cur.fetchall() 和cur.fetchone()用法详解
2020/05/15 Python
Python pexpect模块及shell脚本except原理解析
2020/08/03 Python
Servlet的生命周期
2013/08/25 面试题
自我评价中英文语句
2013/11/30 职场文书
《春笋》教学反思
2014/04/15 职场文书
五好文明家庭事迹材料
2014/12/20 职场文书