背景

咱们用 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