自制前端脚手架工具cli与发布npm包

297 阅读4分钟

前言

之前为了学习webpack和ts编译,写了几个脚手架,自制的脚手架包括vue和react框架,可选择js与ts语言,脚手架只包括最基本的依赖。为了更好的管理与使用自己的写的脚手架,就写了个cli npm包。使用方式如下:

npm i @sanhuamao1/sancli      // 安装
san-cli create <project-name> // 使用

项目说明

基本结构

- lib
	- config
		- constants  // 常量
		- repoMap    // 仓库地址的映射
	- core
		- commands   // 命令遍历
		- create     // create命令执行逻辑
		- help       // --help命令执行逻辑
- index		             // 入口
- package.json

所需依赖

  • commander:命令行操作
  • download-git-repo:用于下载仓库
  • inquirer@8:交互
  • ora@4:可视加载

先来安装一下吧~

npm i commander download-git-repo inquirer@8 ora@4

注意下载的版本,因为inquirer@8和ora@4才支持CommanJs

基本配置

  1. npm init -y初始化

  2. 进入package.json需要进行一些配置:

{
  "main": "./index.js",  // 入口文件
  "bin": {
    "san-cli": "./index.js"  // 通过执行`san-cli`可执行对应的文件
  },
}
  1. 进入index.js文件,声明执行环境
#!/usr/bin/env node 
console.log('okk')
  1. 全局链接命令
npm link
  1. 尝试执行,此时终端会打印出okk
san-cli

命令与文件说明

version与help

lib/config/constants.js

// 01 获取版本信息
const {version} = require('../../package.json')  

// 02 配置所有需要的命令。写法是commander规定的,具体可在官网查看
const actions = {
    create: {  // create 命令,会通过`san-cli create`执行
       alias: "c", // 别名
       description: "create a project", //描述
       examples: ["san-cli create <project-name>"],
    },
    "*": {     // 其他没有被匹配的命令
       alias: "",
       description: "command not found",
       examples: [],
    },
 }

module.exports={
    version,
    actions
}

lib/core/help.js

const program=require('commander')
const {actions} =require('../config/constants')  // 导入所有注册的命令

const helpOptions=()=>{
    program.on("--help", () => {
        console.log("\nExamples:")			      // 依次遍历打印所有命令的例子
        Reflect.ownKeys(actions).forEach((action) => {
            actions[action].examples.forEach((example) => {
              console.log(example)
           })
        })
     })
}

module.exports=helpOptions

index.js:现在将上面的内容注册在入口文件中

#!/usr/bin/env node 

const program=require('commander')
const helpOptions=require('./lib/core/help')
const { version } = require("./lib/config/constants")

program.version(version,'-v,--version') //设置版本命令
helpOptions() // 设置help命令

program.parse(process.argv);   // 最后需要执行下这条才会生效(!)

尝试一下:

san-cli -v
san-cli --help

注册所有命令

在上面已经写过actions常量了,现在根据常量注册一下所有命令。

lib/core/commands.js

const {actions} =require('../config/constants')
const program=require('commander')
const path=require('path')

const commands=()=>{
   Reflect.ownKeys(actions).forEach((action) => {
      program
         .command(action) // 配置命令名称
         .alias(actions[action].alias) // 配置命令别名
         .description(actions[action].description) // 配置命令描述
         .action(() => {
            if (action === "*") {
               console.log(actions[action].description) // 匹配不到时
            } else {
               // 匹配到命令时,获取对应的文件(该文件返回一个函数),并执行该函数
               // 假设action为create,那么下面的操作就是去导入create.js的函数,并执行它
                
               // 简单说明一下process.argv的结构:
               // [node可执行文件的绝对路径,当前执行文件的路径,其他参数]
               // 现在具体到 san-cli create myproject,
               // (由于前面在package.json进行了配置,其实node就可忽略,san-cli代表的就是index.js)
               //  翻译一下,实际执行的是:node index.js create myproject
               // 所以process.argv的结构是:
               // [node, index.js, create, myproject]
               // 现在通过.slice(3)剔除掉前面三个后就是[myproject]
               // 最后再解构一下,传入的就是 myproject,也就是写入的项目名称
               require(path.resolve(__dirname, action))(...process.argv.slice(3))
            }
         })
   })
}

module.exports=commands

现在回到入口文件注册一下:

index.js

#!/usr/bin/env node 

//...
const commands=require('./lib/core/commands')
commands()
//...

program.parse(process.argv);

create

现在就是写create命令的逻辑了。

下载仓库当然要下载地址,关于下载地址的写法可参考download-git-repo官网,我这边采用direct方式。

由于我提供了交互,我将根据交互结果为用户提供指定的模板。为了更好的管理模板,建议创建一个组织。下面san-cli就是我的组织,在组织里面可以创建很多仓库。

点进某个仓库之后,我需要的地址如图所示:

知道自己的地址结构后,就可以安心地写映射了:

lib/config/repoMap.js

const getReportUrl=(name)=>{
    return `direct:https://github.com/san-cli/${name}.git#main`
} // 可以通过#xxx来指明分支

const vueTsRepo=getReportUrl('vue-ts')
const vueJSRepo=getReportUrl('vue-js')
const reactJSRepo=getReportUrl('react-js')
const reactTsRepo=getReportUrl('react-ts')
const tsRepo=getReportUrl('ts')

// 由于是根据用户的交互结果来下载某个模板的
// 前面的key就是用户交互的结果,我要根据答案给用户提供模板
// 我这边设置的是:
// - 是否使用模板 0|无模板 1|vue 2|react
// - 使用的语言: 1|js    2|ts
// 这个比较灵活,根据自身需求写就行
const repoMap={
    '02':tsRepo,
    '11':vueJSRepo,
    '12':vueTsRepo,
    '21':reactJSRepo,
    '22':reactTsRepo
}

module.exports=repoMap

lib/core/create.js

const { promisify } = require("util")  // 转为异步
const download = promisify(require("download-git-repo"))
const ora=require('ora') // 可视加载
const inquirer = require("inquirer") // 交互
const repoMap = require("../config/repoMap") // 仓库地址映射

// 询问1:选择框架
const useFrame = ()=>{
    return inquirer.prompt([
        {
            type: "list",
            name: "frame",  // 交互结果会放在这个字段中
            message: "chose frame:",
            default: 0,
            choices: [
               { value: 0, name: "(no frame)" },
               { value: 1, name: "Vue" },
               { value: 2, name: "React" },
            ],
         }
    ])
}

// 询问2:选择语言
const useLang = ()=>{
    return inquirer.prompt([
        {
            type: "list",
            name: "lang",
            message: "chose lang:",
            default: 1,
            choices: [
               { value: 1, name: "JavaScript" },
               { value: 2, name: "TypeScript" },
            ],
         }
    ])
}

const getResult=async ()=> {
   const frame = await useFrame()
   // 当使用框架时,让用户选择语言
   if(frame.frame!==0){   
      const lang = await useLang()
      return `${frame.frame}${lang.lang}`
   }else{
   // 否则跳过第二个问题,直接返回结果
      return `${frame.frame}2`
   }
}

const createProjectAction = async (projectName) => {
   const result = await getResult()
   const spinner = ora('Fetch...').start(); // 开始可视加载
    
   // 传入仓库地址,项目名称,配置
   await download(repoMap[result], projectName, { clone: true }).then(()=>{
      spinner.succeed("success!")			// 结束可视加载
      console.log(`- cd ${projectName}`)
      console.log("- npm install     -- to install dependencies")
      console.log("- npm start       -- to run the project")
      console.log("- npm run build   -- to build the project")
   }).catch((error)=>{
      spinner.fail("error!",error)			// 结束可视加载
   })
}

module.exports = createProjectAction

尝试一下~

san-cli create mydemo

npm包

发布包

  1. 先去官网注册一个账号
  2. 执行如下命令
npm config set registry https://registry.npmjs.org  // 切换镜像
npm adduser // 添加或验证用户
username // 输入用户名 
password // 输入密码(不要怀疑,在这里不管你有没有输入,光标都是不会动的,输入密码后按回车就可)
email    // 输入邮箱
code     // 邮箱会接收一个一次性密码,输入即可

npm login  // 登录
npm adduser // 忘记要不要输入这个了 看着来
username
password
email
code

npm publish  // 发布,如果有问题,就换个名称,在package.json中修改name
npm config set registry http://registry.npm.taobao.org/ // 最后记得切换回来
  1. 最后记得保存好自己的npm包

更新包

npm version <update_type>  // 类型有:major minor patch 
npm publish