目录

  • 一、 脚手架用到的工具
  • 二、初始化项目
  • 三、获取版本
  • 四、安装依赖
  • 五、 inquirer实现问答模式
  • 六、 shell实现拉取代码(或者用download-git-repo)
  • 七、download-git-repo实现拉取代码(或者用shelljs)
  • 八、NPM发布
  • 九、优化脚手架
  • 十、常见错误

前端开发者都会用脚手架搭建vue、react项目,那么如何搭建一套自己的脚手架cli工具呢?

一、 脚手架用到的工具

名称用途
commander用于命令行的自定义指令
download-git-repo下载git仓库
fs-extrafs的一个扩展
handlebars可以替换模板中的动态字符串
inquirer交互式步骤提示问答
ora动画效果
shelljsshell脚本
chalk美化样式,高亮字体

二、初始化项目

  1. 初始化项目
npm init
  1. 新建bin文件夹,并在该文件夹下新建index.jsquestion.jscreate.js

  2. 配置初始化后生成的package.json文件

  3. npm link链接到全局
    主要是为了方便测试,把npm link在安装在本地目录。执行npm link之前,在package.json中指定bin 指定名字以及文件地址(上面我们已经配置过了), 然后执行npm link(mac系统加sudo)。

  4. 初步测试
    #!/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实现问答模式

  1. 在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}
  1. 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并在项目中使用