目录
前端开发者都会用脚手架搭建vue、react项目,那么如何搭建一套自己的脚手架cli工具呢?
一、 脚手架用到的工具
二、初始化项目
- 初始化项目
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 node
console.log('测试')
执行guilai-cli 命令
guilai-cli
输出:
说明我们初步测试完成啦
三、获取版本
通过process.argv
可以以数组形式获取命令行参数,通过用户传来的不同参数来判断执行不同操作
#! /usr/bin/env node
program.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
判断项目是否重名
```javascript
const 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)//test
console.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 } = myChalk
function 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 {} 哦,以便出错时可以终止掉 Spinner
try {
// 等待读取文件
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美化字体(见上)