背景
我们用 vue,react,或是 vite,搭建项目时,大都是执行一个命令行,然后终端出现模板,让你选择,你输入项目名称,选择模板,选择一些配置项,是否需要安装等等,最后得到一个初始化的模板,然后在上面进行业务的开发。
实现一个这样的命令行脚手架,其实不难,借助一些第三方库,我们就能做到
为啥要自己自定义封装这么一个命令行脚手架
。减少重复性的工作,不再需要复制其他项目再删除无关代码,或者从零创建一个项目和文件。
。根据交互动态生成项目结构和配置文件等。
。多人协作更为方便,不需要把文件传来传去,ctrl + C/V。
快速讲解下实现的过程
- 在github仓库先建立好模板
- 用户通过命令交互的方式下载不同的模板
- 经过用户自定义的选择,将模板引擎渲染定制项目模板
- 模板变动,只需更新模板即可,不需要用户更新脚手架
用到的第三方库
js库 | 说明 |
---|---|
commander.js | 可以自动的解析命令和参数,用于处理用户输入的命令 |
download-git-repo | 下载并提取 git 仓库,用于下载项目模板 |
inquirer.js | 通用的命令行用户界面集合,用于和用户进行交互 |
handlebars.js | 模板引擎,将用户提交的信息动态填充到文件中 |
ora | 下载过程久的话,可以用于显示下载中的动画效果 |
chalk | 可以给终端的字体加上颜色 |
log-symbols | 可以在终端上显示出√或x等的图标 |
再放下各个插件的版本,避免因为插件的高低版本不同,导致同样的代码运行起来频频报错
json
"dependencies": {
"chalk": "^2.4.1",
"commander": "^12.0.0",
"download-git-repo": "^3.0.2",
"handlebars": "^4.0.11",
"inquirer": "^6.1.0",
"log-symbols": "^2.2.0",
"ora": "^3.0.0"
}
这里可能大家会有个好奇,package.json
中有 dependencies
,还有 devDependencies
,我为什么要把这些插件安装在 dependencies
中,而不是安装在 devDependencies
,我为什么 npm i xx -S
而不是 npm i xx -D
这里我不展开详细讲 dependencies
和 devDependencies
的区别,我就快速简单说一下:
devDependencies
里面平时放的都是工具类的插件,在生产环境中不会再用到
- 举个栗子:比如
Babel
这种,我不希望它在生产环境中还在,因为生产环境的代码是已经打包过之后,已经从 es6 处理成 es5 了,不需要再用Babel了
- 举个栗子:比如
dependencies
里面平时放的也是工具类的插件,但是这类插件在生产环境中也依然会用到
- 举个栗子:比如
echarts
, 我最后代码打包之后,在生产环境中要可视化展示一些数据,需要用到图表,那echarts, 就是在生产环境中也要用到的
- 举个栗子:比如
那问题来了:我开发自定义的脚手架,为什么扯到了dependencies
和 devDependencies
?它两的区别我也不是不知道。
公布答案:主要原因是因为开发脚手架,我在本地用这两个没啥区别,最后发现问题是出在发包之后
的下载安装
上
我本地调的没问题,我当时的依赖都是放在devDependencies
里的,发包之后
bash
# 发包之后进行安装
npm install --global my-hahaha-cli
经过排查,才发现,问题就是出在 devDependencies
上,当我把我的包卸载掉之后,把所有的依赖都转移到dependencies
上。
bash
# 卸载自己安装的包
npm uninstall --global my-hahaha-cli
# 重新发布之后,再次安装
npm install --global my-hahaha-cli
这个时候,依赖才自动安装上了。
本地环境
nodejs 14.18.0
因为我是本地用 nvm管理多版本的node, 同时也安装了 yarn, 今天复盘整理笔记的时候,之前是用 npm install来安装的,后来习惯性的用了 yarn 结果报错了,这里需要注意一下
简单介绍下各个插件
commander
作用:可以自动的解析命令和参数,用于处理用户输入的命令
node原生有 process.argv
这个属性,也能用户输入的命令。 但是这个属性是从第三个开始
才是用户输入的,比较麻烦, 所以我们还是使用 commander
库
bash
npm install commander@12.0.0 -S
download-git-repo
作用:这个包可以帮助我们下载 github仓库中的包到本地
bash
npm install download-git-repo@3.0.2 -S
使用这个库时,有个注意点
使用 download-git-repo
这个第三方包 进行模板的下载,先在自己的github上创建几个公开的git仓库,用 down-git-repo的时候,要注意一个问题,他的download方法
的第一个参数 要写 git仓库的地址,但是这个地址一般来讲,是 https://github.com/github用户名/仓库名
,但是在用这个第三方库download
的时候,https://github.com
后面跟紧的是 :(冒号
)然后才是 github用户名/仓库名
再跟着 #分支名
否则,会报一个128 没有权限的问题
inquirer
作用:主要是 实现向导作用,实现一问一答的交互方式
handlebars
作用:主要是 当作模板引擎来处理字符串,再配合 node自带的 fs模块
将处理好的字符串 替换掉原来的模板package.json
inquirrer
和 handlebars
配合使用
视觉美化的第三方库
ora
作用:控制台下的loading效果,加载中...
hash
npm install ora@3.0.0 -S
chalk
作用:为打印信息加上样式,比如成功信息为绿色,失败信息为红色,这样子会让用户更加容易分辨同时也让终端的显示更加的好看
hash
npm install chalk@2.4.1 -S
log-symbols
hash
npm install log-symbols@2.2.0 -S
作用:各种日志级别的彩色符号,给终端的提示信息前面加上 ✔,× 的符号
过程及代码
- 先在github上建立3个项目模板
如法炮制,再新建2个
然后,以第一个my-template1
为例,新建一个package.json,给里面的字段设置成动态的
github这边的已经设置好了,然后就是编码环节
- 这里我直接放代码了,讲解都写在了注释里了
index.js
js
#!/usr/bin/env node
//使用Node开发命令行工具所执行的 Javascript 脚本必须在顶部加入 #!/usr/bin/env node 声明
// console.log("hello demo")
// 1. 获取用户输入命令 (原生获取命令行参数的方法)
// console.log(process.argv)
const { Command } = require('commander');
const download = require('download-git-repo')
const chalk = require('chalk')
const logSymbols = require('log-symbols')
const inquirer = require('inquirer')
const fs = require('fs')
const handlebars = require('handlebars')
const ora = require('ora')
const program = new Command();
program
.version('0.1.0');
const templates = {
'tpl-obj1': {
url: 'https://github.com/OhBadWorld/my-template1',
downloadUrl: 'https://github.com:OhBadWorld/my-template1#main',
description: '基于vue2搭建的obj1模板'
},
'tpl-obj2': {
url: 'https://github.com/OhBadWorld/my-template2',
downloadUrl: 'https://github.com:OhBadWorld/my-template2#main',
description: '基于vue2搭建的obj2模板'
},
'tpl-obj3': {
url: 'https://github.com/OhBadWorld/my-template3',
downloadUrl: 'http://github.com:OhBadWorld/my-template3#main',
description: '基于vue2搭建的obj3模板'
}
}
// mycli init a a-name 基于 a模板进行初始化
// mycli init b b-name 基于 b模板进行初始化
// mycli init c c-name 基于 c模板进行初始化
program
.command('init <template-name> <project-name>')
.description('初始化项目模板')
.option('-s, --setup_mode [mode]', 'Which setup mode to use')
.action((templateName, projectName) => {
// 下载之前做 loading 提示
const spinner = ora('正在下载模板...').start();
// 根据模板名下载对应的模板到本地并取名为 projcetName
// console.log(templates[templateName], projectName);
// download
// 第一个参数: 仓库地址
// 第二个参数: 下载路径
const { downloadUrl } = templates[templateName]
download(downloadUrl, projectName, { clone: true }, (err)=>{
if (err) {
spinner.fail() // 下载失败提示
console.log(logSymbols.error, chalk.red(`${templateName} download fail`))
return
}
spinner.succeed() // 下载成功提示
// 把项目下的 package.json 文件读取出来
// 使用向导的方式采集用户输入的值
// 使用模板引擎把用户输入的数据解析到 package.json 文件中
// 解析完毕,把解析之后的结架重新写入package.json 文件中
inquirer.prompt([
{
type: 'input',
name: 'name',
message:'请输入项目名称'
},
{
type: 'input',
name: 'description',
message:'请输入项目简介'
},
{
type: 'input',
name: 'author',
message:'请输入作者名称'
},
]).then((answers)=>{
const packagePath = `${projectName}/package.json`
// 把采集到的用户输入的数据解析替换到 package.json 文件中
const packageContent = fs.readFileSync(packagePath, 'utf8')
// 通过 handlebars进行模板解析,作用 编译并替换
const packageResult = handlebars.compile(packageContent)(answers)
fs.writeFileSync(packagePath, packageResult)
console.log(logSymbols.success, chalk.green(`模板 ${templateName} init success`))
})
})
});
program
.command('list')
.description('查看所有可用模板')
.action(() => {
for (let key in templates) {
console.log(`
${key} ${templates[key].description}
`)
}
})
program.parse();
package.json
json
{
"name": "my-cli",
"version": "1.0.1",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"type": "commonjs",
"keywords": [],
"author": "",
"license": "ISC",
"bin": {
"my": "index.js"
},
"dependencies": {
"chalk": "^2.4.1",
"commander": "^12.0.0",
"download-git-repo": "^3.0.2",
"handlebars": "^4.0.11",
"inquirer": "^6.1.0",
"log-symbols": "^2.2.0",
"ora": "^3.0.0"
}
}
- 本地开发,npm link 命令
你本地开发,你要用自定义的脚手架命令,需要在项目的当前目录下,在终端执行npm link
命令,这样,你在任何位置目录的终端里,输入 my
,都可以执行到你自定义的命令。
如果你想换个命令名,不叫my
,叫my123
,也可以,你还是去在项目的当前目录下,在终端,先执行npm unlink
, 再执行 npm link
即可。
你本地开发好了,那就在项目的当前目录下,在终端执行npm unlink
即可。
发包
前置条件:将你本地的 npm源 设置成 npm官网的镜像源
在这里,需要把你的npm的镜像源设置成 npm官方网站的镜像源
因为 平时我们开发,下载依赖包,由于npm官网在国外,受到网络的传输速度的影响,平时我们都是在淘宝镜像
上下载依赖的。但是你要发包,是发布在npm官网
上,所以就要切换成 npm官网的依赖
bash
查看npm的配置
npm config list
设置官方源
npm config set registry https://registry.npmjs.org/
设置国内镜像源
npm config set registry http://registry.npmmirror.com/
这里推荐使用 nrm 来管理 镜像源,可以参考我这篇文章:npm 与 nrm
- 首先需要先登录 打开你的终端,任何位置的终端都行,输入 npm login,然后输入你的账号密码 (账号邮箱可以看见,密码是看不见的,你照旧输入就行)
- 进入你的脚手架目录,打开终端,输入 npm publish 命令
- 在输入命令之前,你先查下,你要发布的包名,在npm官网上有没有重名的,可能会存在冲突
- 莽一波,果然失败了
就是包名重复了
这下发布成功了
bash
# 下载依赖
npm install --global my-hahaha-cli
# 卸载依赖
npm uninstall --global my-hahaha-cli