likes
comments
collection
share

vue实现简易脚手架

作者站长头像
站长
· 阅读数 11

前言:

为什么需要自己的脚手架

  1. 公司内部后台管理项目架构基本一样,每次新建项目都要拷贝重命名
  2. 没有权限或者不知道框架的存在
  3. 通过简单命令一键下载,快速帮助我们搭建预定义好的项目模板

实现过程:

一:脚手架组织结构

vue实现简易脚手架

二:配置package.json文件

配置完成后,执行npm install命令【不建议修改dependencies版本

{
  "name": "ifweb",
  "version": "0.0.1",
  "description": "后台管理系统脚手架",
  "bin": {
    "ifweb": "./bin/cli.js"
  },
  "scripts": {
    "watch": "npm run compile -- --watch"
  },
  "keywords": [
    "fweb-cli",
    "fweb",
    "后台管理",
    "管理系统",
    "脚手架"
  ],
  "license": "MIT",
  "dependencies": {
    "axios": "^0.21.1",
    "chalk": "^4.1.2",
    "commander": "^8.1.0",
    "download-git-repo": "^3.0.2",
    "figlet": "^1.5.2",
    "fs-extra": "^10.0.0",
    "inquirer": "^8.1.2",
    "ora": "^5.4.0"
  }
}

三:配置command目录

3.1:配置http.js文件内容

const axios = require('axios')

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

/**
 * 获取模版列表
 * @return Promise
 */
async function getRepoList() {
  return axios.get('https://api.github.com/orgs/xxx/xxx/repos')
}

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

module.exports = {
  getRepoList,
  getTagList
}

3.2:配置generator.js文件内容

const { getRepoList, getTagList } = require('./http')
const ora = require('ora')
const inquirer = require('inquirer')
const util = require('util')
const path = require('path')
const downloadGit = require('download-git-repo')
const chalk = require('chalk')

/**
 * 添加动画
 * @param {*} fn
 * @param {*} message
 * @param  {...any} args
 * @returns
 */
const drawLoading = async (fn, message, ...args) => {
  // 使用 ora 初始化,传入提示信息 message
  const spinner = ora(message)
  // 开始加载动画
  spinner.start()
  try {
    // 执行传入方法 fn
    const result = await fn(...args)
    //  状态修改为成功
    spinner.succeed()
    return result
  } catch (error) {
    // 状态修为失败
    spinner.fail('Request fail, refetch ....', error)
  }
}

class Generator {
  constructor(name, targetDir) {
    // 目录名称
    this.name = name
    // // 创建位置
    this.targetDir = targetDir
    // 对 download-git-repo 进行 promise 化改造
    this.downloadGit = util.promisify(downloadGit)
  }

  /**
   * 获取用户选择的模板
   * @returns 用户选择的模板名称
   */
  async getRepo() {
    const repoList = await drawLoading(getRepoList, 'wait fetch template')
    if (!repoList) return

    const repos = repoList.map((item) => item.name)

    const { repo } = await inquirer.prompt({
      name: 'repo',
      type: 'list',
      choices: repos,
      message: 'Please choose a template to create project'
    })

    return repo
  }

  /**
   * 获取用户选择的版本
   * @param {*} repo 已选择模板
   * @returns 选择的 tag
   */
  async getTag(repo) {
    // 基于 repo 结果,远程拉取对应的 tag 列表
    const tags = await drawLoading(getTagList, 'waiting fetch tag', repo)
    if (!tags) return

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

    // 用户选择自己需要下载的 tag
    const { tag } = await inquirer.prompt({
      name: 'tag',
      type: 'list',
      choices: tagsList,
      message: 'Place choose a tag to create project'
    })

    return tag
  }

  /**
   * 下载远程模板tag
   */
  async download(repo, tag) {
    // 下载地址
    const requestUrl = `xxx/${repo}${tag ? '#' + tag : ''}`
    // 调用下载方法
    await drawLoading(
      this.downloadGit,
      'waiting download template',
      requestUrl,
      path.resolve(process.cwd(), this.targetDir)
    )
  }

  /**
   * 核心创建逻辑
   */
  async create() {
    const repo = await this.getRepo()
    const tag = await this.getTag(repo)
    // 下载模板到模板目录
    await this.download(repo, tag)
    // 模板使用提示
    console.log(`\r\nSuccessfully created project ${chalk.cyan(this.name)}`)
    console.log(`\r\n  cd ${chalk.cyan(this.name)}`)
    console.log('  npm run dev\r\n')
  }
}

module.exports = Generator

3.3:配置init.js文件内容

const path = require('path')
const fs = require('fs-extra')
const inquirer = require('inquirer')
const Generator = require('./generator')

module.exports = async function (name, options) {
  // 当前命令行选择的目录
  const cwd = process.cwd()
  // 需要创建的目录地址
  const targetAir = path.join(cwd, name)
  // 目录是否已经存在
  if (fs.existsSync(targetAir)) {
    if (options.force) {
      await fs.remove(targetAir)
    } else {
      // 询问用户是否确定要覆盖
      let { action } = await inquirer.prompt([
        {
          name: 'action',
          type: 'list',
          message: 'Target directory already exists  Pick an actions:',
          choices: [
            {
              name: 'Overwrite',
              value: 'overwrite'
            },
            {
              name: 'Cancel',
              value: false
            }
          ]
        }
      ])
      if (!action) {
        return
      } else if (action === 'overwrite') {
        //移除已经存在的目录
        console.log(`\r\nRemoving`)
        await fs.remove(targetAir)
      }
    }
  }

  const generator = new Generator(name, targetAir)
  generator.create()
}

四:配置bin目录

4.1 配置cli.js文件内容

#! /usr/bin/env node
const commander = require('commander')
const chalk = require('chalk')
var figlet = require('figlet')

commander
  .command('create <app-name>')
  .description('create a new project')
  .option('-f, --force', 'overwrite target directory if it exit')
  .action((name, options) => {
    console.log('name:', name, 'option:', options)
    require('../command/init.js')(name, options)
  })
  .version(`v${require('../package.json').version}`)
  .usage('<command> [option]')

// 配置 config 命令
commander
  .command('config [value]')
  .description('inspect and modify the config')
  .option('-g, --get <path>', 'get value from option')
  .option('-s, --set <path> <value>')
  .option('-d, --delete <path>', 'delete option from config')
  .action((value, options) => {
    console.log(value, options)
  })

commander.on('--help', () => {
  // 使用 figlet 绘制 Logo
  console.log(
    '\r\n' +
      figlet.textSync('FWEB', {
        font: 'Ghost',
        horizontalLayout: 'default',
        verticalLayout: 'default',
        width: 80,
        whitespaceBreak: true
      })
  )
  // 新增说明信息
  console.log(
    `\r\nRun ${chalk.cyan(`ifweb <command> --help`)} show details\r\n`
  )
})

commander.parse(process.argv)

五:执行命令

5.1 本地调试

打开项目终端,执行npm link链接到全局

5.2 全局安装脚手架

// 打开终端执行:
npm install -g ifweb
// 根据自身情况,选择磁盘执行:
cd code
ifweb create <project-name>

5.3 测试通过,脚手架发布到npm

注册npm账号:www.npmjs.com npm镜像恢复为原始路径:npm config set registry registry.npmjs.org/ 打开项目终端,执行:

// 添加npm用户
npm adduser
// 发布到npm
npm publish

六:获取模板信息

6.1 代码已上传至github/gitlab仓库

6.2 github:仓库获取地址

api.github.com/orgs/xxx/xxx/repos

6.3 github:仓库版本获取地址

api.github.com/repos/xxx/xxx