学习总结篇,以是否造轮子来掂量学习效果。本篇次要介绍近期应用的一个cli工具
脚手架cli的解决的问题
随着公司各端的业务进行,前端方面会积淀出一些通用的解决方案和模板。此时,对立保护和治理就十分有必要了。allen-cli
就是基于这样的场景而诞生的。
这个我的项目脚手架,最终实现:整合各个模板,一键生成模板
应用示例
目前实现的性能为:
- 输出
allen init
命令抉择一个脚手架模版进行下载,而后创立对应的app。 - 动静抉择构建环境,适配挪动端等不同状况。
allen-cli的具体流程
我的项目的整体构造:
1. 创立我的项目
npm init
创立package.json
, 次要加上bin
命令
{ "bin": { "allen": "bin/allen", "allen-init": "bin/allen-init" },}
2. 解析参数
一个CLI须要通过命令行输出各种参数,能够间接用nodejs的process相干api进行解析,然而更举荐应用commander
这个npm包能够大大简化解析的过程。
#!/usr/bin/env nodeconst program = require('commander')console.log('version', require('../package').version)program .version(require('../package').version) .usage('<command> [项目名称]') .command('init', '创立新我的项目') .parse(process.argv)
3. main主体流程
allen-init
// NODE moudle// node.js 命令行解决方案const program = require("commander");// node.js path模块const path = require("path");// node.js fs模块const fs = require("fs");// 常见的交互式命令行用户接口的汇合const inquirer = require("inquirer");// 应用shell模式匹配文件const glob = require("glob");// 流动最新的npm包const latestVersion = require("latest-version");// node.js 子过程const spawn = require("child_process").spawn;// node.js 命令行环境的 loading成果, 和显示各种状态的图标const ora = require("ora");// The UNIX command rm -rf for node.const rm = require("rimraf").sync;async function main() { let projectRoot, templateName try { // 检测版本 let isUpate = await checkVersion(); // 更新版本 if (isUpate) await updateCli(); // 检测门路 projectRoot = await checkDir(); // 创立门路 makeDir(projectRoot) // 抉择模板 let { git } = await selectTemplate(); // 下载模板 templateName = await dowload(rootName, git); // 本地配置 let customizePrompt = await getCustomizePrompt(templateName, CONST.CUSTOMIZE_PROMPT) // 渲染本地配置 await render(projectRoot, templateName, customizePrompt); // 删除无用文件 deleteCusomizePrompt(projectRoot) // 构建完结 afterBuild(); } catch (err) { log.error(`创立失败:${err.message}`) afterError(projectRoot, templateName) }}
3.1 创立文件下载模板
创立文件和抉择模板
// 创立门路function makeDir (projectRoot) { if (projectRoot !== ".") { fs.mkdirSync(projectName); }}/** * 模板抉择 */function selectTemplate() { return new Promise((resolve, reject) => { let choices = Object.values(templateConfig).map(item => { return { name: item.name, value: item.value }; }); let config = { // type: 'checkbox', type: "list", message: "请抉择创立我的项目类型", name: "select", choices: [new inquirer.Separator("模板类型"), ...choices] }; inquirer.prompt(config).then(data => { let { select } = data; let { value, git } = templateConfig[select]; resolve({ git, // templateValue: value }); }); });}
下载模板, 用的是download-git-repo
const download = require('download-git-repo')const path = require('path')const ora = require('ora')const logSymbols = require("log-symbols");const chalk = require("chalk");const CONST = require('../conf/const')module.exports = function (target, url) { const spinner = ora(`正在下载我的项目模板,源地址:${url}`) target = path.join(CONST.TEMPLATE_NAME) spinner.start() return new Promise((resolve,reject) => { download(`direct:${url}`, target, { clone: true }, (err) => { if (err) { spinner.fail() console.log(logSymbols.fail, chalk.red("模板下载失败:(")); reject(err) } else { spinner.succeed() console.log(logSymbols.success, chalk.green("模板下载结束:)")); resolve(target) } }) })}
3.2 界面交互配置
采纳的是inquirer
的这个库
// 常见的交互式命令行用户接口的汇合const inquirer = require("inquirer");
3.3 本地配置
如果须要将一些配置放在本地文件,则能够创立一些本地配置
/** * * @param target 模板门路 * @param fileName 读取文件名 */function getCustomizePrompt (target, fileName) { return new Promise ((resolve) => { const filePath = path.join(process.cwd(), target, fileName) if(fs.existsSync(filePath)) { console.log('读取模板配置文件') let file = require(filePath) resolve(file) } else { console.log('该文件没有配置文件') resolve([]) } })}
template.json
{ type: "confirm", name: "mobile", message: "是否用于挪动端?" }, { type: "confirm", name: "flexible", message: "是否应用挪动端适配?", when: function (answers) { return answers.mobile } },
4. 波及到的node.js操作
// NODE moudle// node.js 命令行解决方案const program = require("commander");// node.js path模块const path = require("path");// node.js fs模块const fs = require("fs");// 常见的交互式命令行用户接口的汇合const inquirer = require("inquirer");// 应用shell模式匹配文件const glob = require("glob");// 流动最新的npm包const latestVersion = require("latest-version");// node.js 子过程const spawn = require("child_process").spawn;// node.js 命令行环境的 loading成果, 和显示各种状态的图标const ora = require("ora");// The UNIX command rm -rf for node.const rm = require("rimraf").sync;
5. 本地装置应用
在我的项目目录下运行npm i -g
,注册全局命令allen-cli
即可应用
C:\Users\XX\AppData\Roaming\npm
目录下会生成相应的可执行文件:
6. npm包allen-cli
一个根本的脚手架CLI就实现了。
npm包传送门
:https://www.npmjs.com/package/allen-cli- github传送门: 我的项目脚手架allen-cli
欢送试用:npm i -g allen-cli
相干解析
!/usr/bin/env node
应用过Linux或者Unix的开发者,对于Shebang应该不生疏,它是一个符号的名称,#!
。这个符号通常在Unix零碎的根本中第一行结尾中呈现,用于指明这个脚本文件的解释程序
。理解了Shebang之后就能够了解,减少这一行是为了指定用node执行脚本文件
。
当你输出一个命令的时候,npm是如何辨认并执行对应的文件的呢?
具体的原理阮一峰大神曾经在npm scripts 使用指南中介绍过。简略的了解:
就是输出命令后,会有在一个新建的shell中执行指定的脚本,在执行这个脚本的时候,咱们须要来指定这个脚本的解释程序是node。
在一些状况下,即便你减少了这一行,但还是可能会碰到一下谬误,这是为什么呢?
No such file or directory
为了解决这个问题,首先须要理解一下/usr/bin/env。咱们曾经晓得,Shebang是为了指定脚本的解释程序,可是不同用户或者不同的脚本解释器有可能装置在不同的目录下,零碎如何晓得要去哪里找你的解释程序呢?
/usr/bin/env就是通知零碎能够在PATH目录中查找。
所以配置#!/usr/bin/env node
, 就是解决了不同的用户node门路不同的问题,能够让零碎动静的去查找node来执行你的脚本文件。
看到这里你应该了解,为什么会呈现No such file or directory的谬误?因为你的node装置门路没有增加到零碎的PATH中。所以去进行node环境变量配置就能够了。
NPM 执行脚本的原理
npm 脚本的原理非常简单。每当执行npm run,就会主动新建一个 Shell,在这个 Shell 外面执行指定的脚本命令。因而,只有是 Shell(个别是 Bash)能够运行的命令,就能够写在 npm 脚本外面。
比拟特地的是,npm run新建的这个 Shell,会将当前目录的node_modules/.bin子目录退出PATH变量,执行完结后,再将PATH变量复原原样。
参考链接
- 阮一峰的网络日志-npm scripts 使用指南
- vue-cli
- http://nodejs.cn/api/