从0-1实现一个自己的脚手架

当我们在开发的时候,时常会在创建项目的时候重复很多操作,比如下载相同的依赖(锁定依赖版本)封装网络请求封装路由配置代码提示/检查配置vite/webpack打包配置自动导入(auto-import)配置项目基础结构注册全局指令等,有非常多重复的步骤其实都是可以被省略的,或者说开发者应该专注于业务的实现,而不是在这些上浪费大量的时间,所以我们可以将以上重复且关键的步骤提炼出来放在一个仓库里,然后创建一个属于我们自己的脚手架并放在npm上,通过全局安装这个脚手架,使用脚手架指令就可以直接下载我们存在仓库里面的基础模版了。

 一、创建脚手架

1、初始化脚手架

npm init -y

2、创建入口文件

mkdir bin

cd bin

touch www.js

//然后进入 www.js 文件 ,将下列代码粘贴上去

#! /usr/bin/env node

require('../index.js')

3、配置package.json的入口文件

//将以下代码贴入 package.json中

  "bin": "bin/www",

4、在根目录创建index.js并软链接到全局

touch index.js

cnpm link

5、安装依赖(工具)

    "axios": "^0.26.1",
    "chalk": "^4.0.0",
    "commander": "^9.0.0",
    "download-git-repo": "^3.0.2",
    "figlet": "^1.5.2",
    "fs-extra": "^10.0.1",
    "gitee-repo": "^0.0.2",
    "inquirer": "^8.2.1",
    "ora": "^5.0.0"

6、创建配置文件

        我已经将所要配置的文件都放在代码里面的,直接贴进去就行,当然也可以在我的基础上加以改造,可以让脚手架变的更加完善。

mkdir lib

touch api.js create.js Creator.js util.js

//index.js
const program = require("commander");
const chalk = require("chalk");
const figlet = require("figlet");
program
  .command("create <project-name>") //增加创建指令
  .description("create a new project") //添加描述信息
  .option("-f, --force", "overwrite target directory if it exists") //强制覆盖
  .action((projectName, cmd) => {
    // 处理用户输入create 指令附加的参数
    require("./lib/create")(projectName, cmd);
  });

program
  .command("config [value]")
  .description("inspect and modify the config")
  .option("-g, --get <key>", "get value by key")
  .option("-s, --set <key> <value>", "set option[key] is value")
  .option("-d, --detede <key>", "delete option by key")
  .action((value, keys) => {
    // 处理用户输入create 指令附加的参数
    console.log(value, keys);
  });

program.on("--help", function () {
  console.log(
    "\r\n" +
      figlet.textSync("当输入 --help 所展示的颜文字,可以自定义", {
        font: "3D-ASCII",
        horizontalLayout: "default",
        verticalLayout: "default",
        width: 80,
        whitespaceBreak: true,
      })
  );
  // 前后两个空行调整格式,更舒适
  console.log();
  console.log(
    `Run ${chalk.cyan(
      "自定义颜文字 <command> --help"
    )} for detailed usage of given command.`
  );
  console.log();
});

program
  .name("自定义脚手架命名")
  .usage(`<command> [option]`)
  .version(`dragon-cli-frontend ${require("./package.json").version}`);

// 解析用户执行时输入的参数
// process.argv 是 nodejs 提供的属性
// npm run server --port 3000
// 后面的 --port 3000 就是用户输入的参数
program.parse(process.argv);
********************************************************************************

//api.js
const axios = require("axios");

// 拦截全局请求响应
axios.interceptors.response.use((res) => {
  return res.data;
});

/**
 * 获取模版
 * @returns Promise
 */

async function getZhuRongRepo() {
  //这里我填写是gitee的
  return axios.get("https://gitee.com/api/v5/users/**这是你gitee的用户名**/repos");
}

/**
 * 获取仓库下的版本
 * @param {String} repo 仓库名称
 * @returns Promise
 */

async function getTagsByRepo(repo) {
  return axios.get(`https://gitee.com/api/v5/repos/**这是你gitee的用户名**/${repo}/tags`);
}

module.exports = {
    getZhuRongRepo,
    getTagsByRepo
}
********************************************************************************

//create.js
const path = require("path");
const fs = require("fs-extra");
const Inquirer = require("inquirer");
const Creator = require("./Creator");
const { loading } = require("./util");
module.exports = async function (projectName, options) {
  // 获取当前工作目录
  const cwd = process.cwd();
  const targetDirectory = path.join(cwd, projectName);

  if (fs.existsSync(targetDirectory)) {
    if (options.force) {
      // 删除重名目录
      await fs.remove(targetDirectory);
    } else {
      let { isOverwrite } = await new Inquirer.prompt([
        // 返回值为promise
        {
          name: "isOverwrite", // 与返回值对应
          type: "list", // list 类型
          message: "Target directory exists, Please choose an action",
          choices: [
            { name: "Overwrite", value: true },
            { name: "Cancel", value: false },
          ],
        },
      ]);
      if (!isOverwrite) {
        console.log("Cancel");
        return;
      } else {
        await loading(
          `Removing ${projectName}, please wait a minute`,
          fs.remove,
          targetDirectory
        );
      }
    }
  }

  // 创建项目
  const creator = new Creator(projectName, targetDirectory);

  creator.create();
};
********************************************************************************

//Creator.js
const inquirer = require("inquirer");
const downloadGitRepo = require("gitee-repo");
const chalk = require("chalk");
const fs = require("fs-extra");
const util = require("util");
const path = require("path");
const { loading } = require("./util");
const { getZhuRongRepo, getTagsByRepo } = require("./api");

class Creator {
  constructor(name, target) {
    this.name = name;
    this.target = target;
    // 转化为 promise 方法
    this.downloadGitRepo = util.promisify(downloadGitRepo);
  }
  // 创建项目部分
  async create() {
    // 仓库信息 —— 模板信息
    let repo = await this.getRepoInfo();

    // 标签信息 —— 版本信息
    console.log("我是版本信息", repo);
    let tag = await this.getTagInfo(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 install");
    console.log("  npm run serve\r\n");
  }
  // 获取模板信息及用户选择的模板
  async getRepoInfo() {
    // 获取组织下的仓库信息
    let repoList = await loading(
      "waiting for fetching template",
      getZhuRongRepo
    );
    if (!repoList) return;
    // 提取仓库名
    const repos = repoList.map((item) => item.name);
    // 选取模板信息
    let { repo } = await new inquirer.prompt([
      {
        name: "repo",
        type: "list",
        message: "Please choose a template to create project",
        choices: repos,
      },
    ]);
    return repo;
  }
  // 获取版本信息及用户选择的版本
  async getTagInfo(repo) {
    console.log(repo);
    let tagList = await loading(
      "waiting for fetching version",
      getTagsByRepo,
      repo
    );
    if (!tagList) return;
    const tags = tagList.map((item) => item.name);
    // 选取模板信息
    let { tag } = await new inquirer.prompt([
      {
        name: "tag",
        type: "list",
        message: "Please choose a version to create project",
        choices: tags,
      },
    ]);
    return tag;
  }
  // 下载git仓库
  async download(repo, tag) {
    // 模板下载地址
    const templateUrl = `gitee:***gitee的用户名***/${repo}${tag ? "#" + tag : ""}`;
    // 调用 downloadGitRepo 方法将对应模板下载到指定目录
    await loading(
      "downloading template, please wait",
      this.downloadGitRepo,
      templateUrl,
      path.resolve(process.cwd(), this.target) // 项目创建位置
    );
    const packageJsonPath = path.join(
      process.cwd() + `/${this.name}`,
      "package.json"
    );
    const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
    packageJson.name = this.name;
    fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
  }
}

module.exports = Creator;
********************************************************************************

//utils.js
const ora = require("ora");

/**
 * 睡觉函数
 * @param {Number} n 睡眠时间
 */
function sleep(n) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, n);
  });
}

/**
 * loading加载效果
 * @param {String} message 加载信息
 * @param {Function} fn 加载函数
 * @param {List} args fn 函数执行的参数
 * @returns 异步调用返回值
 */
async function loading(message, fn, ...args) {
  const spinner = ora(message);
  spinner.start(); // 开启加载
  try {
    let executeRes = await fn(...args);
    spinner.succeed();
    return executeRes;
  } catch (error) {
    spinner.fail("request fail, reTrying");
    await sleep(1000);
    return loading(message, fn, ...args);
  }
}

module.exports = {
  loading,
};

7、发布脚手架到npm上

1、先 npm login 登陆npm(需要先有npm账号,可以先去npm上创建一个账号)

2、发布npm包 使用 npm push 将当前包传上去(切记npm的源要是国外哪个,不能是国内的)

完成后就可以登陆npm上看见了。

二、发布gitee基础模版标签(否则在创建的时候会失败)

        在我们使用脚手架的时候有一步就是选择要下载基础模版的版本,所以我们需要给gitee上的项目打一个标签推上去

//查看标签

git tag

//创建标签

git tag v1.0.0
//或
git tag v1.0.0 -m "添加新功能"

//查看标签
git tag

//v1.0.0

git show v1.0.0
//将标签推送到远程

三、源码地址

dragom-cli-frontend:dragon-cli-frontend - npm

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值