一、目的
二、自动化流程分析
传统的前端项目初始流程一般是这样:
可以看出,传统的初始化步骤,花费的时间并不少。而且,人工操作的情况下,总有改漏的情况出现。这个缺点有时很致命。
甚至有马大哈,没有更新项目仓库地址,导致提交代码到旧仓库,这就很尴尬了。。。
基于这些情况,编写命令行工具(CLI)的目的就很明确:
甚至有马大哈,没有更新项目仓库地址,导致提交代码到旧仓库,这就很尴尬了。。。
基于这些情况,编写命令行工具(CLI)的目的就很明确:
- 用于新项目工程的初始化
- 利用工具进行初始化,可以节省项目初期的准备时间
- 避免出现改漏的情况
- 杜绝未更新项目版本仓库地址的问题
以下是新的流程示意图:
以下是自动化流程图:
从流程图可以得出两个重要的信息:
- 配置信息
- 模板文件
命令行工具的角色,是负责将两个信息进行融合,提供一个交互平台给用户。
三、工具准备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? description3.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,可以在项目中简化以下步骤:
- 一些项目文件,不需要修改,只用直接copy。可以使用shelljs.copySync同步方式生成。
- 一些文件夹,需要提前构建,可以使用shelljs.mkdir进行创建
以下按我做的开源项目——npm-package-cli的创作过程进行分拆、讲解。
4.1 初始化新建项目文件夹npm-package-cli,并在该文件夹下运行npm init,生成package.json。
项目结构如下:
npm-package-cli |-- package.json4.2 生成全局指令项目结构如下:
这里要生成的全局指令是npg-cli。
4.2.1 新建执行文件新建文件夹bin,并在文件夹下新建名称为cli的shell脚本文件(注意:不能有后缀名)。
clishell脚本文件内容如下:
clishell脚本文件内容如下:
#
!/usr/bin/env nodeconsole.log('hello world');其中,#!/usr/bin/env node是告诉编译器,以node的方式,运行代码。
并在package.json加入以下内容:
"bin": { "npg-cli": "bin/cli"}此时,项目结构如下:
npm-package-cli |-- bin |-- cli |-- package.json4.2.2 链接指令到全局链接指令有两种方式:
- npm link
- npm install -g
两种方式,都需要在npm-package-cli文件夹下运行,才能生效。
作用是把npg-cli指令,指向全局的bin文件下,实现软链。
4.2.3 运行作用是把npg-cli指令,指向全局的bin文件下,实现软链。
在任意文件夹下运行命令:
npg-cli#
输出hello world到这里,一个基本的指令就算完成了,接下来是指令的工作内容细化。
4.3 初始化操作类CreationCreation的作用是整合所有操作,并提供接口给指令文件cli。
Creation的结构如下:
class Creation{ Creation的结构如下:
constructor
(){ // code
} do
(){ // code
} // other function
}其中do方法暴露给脚本文件cli调用。
Creation类放在src/index.js中。
此时,项目结构如下:
npm-package-cli |-- bin |-- cli |-- src |-- index.js |-- package.json4.4 修改cli文件#
!/usr/bin/env nodeconst Creator = require('../src/index.js');const project = new Creator();project.do();这样,只要实现好do方法,就可以完成npg-cli指令的运行了。
4.5 实现命令行参数读取实现npg-cli --help,需要借助上文提到的工具commander。
新建src/command.js文件,文件内容如下:
新建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文件,文件内容如下:
新建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。
文件操作,要用到工具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包的注意点5.1 安装依赖包的方式命令行工具中,使用的第三方工具包,都需要用--save的方式安装。
体现在package.json的表现是dependencies字段:
体现在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工具时,才会自动安装这些依赖。
5.2 .gitignore文件npm官方是默认去除.gitignore文件的,不管你用任何方式声明.gitignore文件需要publish。
解决方式是:将.gitignore改名称,比如改为gitignore。当使用CLI工具时,再将文件名改回来。
例子:
creation.copy(解决方式是:将.gitignore改名称,比如改为gitignore。当使用CLI工具时,再将文件名改回来。
例子:
'gitignore'
, projectResolve('.gitignore'
));六、项目开源我创作的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作者简介:纯洁的代码,一个有故事的程序员。希望用文字,让你读懂代码世界。微信号gzitcast,欢迎大家找我获取各种资源。