从零开始搭建一个自己的脚手架

9 篇文章 0 订阅
6 篇文章 0 订阅
此文为已有一个自己的框架为前提,npm角度出发搭建一个脚手架
发布流程不阐述,自行发布
项目地址,npm地址均在文末

步骤

  • 创建项目
  • 开发脚手架命令
  • 控制台与用户交互获取创建信息
  • 远程下载模版
  • 发布

创建项目(start.js)

目标父级文件夹:

// 创建文件夹
mdkir zzy-react-cli(你的脚手架名称)

// 进入创建的文件夹
cd zzy-react-cli

// npm 初始化
npm init

...一路enter

首先创建下文件夹,npm初始化之后对package.json进行改造

{
  "name": "zzy-react-cli",
  "version": "1.0.0",
  "description": "这里是描述",
  "main": "./bin/start.js", // 入口
  "bin": {
    "zzy-cli": "./bin/start.js", // 配置启动文件路径, zzy-cli 为别名
  },
  "author": "zzy",
  "license": "ISC",
  "dependencies": {}
}

测试一下 start.js 文件是否有效

#! /usr/bin/env node

// #! 符号的名称叫 Shebang,用于指定脚本的解释程序
// Node CLI 应用入口文件必须要有这样的文件头
// 如果是Linux 或者 macOS 系统下还需要修改此文件的读写权限为 755
// 具体就是通过 chmod 755 cli.js 实现修改

// 检查入口是否正常执行
console.log('hello start.js')

这里为了调试方便,需要 npm link ,将这个npm包链接到全局上
如果是mac os系统,还需要前置 sudo 来增加权限,也就是 sudo npm link
执行成功
到这里说明这个npm包运行之后成功执行了这个文件,接着下一步。

开发脚手架命令(start.js)

子步骤:

  1. 需要依赖 commander 插件来进行实现
  2. 最初只使用一个 create 命令,如果已有创建目标的文件名,提示是否覆盖

安装 commander 这个插件

yarn add commander -D

处理 start.js

#! /usr/bin/env node

const program = require('commander')


program
  // 版本
  .version('1.0.0')
  // 新建命令 create
  .command('create <name>')
  // -f or --force 为强制创建,如果创建的目录存在则直接覆盖
  .option('-f, --force', '是否强制覆盖')
  // 描述
  .description('创建项目')
  // 回调
  .action((name, options) => {
    // 打印命令行输入的值
    console.log('name:', name, 'options:', options);
  })

// 解析用户执行命令传入参数
program.parse(process.argv)

到这里,为创建项目命名 就已经完成了

命令行输入 zzy-cli
在这里插入图片描述
这里看到已经加入了 create 这个命令,并赋予了描述,紧接着进行创建

zzy-cli create test-project

在这里插入图片描述

在这里我们已经拿到了项目的名称和配置项

紧接着在 bin 文件夹下创建 create.js 在这里进行创建项目的操作

创建文件夹(create.js)

创建之后在 start.js 中使用 create.js

program
  .version('1.0.0')
  .command('create <name>')
  // -f or --force 为强制创建,如果创建的目录存在则直接覆盖
  .option('-f, --force', '是否强制覆盖')
  .description('创建项目')
  .action((name, options) => {
    // 调用 create.js 并传入值
    require('./create.js')(name, options)
  })

创建目录时需要考虑到一个问题,就是当前文件夹内是否存在同名文件夹?

  • 当目录存在
    1. { fouce: true } 时 移除原先文件夹,进行创建
    2. { fouce: false } 时 询问用户是否进行强制覆盖,是则移除原有进行创建,否则终止进程
  • 当目录不存在 直接进行创建
    操作系统文件需要用到 fs-extra 插件
yarn add fs-extra -D

fs的使用详情参考 fs模块 Node.js API 文档

接下来对 create.js 这个文件进行编辑:

// bin.create.js
const path = require('path')
const fs = require('fs-extra')

module.exports = async function (name, options) {
  // 获取当前目录
  const cwd = process.cwd()
  // 获取目标文件夹地址
  const targetDir = path.join(cwd, name)

  // 是否已存在文件夹
  if (fs.existsSync(targetAir)) {
    // 是否强制创建(覆盖)
    if (options.force) {
      await fs.remove(targetAir)
    } else {
      // ...some code  询问用户是否确定要覆盖,下文阐述
    }
  }
}

控制台与用户交互获取创建信息(create.js)

子步骤:

  • 继续之前的流程,询问用户是否进行覆盖操作
  • 用户选择项目模版
  • 获取对应项目模版的链接

继续之前的流程,询问用户是否进行覆盖操作

这里需要用到 inquirer 插件
插件描述 npm.jsCSDN

这里不再阐述如何使用

const inquirer = require('inquirer')

// ...some code

// 是否已存在文件夹
  if (fs.existsSync(targetDir)) {
    // 是否强制创建(覆盖)
    if (options.force) {
      await fs.remove(targetDir)
    } else {
    // 异步获取结果
      let { action } = await inquirer.prompt([
        {
          name: 'action',
          type: 'list',
          message: '目录已存在,请选择一项进行操作:',
          choices: [
            {
              name: '覆盖',
              value: 'overwrite'
            }, {
              name: '取消',
              value: false
            }
          ],
        }
      ])
      // 根据结果做出相应处理
      if (!action) {
        return
      } else if (action === 'overwrite') {
        // 移除已存在的目录
        console.log(`\r\nRemoving...`)
        await fs.remove(targetDir)
      }
    }
  }

完善相关逻辑之后,测试一波:
当前目录下手动创建 test-project 文件夹,然后执行 create 命令
desc
选择覆盖之后,执行移除文件夹命令。

这里也可以直接使用 -f 或者 --force 强制执行

zzy-cli create test-project --force

这里之所以只移除,是因为线上拉取的模版会直接创建项目目录

获取模版信息

github提供了一些获取用户公开库的API接口

https://api.github.com/users//[github用户名称]/repos

这里建立 bin/https.js 来专门处理模版信息获取

// 远程下载模版
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/Weibienaole/repos')
}

module.exports = {
  getRepoList
}

用户选择模版

再在 bin 下新建 Generator.js 来专门处理 模版相关

这里用到了ora插件,用于显示状态
详情:npm简书

// bin/Generator.js
const ora = require('ora')
const { getRepoList } = require('./https')
const inquirer = require('inquirer')

class Generator {
  constructor(name, targetDir) {
    // 文件夹名称
    this.name = name

    // 位置
    this.targetDir = targetDir
  }
  async create() {
    // 1)异步获取模板名称
    const repo = await this.getRepo()
    console.log(repo);

  }
  /*
  获取用户选择的模版
    1.远程拉取模版数据
    2.用户选择所要下载的模版名称
    3.return用户选择的名称
  */

  async getRepo() {
    const repoList = await wrapLoading(getRepoList, '获取目标模版中...')
  	// 空则终止执行
    if (!repoList) return

    // 筛选指定项目,只要目标模版系列
    const repos = repoList.filter(item => item.name.indexOf('zzy-react-project') !== -1)

    // 2)用户选择需要下载的模板名称
    const { repo } = await inquirer.prompt({
      name: 'repo',
      type: 'list',
      choices: repos.map(item => item.description),
      message: '请选择一个模版进行创建'
    })

    // 3. return用户选择
    const selectRepos = repos.filter(item => item.description === repo)[0]
    return { name: selectRepos.name, branch: selectRepos.default_branch }
  }
}

// 添加加载动画
async function wrapLoading(fn, message, ...args) {
  // ora初始化,传入提示 message
  const spinner = ora(message)
  // 开始
  spinner.start()
  try {
    // 执行fn
    const result = await fn(...args)
    // 成功
    spinner.succeed()
    return result
  } catch {
    // 失败
    spinner.fail('请求失败,请重试...')
    return null
  }
}

module.exports = Generator

在 create.js 中引入并执行

// create.js
const Generator = require('./Generator')
// ...some code
module.exports = async function (name, options) {
	// ...some code
	// 最后询问完成之后进行调用
	const generator = new Generator(name, targetDir)
  	generator.create()
}

保存执行之后,最终得到以下内容:
选择
选择后者
在这里插入图片描述

这里已经拿到了目标模版的名称和分支

下载远程模版(Generator.js)

下载github模版需要download-git-repo插件,但是它不支持promise化,这里需要另一个插件将它转化为promise方式的,它是promisify

yarn add download-git-repo -D
yarn add util -D

downloadGitRepo promise化处理

// Generator.js
const downloadGitRepo = require('download-git-repo')
const util = require('util')
// ...some code
class Generator {
  constructor(name, targetDir) {
  	// ...some code
    // 对download-git-repo 进行promise话改造
    this.downloadGitRepo = util.promisify(downloadGitRepo);
  }
  // ...some code
}

紧接着是下载模版的核心逻辑:

  // 远程下载模版
  async download({ name, branch }) {
    // 拼接链接
    const requestUrl = `direct:https://github.com/Weibienaole/${name}/archive/refs/heads/${branch}.zip`

    // 2min 之后弹出,显示超时
    timer = setTimeout(() => {
      clearTimeout(timer)
      throw ('模版下载超时,请重试!')
    }, 1000 * 60 * 2);
    // 调用下载方法,进行远程下载
    await wrapLoading(
      this.downloadGitRepo,
      '正在下载目标模版中...',
      requestUrl,
      path.resolve(process.cwd(), this.targetDir)
    )
  }
  async create() {
    // 1)获取模板名称
    const repo = await this.getRepo()
    
	// 同步对指定模版进行下载
    await this.download(repo)
    console.log('模版下载成功!');
    clearTimeout(timer)
  }

到这里就在指定目录下远程下载了模版!
在这里插入图片描述
下载下的内容:
在这里插入图片描述

到这里就结束了,发布npm再次不阐述,自行搜索发布,很简单的

项目相关地址

zzy-react-cli

github

原始文章

如果转载,请标明作者、地址,谢谢。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值