目录
- 一、 脚手架用到的工具
- 二、初始化项目
- 三、获取版本
- 四、安装依赖
- 五、 inquirer实现问答模式
- 六、 shell实现拉取代码(或者用download-git-repo)
- 七、download-git-repo实现拉取代码(或者用shelljs)
- 八、NPM发布
- 九、优化脚手架
- 十、常见错误
前端开发者都会用脚手架搭建vue、react项目,那么如何搭建一套自己的脚手架cli工具呢?
一、 脚手架用到的工具
名称 | 用途 |
---|---|
commander | 用于命令行的自定义指令 |
download-git-repo | 下载git仓库 |
fs-extra | fs的一个扩展 |
handlebars | 可以替换模板中的动态字符串 |
inquirer | 交互式步骤提示问答 |
ora | 动画效果 |
shelljs | shell脚本 |
chalk | 美化样式,高亮字体 |
二、初始化项目
- 初始化项目
npm init
新建bin文件夹,并在该文件夹下新建
index.js
,question.js
,create.js
配置初始化后生成的
package.json
文件
npm link链接到全局
主要是为了方便测试,把npm link
在安装在本地目录。执行npm link
之前,在package.json中指定bin 指定名字以及文件地址(上面我们已经配置过了), 然后执行npm link
(mac系统加sudo)。初步测试
#!/usr/bin/env node
需要固定在第一行,系统执行到这里后会沿着对应路径查找 node 并执行。
#! /usr/bin/env nodeconsole.log('测试')
执行guilai-cli 命令
guilai-cli
输出:
说明我们初步测试完成啦
三、获取版本
通过process.argv
可以以数组形式获取命令行参数,通过用户传来的不同参数来判断执行不同操作
#! /usr/bin/env nodeprogram.version(require('../package.json').version);program.parse(process.argv);
guilai-cli -V
输出:
四、安装依赖
默认安装最新版本的命令,启动后可能会有一系列报错,博主的插件版本不会报错,报错可按上图的版本
yarn add chalk commander download-git-repo fs-extra handlebars inquirer ora shelljs
五、 inquirer实现问答模式
- 在bin文件夹下新建question.js文件。
fs-extra
继承了fs
的所有方法,并在此基础上进行了扩展,fse.existsSync
判断项目是否重名
```javascriptconst fse = require("fs-extra")const create = [{name: 'conf',type: 'confirm',message: '是否创建新的项目?',}, {name: 'name',message: '请输入项目名称:',validate: function (val) {if (!val) {return '亲,你忘了输入项目的名称哦~'}if (fse.existsSync(val)) {return '当前目录已存在同名的项目,请更换项目名'}return true},//如果上面为false,则该步骤就不执行when: res => Boolean(res.conf)}, {name: 'desc',message: '请输入项目的描述:',when: res => Boolean(res.conf)},]module.exports = {create}
- index.js文件中
如果在刚开始的选项是否新建项目选择false时,answers.conf
的值就为false,将不会继续向下执行。
const program = require('commander');const inquirer = require('inquirer');const question = require("./question");const initAction = () => {inquirer.prompt(question.create).then(answers => {if(answers.conf){console.log(answers)console.log('项目名称:', answers.name)//testconsole.log("正在拷贝项目,稍等-----")}})}program.version(require('../package.json').version);program.command('init').description('创建项目').action(initAction);program.parse(process.argv);
六、 shell实现拉取代码(或者用download-git-repo)
同样还是在index.js中,拉取代码到本地。
const initAction = () => {inquirer.prompt(question.create).then( async answers => {// shell脚本console.log('项目名为:', answers.name);console.log('正在拷贝项目,请稍等-------')const remote = "https://github.com/zbsguilai/catui.git"//克隆地址const currentName = "guilai-test"const targetName = answers.name;shell.exec(` git clone ${remote} --depth=1 mv ${currentName} ${targetName} rm -rf ./${targetName}/.git cd ${targetName} yarn`, (error, stdout, stderr) => {if (error) {console.error(`exec error:${error}`)}console.log(stdout)console.log(stderr)console.log("项目拷贝成功啦---------")})}).catch(error => {red(`❌ 程序出错 ❌`)process.exit(1);});}
七、download-git-repo实现拉取代码(或者用shelljs)
在bin下新建create文件
- process.exit(code)方法用于通过NodeJS中的退出代码结束同时运行的进程。
参数:code:它可以是0或1。0表示没有任何类型的故障结束进程,而1表示由于某种故障而结束进程。
const download = require('download-git-repo')const ora = require('ora')const fse = require('fs-extra')const handlebars = require('handlebars')const myChalk = require('chalk')const { red, yellow, green } = myChalkfunction createProject(project) {//获取用户输入,选择的信息const { template, name, desc } = project;const spinner = ora("正在拉取框架...");spinner.start();download(template, name,{ clone: true }, async err => {if (err) {red(err);spinner.text = red(`拉取失败. ${err}`)spinner.fail()process.exit(1);} else {spinner.text = green(`拉取成功...`)spinner.succeed()spinner.text = yellow('请稍等,. 正在替换package.json中的项目名称、描述...')const multiMeta = {project_name: name,project_desc: desc}const multiFiles = [`${name}/package.json`]// 用条件循环把模板字符替换到文件去for (var i = 0; i < multiFiles.length; i++) {// 这里记得 try {} catch {} 哦,以便出错时可以终止掉 Spinnertry {// 等待读取文件const multiFilesContent = await fse.readFile(multiFiles[i], 'utf8')// 等待替换文件,handlebars.compile(原文件内容)(模板字符)const multiFilesResult = await handlebars.compile(multiFilesContent)(multiMeta)// 等待输出文件await fse.outputFile(multiFiles[i], multiFilesResult)} catch (err) {// 如果出错,Spinner 就改变文字信息spinner.text = red(`项目创建失败. ${err}`)// 终止等待动画并显示 X 标志spinner.fail()// 退出进程process.exit(1)}}// 如果成功,Spinner 就改变文字信息spinner.text = yellow(`项目已创建成功!`)// 终止等待动画并显示 ✔ 标志spinner.succeed()}});}module.exports = createProject
index.js
const initAction = () => {inquirer.prompt(question.create).then( async answers => {if (answers.conf) {createProject(answers)} else {red(` 您已经终止此操作 `)}}).catch(error => {red(`❌ 程序出错 ❌`)process.exit(1);});}
八、NPM发布
在此之前,博主有详细介绍将本地项目发布到npm,详细见本人底部
npm login//登录npm publish//发布
九、优化脚手架
- 使用ora实现动画效果(见上)
- 使用chalk美化字体(见上)
十、常见错误
附:如何实现一个公共组件库上传到npm并在项目中使用