前端脚手架搭建

61 篇文章 1 订阅
39 篇文章 0 订阅

以下均在node版本为14.18.1的环境下运行的

一.第一版(简易版)

1.基础配置

1.1 配置cli文件

cli.js

#! /usr/bin/env node

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

// 用于检查入口文件是否正常执行
console.log('my-node-cli working~/项目运行成功')

1.2 配置package.json

package.json

{
  "name": "my-node-cli",
  "version": "1.0.0",
  "description": "",
  "main": "cli.js",
  "bin": "cli.js", 
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

1.3 npm link 链接到全局

npm link详情解释

执行软链

npm link

执行具体如下图,会自动创建一个package-lock.json,并且会把my-node-cli通过软链接的方式放入到node安装目录下的node_modules文件夹中(其实就是一个快捷方式映射),这也是后面为什么我们能够直接使用my-node-cli指令的原因。
在这里插入图片描述
此时的目录图如下:
在这里插入图片描述
我们再看下本地的node文件夹和node_modules文件夹

在这里插入图片描述
在这里插入图片描述
点击上面的my-node-cli,发现直接显示了my-node-cli项目所有文件。

1.4 执行脚手架

命令行中输入 my-node-cli 执行一下(直接执行了bin指令对应的文件)

my-node-cli

在这里插入图片描述

2.package.json文件配置介绍

为了更好的理解项目的配置,以下对一个通用的package.json文件的配置项进行逐一解释

2.1 bin

一些包的作者希望可以作为命令行工具使用,配置好 bin 字段之后,通过 npm install package_name -g(package_name对应这里的是my-node-cli) 命令可以将脚本添加到执行路径中,之后可以在命令行中直接执行。

这样就可以直接命令行中使用 bin 字段里注册命令,而不需要加任何其余字段。

2.2 main

指定的是项目的入口文件

3.引入询问指令inquirer

3.1 询问用户信息

引入 inquirer.js,实现与询问用户信息的功能

$ npm install inquirer --dev

接着设置我们在 cli.js 需要询问的问题

#! /usr/bin/env node
const inquirer = require('inquirer')
inquirer.prompt([
  {
    type: 'input', //type: input, number, confirm, list, checkbox等操作类型 
    name: 'name', // key 名
    message: '请在后面输入内容,设置你的项目名称', // 提示信息
    default: 'my-node-cli' // 默认值,如未输入则采用默认值
  }
]).then(answers => {
  // 打印互用输入结果
  console.log(answers)
})

!!!注意这里再次运行my-node-cli,可能会报错:internal/modules/cjs/loader.js:1102 throw new ERR_REQUIRE_ESM(filename, parentPath, packageJsonPath);
在这里插入图片描述

这是因为在,inquirer9.0.0版本开始,模块化方式改为native esm modules,如果你的项目中使用的是CMD模块化方式,则需要限制inquirer的版本低于9.0.0,否则将会抛出以上错误。

ok,那我们就删除node_module文件夹,重新安装指定版本

npm install inquirer@8.0.0 --dev

再次执行my-node-cli,输入项目名称my-app

结果如下:
在这里插入图片描述

4.引入模板(v1)

4.1 新建模版文件夹

mkdir templates # 创建模版文件夹

在这里插入图片描述

4.2 在templates新建 index.html 和 common.css 两个示例文件

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>
    <!-- ejs 语法 -->
    <%= name %>
  </title>
</head>
<body>
  <h1><%= name %></h1>
</body>

</html>

index.css

/* common.css */
body {
  margin: 20px auto;
  background-color: azure;
}

在这里插入图片描述

4.3 接着完善文件生成逻辑

这里借助 ejs 模版引擎将用户输入的数据渲染到模版文件上

npm install ejs --save

修改cli.js文件:

#! /usr/bin/env node

const inquirer = require('inquirer')
const path = require('path')
const fs = require('fs')
const ejs = require('ejs')

inquirer.prompt([
  {
    type: 'input', //type:input,confirm,list,rawlist,checkbox,password...
    name: 'name', // key 名
    message: 'Your name', // 提示信息
    default: 'my-node-cli' // 默认值
  }
]).then(answers => {
  // 模版文件目录
  const destUrl = path.join(__dirname, 'templates'); 
  // 生成文件目录
  // process.cwd() 对应控制台所在目录
  const cwdUrl = process.cwd();
  // 从模版目录中读取文件
  fs.readdir(destUrl, (err, files) => {
    if (err) throw err;
    files.forEach((file) => {
      // 使用 ejs 渲染对应的模版文件
      // renderFile(模版文件地址,传入渲染数据)
      console.log(answers,'answers===')
      ejs.renderFile(path.join(destUrl, file), answers).then(data => {
        // 生成 ejs 处理后的模版文件
        fs.writeFileSync(path.join(cwdUrl, file) , data)
      })
    })
  })
})

此时会在根目录生成1个html文件和1个css文件,并且把用户在控制台输入的name值传入到了html中
在这里插入图片描述

二.热门脚手架工具库

1.commander 自定义命令行指令(v2)

以下通过一个简单案例进行介绍,目录如下:
在这里插入图片描述

package.json

{
  "name": "lw-cli",
  "version": "1.0.0",
  "description": "",
  "bin": "./bin/cli.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "T-Roc",
  "license": "ISC",
  "devDependencies": {
    "commander": "^7.2.0"
  }
}

引入 commander 依赖

npm install commander

bin/cli.js

#! /usr/bin/env node

const program = require('commander')

program
.version('0.1.0')
.command('create <name>')
.description('create a new project')
.action(name => { 
    // 打印命令行输入的值
    console.log("project name is " + name)
})

program.parse()

然后执行 npm link 将应用 lw-cli链接到全局:
完成之后,在命令行中执行 lw-cli

结果如下:

在这里插入图片描述
这个时候就有了 lw-cli 命令使用的说明信息,在 Commands 下面出现了我们刚刚创建的 create 命令 create <name>,我们在命令行中运行一下:

lw-cli create firstApp

在这里插入图片描述
控制台获取到了我们在create 的 name值

2.chalk 命令行美化工具(v3)

chalk(粉笔)可以美化我们在命令行中输出内容的样式,例如对重点信息添加颜色

2.1 安装依赖

// 不指定版本可能会报错require() of ES modules is not supported.
// 当前环境node版本为:14.18.1
npm install chalk@3.0.0

修改bin/cli.js为:

#! /usr/bin/env node

const program = require('commander')
const chalk = require('chalk')

program
.version('0.1.0')
.command('create <name>')
.description('create a new project')
.action(name => { 
    // 打印命令行输入的值

    // 文本样式
    console.log("project name is " + chalk.bold(name))

    // 颜色
    console.log("project name is " + chalk.cyan(name))
    console.log("project name is " + chalk.green(name))

    // 背景色
    console.log("project name is " + chalk.bgRed(name))

    // 使用RGB颜色输出
    console.log("project name is " + chalk.rgb(4, 156, 219).underline(name));
    console.log("project name is " + chalk.hex('#049CDB').bold(name));
    console.log("project name is " + chalk.bgHex('#049CDB').bold(name))
})

program.parse()

在命令行中运行项目 lw-cli create my-app 看一下效果
在这里插入图片描述

3.ora 命令行 loading 动效(v4)

3.1 安装依赖

// 不指定版本可能会报错require() of ES modules is not supported.
// 当前环境node版本为:14.18.1
npm install ora@4.0.0

修改bin/cli.js为:

#! /usr/bin/env node
const ora = require('ora')
// 自定义文本信息
const message = 'Loading unicorns'
// 初始化
const spinner = ora(message);
// 开始加载动画
spinner.start();

setTimeout(() => {
    // 修改动画样式

    // Type: string
    // Default: 'cyan'
    // Values: 'black' | 'red' | 'green' | 'yellow' | 'blue' | 'magenta' | 'cyan' | 'white' | 'gray'
    spinner.color = 'red';    
    spinner.text = 'Loading rainbows';

    setTimeout(() => {
        // 加载状态修改
        spinner.stop() // 停止
        spinner.succeed('Loading succeed'); // 成功 ✔
        // spinner.fail(text?);  失败 ✖
        // spinner.warn(text?);  提示 ⚠
        // spinner.info(text?);  信息 ℹ
    }, 2000);
}, 2000);

在命令行中运行项目 lw-cli create my-app 看一下效果
在这里插入图片描述

4. cross-spawn 跨平台 shell 工具(v5)

4.1 安装依赖

// 当前环境node版本为:14.18.1
npm install cross-spawn

修改bin/cli.js为:

#! /usr/bin/env node 

const spawn = require('cross-spawn');
const chalk = require('chalk')

// 定义需要安装的依赖
const dependencies = ['vue', 'vuex', 'vue-router'];

// 执行安装
const child = spawn('npm', ['install', '-D'].concat(dependencies), { 
    stdio: 'inherit' 
});

// 监听执行结果
child.on('close', function(code) {
    // 执行失败
    if(code !== 0) {
        console.log(chalk.red('Error occurred while installing dependencies!'));
        process.exit(1);
    }
    // 执行成功
    else {
        console.log(chalk.cyan('Install finished'))   
    }
})

在命令行中运行w-cli 看一下效果
在这里插入图片描述
同时可以看到执行了安装依赖'vue', 'vuex', 'vue-router'
在这里插入图片描述

三.搭建一个简单的脚手架(v6)

1.创建项目

根据目录结构,重新创建相应文件

在这里插入图片描述
package.json

{
  "name": "zhurong-cli",
  "version": "1.0.0",
  "description": "simple vue cli",
  "main": "index.js",
  "bin": {
    "zr": "./bin/cli.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": {
    "name": "T-Roc",
    "email": "lxp_work@163.com"
  },
  "license": "MIT"
}

cli.js

#! /usr/bin/env node

console.log('zhurong-cli working ~')

使用 npm link 链接到全局,输入bin的指令
在这里插入图片描述

2.创建脚手架启动命令

2.1 安装依赖

npm install commander --save

2.2 创建命令(v7)

修改cli.js 为:

#! /usr/bin/env node

const program = require('commander')

program
  // 定义命令和参数
  .command('create <app-name>')
  .description('create a new project')
  // -f or --force 为强制创建,如果创建的目录存在则直接覆盖
  .option('-f, --force', 'overwrite target directory if it exist')
  .action((name, options) => {
    // 打印执行结果
    console.log('name:',name,'options:',options)
  })
  
program
   // 配置版本号信息
  .version(`v${require('../package.json').version}`)
  .usage('<command> [option]')
  
// 解析用户执行命令传入参数
program.parse(process.argv);

在命令行输入 zr,检查一下命令是否创建成功
在这里插入图片描述
我们可以看到 Commands 下面已经有了 create [options] <app-name>,接着执行一下这个命令
在这里插入图片描述
依次拿到了输入信息,打印了nameoptions

2.3执行命令(v8)

创建 lib 文件夹并在文件夹下创建 create.js

// lib/create.js

module.exports = async function (name, options) {
  // 验证是否正常取到值
  console.log('>>> create.js', name, options)
}

bin/cli.js

#! /usr/bin/env node
const program = require('commander')

program
  .command('create <app-name>')
  .description('create a new project')
  .option('-f, --force', 'overwrite target directory if it exist') // 是否强制创建,当文件夹已经存在
  .action((name, options) => {
    // 在 create.js 中执行创建任务,require引入的是一个自执行函数,并传入了两个参数
    require('../lib/create.js')(name, options)
  })

  
program
   // 配置版本号信息
  .version(`v${require('../package.json').version}`)
  .usage('<command> [option]')
  
// 解析用户执行命令传入参数
program.parse(process.argv);

执行命令,可以看到指令参数传到了create.js中了
在这里插入图片描述

2.4 目录覆盖(v9)

//fs-extra 是对 fs 模块的扩展,支持 promise 
npm install fs-extra --save

修改create.js 为:

// lib/create.js

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

module.exports = async function (name, options) {
  // 执行创建命令

  // 当前命令行选择的目录
  const cwd  = process.cwd();
  // 需要创建的目录地址
  const targetAir  = path.join(cwd, name)
  console.log('需要创建的目录地址===',targetAir)
  // 目录是否已经存在?
  if (fs.existsSync(targetAir)) {

    // 是否为强制创建?
    if (options.force) {

      await fs.remove(targetAir)
    } else {
      // TODO:询问用户是否确定要覆盖
    }
  }
}

执行指令zr create my-project --force如下:
在这里插入图片描述

2.5 完善帮助信息(v10)

安装chalk 依赖

// 当前环境node版本为:14.18.1
npm install chalk@3.0.0

修改cli.js为:

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

program
    .command('create <app-name>')
    .description('create a new project')
    .option('-f, --force', 'overwrite target directory if it exist') // 是否强制创建,当文件夹已经存在
    .action((name, options) => {
        // 在 create.js 中执行创建任务
        require('../lib/create.js')(name, options)
    })

program
    // 监听 --help 执行
    .on('--help', () => {
        // 新增说明信息
        console.log(`\r\nRun ${chalk.cyan(`zr <command> --help`)} for detailed usage of given command\r\n`)
    })


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

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

输出如下:
在这里插入图片描述

2.6打印个 Logo(v11)

安装依赖

npm install figlet

修改cli.js为:

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

program
    .command('create <app-name>')
    .description('create a new project')
    .option('-f, --force', 'overwrite target directory if it exist') // 是否强制创建,当文件夹已经存在
    .action((name, options) => {
        // 在 create.js 中执行创建任务
        require('../lib/create.js')(name, options)
    })

// bin/cli.js
#! /usr/bin/env node
program
    .on('--help', () => {
        // 使用 figlet 绘制 Logo
        console.log('\r\n' + figlet.textSync('lw', {
            font: 'Ghost',
            horizontalLayout: 'default',
            verticalLayout: 'default',
            width: 80,
            whitespaceBreak: true
        }));
        // 新增说明信息
        console.log(`\r\nRun ${chalk.cyan(`roc <command> --help`)} show details\r\n`)
    })

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

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

输入zr --help,输入如下,可以看到配置的logo出现了:
在这里插入图片描述

3.询问用户问题获取创建所需信息

3.1 询问是否覆盖已存在的目录(v12)

安装指定版本依赖

npm install inquirer@8.0.0 --dev

然后询问用户是否进行 Overwrite
cli.js

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

program
    .command('create <app-name>')
    .description('create a new project')
    .option('--f, --force', 'overwrite target directory if it exist') // 是否强制创建,当文件夹已经存在
    .action((name, options) => {
        // 在 create.js 中执行创建任务
        require('../lib/create.js')(name, options)
    })

// bin/cli.js
#! /usr/bin/env node
program
    .on('--help', () => {
        // 使用 figlet 绘制 Logo
        console.log('\r\n' + figlet.textSync('lw', {
            font: 'Ghost',
            horizontalLayout: 'default',
            verticalLayout: 'default',
            width: 80,
            whitespaceBreak: true
        }));
        // 新增说明信息
        console.log(`\r\nRun ${chalk.cyan(`roc <command> --help`)} show details\r\n`)
    })

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

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

create.js

// lib/create.js

const path = require('path')

// fs-extra 是对 fs 模块的扩展,支持 promise 语法
const fs = require('fs-extra')
const inquirer = require('inquirer')

module.exports = async function (name, options) {
  // 执行创建命令

  // 当前命令行选择的目录
  const cwd  = process.cwd();
  // 需要创建的目录地址
  const targetAir  = path.join(cwd, name)
console.log(targetAir,'打印===targetAir')
  // 目录是否已经存在?
  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 action:',
          choices: [
            {
              name: 'Overwrite',
              value: 'overwrite'
            },{
              name: 'Cancel',
              value: false
            }
          ]
        }
      ])

      if (!action) {
        return;
      } else if (action === 'overwrite') {
        // 移除已存在的目录
        console.log(`\r\nRemoving...`)
        await fs.remove(targetAir)
      }
    }
  }
}

我们来测试一下:

在当前目录,即命令行中显示的目录下手动创建2个目录,这里随便取名为 my-project my-project2
在这里插入图片描述

执行 zr create my-project,效果如下
在这里插入图片描述

  • 选择OverWrite则,my-project目录会被删除
  • 选择Cancel则,my-project目录会保留

在这里插入图片描述

移除后,再次执行zr create my-project会发现my-project并没有创建,这是因为还没有写创建的逻辑。

执行 zr create my-project2 --f,可以直接看到 my-project2 被移除

⚠️注意:为什么这里只做移除? 因为后面获取到模板地址后,下载的时候会直接创建项目目录

3.2 如何获取模版信息(v13)

更改package.json为:

{
  "name": "lw-cli",
  "version": "1.0.0",
  "description": "simple vue cli",
  "main": "index.js",
  "bin": {
    "zr": "./bin/cli.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": {
    "name": "T-Roc",
    "email": "lxp_work@163.com"
  },
  "license": "MIT",
  "dependencies": {
    "axios": "^1.3.4",
    "chalk": "^3.0.0",
    "commander": "^10.0.0",
    "figlet": "^1.5.2",
    "fs-extra": "^11.1.0",
    "inquirer": "^8.0.0",
    "ora": "^4.0.0"
  }
}

bin/cli.js

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

program
    .command('create <app-name>')
    .description('create a new project')
    .option('-f, --force', 'overwrite target directory if it exist') // 是否强制创建,当文件夹已经存在
    .action((name, options) => {
        // 在 create.js 中执行创建任务
        require('../lib/create.js')(name, options)
    })
program
    .on('--help', () => {
        // 使用 figlet 绘制 Logo
        console.log('\r\n' + figlet.textSync('lw', {
            font: 'Ghost',
            horizontalLayout: 'default',
            verticalLayout: 'default',
            width: 80,
            whitespaceBreak: true
        }));
        // 新增说明信息
        console.log(`\r\nRun ${chalk.cyan(`roc <command> --help`)} show details\r\n`)
    })

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

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

lib/create.js

// lib/create.js

const path = require('path')

// fs-extra 是对 fs 模块的扩展,支持 promise 语法
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 action:',
          choices: [
            {
              name: 'Overwrite',
              value: 'overwrite'
            }, {
              name: 'Cancel',
              value: false
            }
          ]
        }
      ])

      if (!action) {
        return;
      } else if (action === 'overwrite') {
        // 移除已存在的目录
        await fs.remove(targetAir)
      }
    }
  }

    // 创建项目
    const generator = new Generator(name, targetAir);

    // 开始创建项目
    generator.create()
}

lib/Generator.js

// lib/Generator.js

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

// 添加加载动画
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 (error) {
        // 状态为修改为失败
        spinner.fail('Request failed, refetch ...')
    }
}

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

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

    async getRepo() {
        // 1)从远程拉取模板数据
        const repoList = await wrapLoading(getRepoList, 'waiting fetch template');
        if (!repoList) return;

        // 过滤我们需要的模板名称
        const repos = repoList.map(item => item.name);

        // 2)用户选择自己新下载的模板名称
        const { repo } = await inquirer.prompt({
            name: 'repo',
            type: 'list',
            choices: repos,
            message: 'Please choose a template to create project'
        })

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

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

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

        console.log('用户选择了,repo=' + repo)
    }
}

module.exports = Generator;

lib/http.js

// lib/http.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/orgs/zhurong-cli/repos')
}

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

module.exports = {
  getRepoList,
  getTagList
}

运行zr create my-project出现选择模板选项
在这里插入图片描述
选择一个模板后,显示:
在这里插入图片描述

3.3 用户选择版本(v14)

修改lib/Generator.js为:

// lib/Generator.js

const { getRepoList, getTagList } = require('./http')
const ora = require('ora')
const inquirer = require('inquirer')

// 添加加载动画
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 (error) {
        // 状态为修改为失败
        spinner.fail('Request failed, refetch ...')
    }
}

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

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

    async getRepo() {
        // 1)从远程拉取模板数据
        const repoList = await wrapLoading(getRepoList, 'waiting fetch template');
        if (!repoList) return;

        // 过滤我们需要的模板名称
        const repos = repoList.map(item => item.name);

        // 2)用户选择自己新下载的模板名称
        const { repo } = await inquirer.prompt({
            name: 'repo',
            type: 'list',
            choices: repos,
            message: 'Please choose a template to create project'
        })

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

    // 获取用户选择的版本
    // 1)基于 repo 结果,远程拉取对应的 tag 列表
    // 2)用户选择自己需要下载的 tag
    // 3)return 用户选择的 tag

    async getTag(repo) {
        // 1)基于 repo 结果,远程拉取对应的 tag 列表
        const tags = await wrapLoading(getTagList, 'waiting fetch tag', repo);
        if (!tags) return;

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

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

        // 3)return 用户选择的 tag
        return tag
    }


    // 核心创建逻辑
    // 1)获取模板名称
    // 2)获取 tag 名称
    // 3)下载模板到模板目录
    async create() {
        // 1)获取模板名称
        const repo = await this.getRepo()

        // 2) 获取 tag 名称
        const tag = await this.getTag(repo)

        console.log('用户选择了,repo=' + repo + ',tag=' + tag)

    }
}

module.exports = Generator;

运行zr create my-project为:
在这里插入图片描述
选择模板后:

在这里插入图片描述
选择tag后
在这里插入图片描述

4. 下载远程模板(v15)

下载远程模版需要使用 download-git-repo 工具包,需要注意一个问题,它不支持 promise的,所以我们这里需要使用 util 模块中的 promisify 方法对其进行 promise

4.1 安装依赖与 promise 化

npm install download-git-repo --save

进行 promise 化处理,lib/Generator.js

// lib/Generator.js

const { getRepoList, getTagList } = require('./http')
const ora = require('ora')
const inquirer = require('inquirer')
const util = require('util')
const downloadGitRepo = require('download-git-repo') // 不支持 Promise

// 添加加载动画
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 (error) {
        // 状态为修改为失败
        spinner.fail('Request failed, refetch ...')
    }
}

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

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

    async getRepo() {
        // 1)从远程拉取模板数据
        const repoList = await wrapLoading(getRepoList, 'waiting fetch template');
        if (!repoList) return;

        // 过滤我们需要的模板名称
        const repos = repoList.map(item => item.name);

        // 2)用户选择自己新下载的模板名称
        const { repo } = await inquirer.prompt({
            name: 'repo',
            type: 'list',
            choices: repos,
            message: 'Please choose a template to create project'
        })

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

    // 获取用户选择的版本
    // 1)基于 repo 结果,远程拉取对应的 tag 列表
    // 2)用户选择自己需要下载的 tag
    // 3)return 用户选择的 tag

    async getTag(repo) {
        // 1)基于 repo 结果,远程拉取对应的 tag 列表
        const tags = await wrapLoading(getTagList, 'waiting fetch tag', repo);
        if (!tags) return;

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

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

        // 3)return 用户选择的 tag
        return tag
    }


    // 核心创建逻辑
    // 1)获取模板名称
    // 2)获取 tag 名称
    // 3)下载模板到模板目录
    async create() {
        // 1)获取模板名称
        const repo = await this.getRepo()

        // 2) 获取 tag 名称
        const tag = await this.getTag(repo)

        console.log('用户选择了,repo=' + repo + ',tag=' + tag)

    }
}

module.exports = Generator;

4.2 核心下载功能

// lib/Generator.js

// lib/Generator.js

const { getRepoList, getTagList } = require('./http')
const ora = require('ora')
const inquirer = require('inquirer')
const util = require('util')
const path = require('path')
const chalk = require('chalk')
const downloadGitRepo = require('download-git-repo') // 不支持 Promise

// 添加加载动画
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 (error) {
        // 状态为修改为失败
        spinner.fail('Request failed, refetch ...')
    }
}

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

    // 下载远程模板
    // 1)拼接下载地址
    // 2)调用下载方法
    async download(repo, tag) {

        // 1)拼接下载地址
        const requestUrl = `zhurong-cli/${repo}${tag ? '#' + tag : ''}`;
        console.log(requestUrl, '====下载地址为')

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


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

    async getRepo() {
        // 1)从远程拉取模板数据
        const repoList = await wrapLoading(getRepoList, 'waiting fetch template');
        if (!repoList) return;

        // 过滤我们需要的模板名称
        const repos = repoList.map(item => item.name);

        // 2)用户选择自己新下载的模板名称
        const { repo } = await inquirer.prompt({
            name: 'repo',
            type: 'list',
            choices: repos,
            message: 'Please choose a template to create project'
        })

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

    // 获取用户选择的版本
    // 1)基于 repo 结果,远程拉取对应的 tag 列表
    // 2)用户选择自己需要下载的 tag
    // 3)return 用户选择的 tag

    async getTag(repo) {
        // 1)基于 repo 结果,远程拉取对应的 tag 列表
        const tags = await wrapLoading(getTagList, 'waiting fetch tag', repo);
        if (!tags) return;

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

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

        // 3)return 用户选择的 tag
        return tag
    }


    // 核心创建逻辑
    // 1)获取模板名称
    // 2)获取 tag 名称
    // 3)下载模板到模板目录
    async create() {
        // 1)获取模板名称
        const repo = await this.getRepo()

        // 2) 获取 tag 名称
        const tag = await this.getTag(repo)

        console.log('用户选择了,repo=' + repo + ',tag=' + tag)

        // 3)下载模板到模板目录
        await this.download(repo, tag)
        // 4)模板使用提示
        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;

结果为:
在这里插入图片描述

5.发布项目

这里可以参考我之前写的文章注册发布自己的npm包

上传前将package.json文件的name改称xyjlw-cli,并把bin里面的zr改成lw
![在这里插入图片描述](https://img-blog.csdnimg.cn/36ccedc9ab7b4863928a728146945c2e.png

参靠之前的文章,进行发布
在这里插入图片描述
发布成功后,接着将原来项目的package.json文件的name改称lw-cli,bin里面的xyjlw-cli改成zr不然还会出现name冲突。

在这里插入图片描述

执行npm install -g xyjlw-cli ,全局安装
在这里插入图片描述

接着可以在本地文件夹看到下载的脚手架xyjlw-cli了,

  • bin对应的指令

在这里插入图片描述

  • 安装的脚手架

在这里插入图片描述

  • 接着使用bin对应的指令生成本地的项目
    在这里插入图片描述

在这里插入图片描述
参考地址

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值