【写个工作用的脚手架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/
以上是 【写个工作用的脚手架cli】用脚手架整合模板和配置 的全部内容, 来源链接: utcz.com/a/44394.html