自定义封装一个命令行脚手架

背景

我们用 vue,react,或是 vite,搭建项目时,大都是执行一个命令行,然后终端出现模板,让你选择,你输入项目名称,选择模板,选择一些配置项,是否需要安装等等,最后得到一个初始化的模板,然后在上面进行业务的开发。

实现一个这样的命令行脚手架,其实不难,借助一些第三方库,我们就能做到

自定义封装一个命令行脚手架

自定义封装一个命令行脚手架

为啥要自己自定义封装这么一个命令行脚手架

。减少重复性的工作,不再需要复制其他项目再删除无关代码,或者从零创建一个项目和文件。

。根据交互动态生成项目结构和配置文件等。

。多人协作更为方便,不需要把文件传来传去,ctrl + C/V。

快速讲解下实现的过程

  1. 在github仓库先建立好模板
  2. 用户通过命令交互的方式下载不同的模板
  3. 经过用户自定义的选择,将模板引擎渲染定制项目模板
  4. 模板变动,只需更新模板即可,不需要用户更新脚手架

用到的第三方库

js库 说明
commander.js 可以自动的解析命令和参数,用于处理用户输入的命令
download-git-repo 下载并提取 git 仓库,用于下载项目模板
inquirer.js 通用的命令行用户界面集合,用于和用户进行交互
handlebars.js 模板引擎,将用户提交的信息动态填充到文件中
ora 下载过程久的话,可以用于显示下载中的动画效果
chalk 可以给终端的字体加上颜色
log-symbols 可以在终端上显示出√或x等的图标

再放下各个插件的版本,避免因为插件的高低版本不同,导致同样的代码运行起来频频报错

  "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

这里我不展开详细讲 dependenciesdevDependencies的区别,我就快速简单说一下:

  1. devDependencies 里面平时放的都是工具类的插件,在生产环境中不会再用到
    • 举个栗子:比如 Babel 这种,我不希望它在生产环境中还在,因为生产环境的代码是已经打包过之后,已经从 es6 处理成 es5 了,不需要再用Babel了
  2. dependencies 里面平时放的也是工具类的插件,但是这类插件在生产环境中也依然会用到
    • 举个栗子:比如echarts, 我最后代码打包之后,在生产环境中要可视化展示一些数据,需要用到图表,那echarts, 就是在生产环境中也要用到的

那问题来了:我开发自定义的脚手架,为什么扯到了dependenciesdevDependencies?它两的区别我也不是不知道。

公布答案:主要原因是因为开发脚手架,我在本地用这两个没啥区别,最后发现问题是出在发包之后下载安装

我本地调的没问题,我当时的依赖都是放在devDependencies里的,发包之后

# 发包之后进行安装
npm install --global my-hahaha-cli

自定义封装一个命令行脚手架

经过排查,才发现,问题就是出在 devDependencies 上,当我把我的包卸载掉之后,把所有的依赖都转移到dependencies上。

# 卸载自己安装的包
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

npm install commander@12.0.0 -S

自定义封装一个命令行脚手架

download-git-repo

作用:这个包可以帮助我们下载 github仓库中的包到本地

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

inquirrerhandlebars配合使用

自定义封装一个命令行脚手架


视觉美化的第三方库

ora

作用:控制台下的loading效果,加载中…

npm install ora@3.0.0 -S

chalk

作用:为打印信息加上样式,比如成功信息为绿色,失败信息为红色,这样子会让用户更加容易分辨同时也让终端的显示更加的好看

npm install chalk@2.4.1 -S

自定义封装一个命令行脚手架

log-symbols

npm install log-symbols@2.2.0 -S

作用:各种日志级别的彩色符号,给终端的提示信息前面加上 ✔,× 的符号

自定义封装一个命令行脚手架

过程及代码

  1. 先在github上建立3个项目模板

自定义封装一个命令行脚手架

自定义封装一个命令行脚手架

如法炮制,再新建2个

自定义封装一个命令行脚手架

自定义封装一个命令行脚手架

然后,以第一个my-template1 为例,新建一个package.json,给里面的字段设置成动态的

自定义封装一个命令行脚手架

自定义封装一个命令行脚手架

自定义封装一个命令行脚手架

自定义封装一个命令行脚手架

github这边的已经设置好了,然后就是编码环节

  1. 这里我直接放代码了,讲解都写在了注释里了

自定义封装一个命令行脚手架

index.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

{
"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"
}
}
  1. 本地开发,npm link 命令

你本地开发,你要用自定义的脚手架命令,需要在项目的当前目录下,在终端执行npm link命令,这样,你在任何位置目录的终端里,输入 my,都可以执行到你自定义的命令。

如果你想换个命令名,不叫my ,叫my123,也可以,你还是去在项目的当前目录下,在终端,先执行npm unlink , 再执行 npm link 即可。

你本地开发好了,那就在项目的当前目录下,在终端执行npm unlink即可。

发包

前置条件:将你本地的 npm源 设置成 npm官网的镜像源

在这里,需要把你的npm的镜像源设置成 npm官方网站的镜像源

因为 平时我们开发,下载依赖包,由于npm官网在国外,受到网络的传输速度的影响,平时我们都是在淘宝镜像上下载依赖的。但是你要发包,是发布在npm官网上,所以就要切换成 npm官网的依赖

查看npm的配置
npm config list
设置官方源
npm config set registry https://registry.npmjs.org/
设置国内镜像源
npm config set registry http://registry.npmmirror.com/

这里推荐使用 nrm 来管理 镜像源,可以参考我这篇文章:npm 与 nrm

  1. 首先需要先登录
    打开你的终端,任何位置的终端都行,输入 npm login,然后输入你的账号密码
    (账号邮箱可以看见,密码是看不见的,你照旧输入就行)

自定义封装一个命令行脚手架

  1. 进入你的脚手架目录,打开终端,输入 npm publish 命令
    • 在输入命令之前,你先查下,你要发布的包名,在npm官网上有没有重名的,可能会存在冲突
      自定义封装一个命令行脚手架
    • 莽一波,果然失败了

自定义封装一个命令行脚手架
就是包名重复了

自定义封装一个命令行脚手架

自定义封装一个命令行脚手架

这下发布成功了

# 下载依赖
npm install --global my-hahaha-cli
# 卸载依赖
npm uninstall --global my-hahaha-cli

原文链接:https://juejin.cn/post/7350971127348641811 作者:海的对岸

(0)
上一篇 2024年3月28日 上午10:27
下一篇 2024年3月28日 上午10:39

相关推荐

发表回复

登录后才能评论