咸鱼呀 咸鱼呀
首页
  • 博客文章

    • node搭建简易cli
    • 一个好看的loading
    • CSS-笔记
    • git提交规范
    • JS工具函数-转载
    • Vue组件打包到发布流程
    • nuxt.js-相关配置信息
    • nvm-upgrade
    • NVM 安装 之 window
    • 正则常用表达式
    • vue 3.0 生命周期函数记录
    • vue 对IE的支持
    • vue入门技术分享
    • 解决ios端inout失焦时,底部白底
    • WebStorm配置本地测试服务器
  • 分类
  • 标签
  • 归档
关于
博客 (opens new window)
GitHub (opens new window)

s-xianyu

前端小白
首页
  • 博客文章

    • node搭建简易cli
    • 一个好看的loading
    • CSS-笔记
    • git提交规范
    • JS工具函数-转载
    • Vue组件打包到发布流程
    • nuxt.js-相关配置信息
    • nvm-upgrade
    • NVM 安装 之 window
    • 正则常用表达式
    • vue 3.0 生命周期函数记录
    • vue 对IE的支持
    • vue入门技术分享
    • 解决ios端inout失焦时,底部白底
    • WebStorm配置本地测试服务器
  • 分类
  • 标签
  • 归档
关于
博客 (opens new window)
GitHub (opens new window)
  • 学习笔记
s-xianyu
2022-02-26

开发一个个人的cli

一、脚手架的本质作用和解决了前端的那些问题

1.为了创建项目的一个基本结构 提供项目规范和约定

  • 相同的组织结构
  • 相同的开发范式
  • 相同的模块依赖
  • 相同的工具配置
  • 相同的代码基础

使用到的包及说明

  • axios---拉取github模板名和信息
  • commander---nodejs命令行工具
  • inquirer---命令行和用户交互工具
  • fs-extra---fs的扩展工具,支持promise
  • util---工具类让download-git-repo支持异步
  • download-git-repo---下载工具(GitHub, GitLab, Bitbucket)

未使用到的

  • chalk---为命令行加字体颜色和背景颜色
  • ora---下载时的loading
  • progress---下载时的进度条

2.脚手架就是在启动的时候询问一些简单的问题,并且通过用户回答的结果去渲染对应的模板文件,基本工作流程如下: 通过命令行交互询问用户问题 根据用户回答的结果生成文件

二、常用的脚手架工具

Yeoman 通用项目脚手架 灵活易拓展 Plop 创建特定类型的文件/模块

三、开始搭建cli

  • 新建项目目录 xianyu-cli
  • npm init 生成 package.json 文件

Snipaste_2022-02-24_23-40-19.png

  • 创建脚手架入口js和package.json配置信息
  • 把Npm命令软链到全局
  • 安装commander (opens new window)

Snipaste_2022-02-24_23-50-28.png

# 如果软链被占用、可以手动删除一下
npm rm | uninstall -g packageName
# 如果不确定可以查看一下是否已存在
npm ls -g packageName

Snipaste_2022-02-25_00-03-28.png

编辑cli.js测试一下代码

#! /usr/bin/env node

const program = require('commander') // node 命令行工具

program
  .version('0.1.0')
  .command('create <name>')
  .description('确认创建一个新项目吗?')
  .action(name => {
    // 打印命令行输入的值
    console.log('您创建的项目名称为' + name)
  })

program
  // 版本号信息
  .version(`v${require('../package.json').version}`)
  .usage('<command> [option]')
program.parse(process.argv)

接下来我们就可以来创建create命令 编写create代码 安装fs 的扩展工具 fs-extra (opens new window) fs-extra是fs的一个扩展工具,支持 promise 方法,编写create.js时会使用

mkdir lib
touch create.js
npm install fs-extra --save
  • 在cli.js里引入create.js 并把name和options传进去
...
program
  .version('1.0.0')
  .command('create <name>')
  .description('确认创建一个新项目吗?')
  // -f or --force 为强制创建,如果创建的目录存在则直接覆盖
  .option('-f, --force', '强制创建传入')
  .action((name, options) => {
    // 打印命令行输入的值
    console.log('您创建的项目名称为' + name)
    // 引入create,把创建任务放在create里执行
    require('../lib/create.js')(name, options)
  })
...
// lib/create.js

const path = require('path')
const fs = require('fs-extra')

// 执行创建命令
module.exports = async function (name, options) {
  console.log('您创建的项目名称为' + name, options)
  // 当前命令行选择的目录
  const cwd  = process.cwd();
  // 需要创建的目录地址
  const targetAir  = path.join(cwd, name)
  // 目录是否已经存在?
  if (fs.existsSync(targetAir)) {
    // 存在提示是否为强制创建? -f --force
    if (options.force) {
      await fs.remove(targetAir)
    } else {
      // TODO:询问用户是否确定要覆盖
    }
  }
}

Snipaste_2022-02-26_13-15-33.png

接下来创建用户的询问信息 询问用户是否进行重写或退出

// 上面引入inquirer
const inquirer = require('inquirer')
...
// TODO:询问用户是否确定要覆盖
let { action } = await inquirer.prompt([
  {
    name: 'action',
    type: 'list',
    message: '目录已存在,请选择一项继续操作!',
    choices: [
      {
        name: '重写',
        value: '重写'
      },{
        name: '退出',
        value: false
      }
    ]
  }
])
if (!action) {
  return;
} else if (action === '重写') {
  // 移除已存在的目录
  console.log(`\r\nRemoving...`)
  await fs.remove(targetAir)
}
...

测试截图 Snipaste_2022-02-26_14-08-51.png

到这里,简易版的cli就搭建完成了

四、从github生成模板

# 安装axios
npm install axios --save
# 在lib目录下新建 request.js 文件拉取模板列表
cd lib && touch request.js
// lib/request.js

// 通过 axios 处理请求
const axios = require('axios')

axios.interceptors.response.use(res => {
  return res.data;
})


/**
 * 获取模板列表
 * @returns Promise
 */
async function getRepoList() {
  return axios.get('https://api.github.com/users/s-xianyu/repos')
}

/**
 * 获取版本信息
 * @param {string} repo 模板名称
 * @returns Promise
 */
async function  getTagList(repo) {
  return axios.get(`https://api.github.com/repos/s-xianyu/${repo}/tags`)
}

module.exports = {
  getRepoList,
  getTagList
}

# 在lib目录下新建 generator.js 用户处理拉取模板创建逻辑代码
touch generator.js

拉取github仓库所有模板

// lib/generator.js

const { getRepoList } = require('./request')
const inquirer = require('inquirer')

// 添加加载动画
async function wrapLoading(fn, message, ...args) {
  console.log(message)
  try {
    // 执行传入方法 fn
    const result = await fn(...args)
    return result
  } catch (error) {
    console.log('模板生成失败,请重试!')
  }
}

class Generator {
  constructor (name, targetDir){
    // 目录名称
    this.name = name;
    // 创建位置
    this.targetDir = targetDir;
  }

  // 获取用户选择的模板
  // 1)从远程拉取模板数据
  // 2)用户选择自己新下载的模板名称
  // 3)return 用户选择的名称

  async getRepo() {
    // 1)从远程拉取模板数据
    const repoList = await wrapLoading(getRepoList, '模板获取中,请等待...')
    if (!repoList.length) return;

    // 返回全部
    // const repos = repoList.map(item => item.name))
    // 过滤我们需要的模板名称
    let repos = []
    repoList.map(item => {
      if (item.name.includes('uni') || item.name.includes('vue') || item.name.includes('xianyu-blog')) {
        repos.push(item.name)
      }
    })

    // 2)用户选择自己新下载的模板名称
    const { repo } = await inquirer.prompt({
      name: 'repo',
      type: 'list',
      choices: repos,
      message: '请选择你要创建的项目模板'
    })

    // 3)return 用户选择的名称
    return repo;
  }

  // 核心创建逻辑
  // 1)获取模板名称
  // 2)获取 tag 名称
  // 3)下载模板到模板目录
  async create(){

    // 1)获取模板名称
    const repo = await this.getRepo()

    console.log('你选择了项目为:' + repo)
  }
}

module.exports = Generator;

拉取模板版本号,更上面的逻辑是一样的 不同是需要传一个模板名称过去

async getRepo(){...}
async getTag(repo) {
  // 1)基于 repo 结果,远程拉取对应的 tag 列表
  const tags = await wrapLoading(getTagList, '版本号获取中,请等待...', repo);
  if (!tags?.length) return;

  // 过滤我们需要的 tag 名称
  const tagsList = tags.map(item => item.name);

  // 2)用户选择自己需要下载的 tag
  const { tag } = await inquirer.prompt({
    name: 'tag',
    type: 'list',
    choices: tagsList,
    message: '请选择版本号'
  })

  // 3)return 用户选择的 tag
  return tag
}
async create(){
  // 1)获取模板名称
  const repo = await this.getRepo()
  // 2) 获取 tag 名称
  const tag = await this.getTag(repo)

  console.log('你选择了项目为:' + repo + ',版本号为:'+ (tag || ''))
}
}

控制台测试结果 Snipaste_2022-02-26_17-36-31.png

五、拉取远程代码到本地

# 安装download-git-repo
npm install download-git-repo --save
# util工具可以让download-git-repo支持promise
npm install util --save

编写下载函数

const util = require('util')
const downloadGitRepo = require('download-git-repo') // 不支持 Promise

async create(){
  ...
  // 3)下载模板到模板目录
  await this.download(repo, tag)
}
async download(repo, tag){

  // 1)拼接下载地址
  const requestUrl = `s-xianyu/${repo}${tag?'#'+tag:''}`;

  // 2)调用下载方法
  await wrapLoading(
    this.downloadGitRepo, // 远程下载方法
    '模板下载中,请稍后...', // 加载提示信息
    requestUrl, // 参数1: 下载地址
    path.resolve(process.cwd(), this.targetDir)) // 参数2: 创建位置
}


# 脚手架的安装与使用
npm install -g xianyu-cli22
#查看版本
xy -V
# 使用简写名称拉取模板
xy create cli-demo
cd cli-demo
npm run server

Snipaste_2022-02-26_20-14-46.png

编辑 (opens new window)
#node#cli
上次更新: 3/10/2022, 3:57:03 AM
最近更新
01
nvm-upgrade
10-11
02
vue 3.0 生命周期函数记录
06-27
03
git提交规范
05-01
更多文章>
Theme by Vdoing | Copyright © 2022-2022 s-xianyu | 小站
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式