cli生成常见项目框架-懒人必备

80 阅读3分钟

在使用插件和vite等框架的时候,我们只要npm i xxx就可以了。在平时的工作中,由于公司业务线的原因,偏向toB系统会比较多。一旦涉及到从零到一的项目,其实每个项目的框架都差异不大,所以能不能先把类似的框架保存起来,要用的时候直接下载就行了,而不是每次都得重新去搭建一个,各种类似的工具,反复npm。

方法一:

将类似的框架架构搭建好,上传到自己的git仓库,要用的时候直接clone就行。

方法二:

我也想模仿一下vite创建对应框架的方法,直接npm i xxx就行了。这样做的好处就是,只要你记得命令,就可以快速下载自己想要的模版。

具体实现步骤:

1.将所有项目模版都放在一个公共库当中,
2.编写cli代码,实现下载对应模版的步骤,并实现跟操作者简单的交互。
3.将cli脚本发布到npm官网上
3.直接本地npm i xxx既可以获取模版

1.准备模版

1.我个人用的github账号,然后自己名下有对应的框架模版,目前自己创建了一个简单的vite+ts+element plus+eslint+prettier+husky的常规框架模版。(可根据自己的需求,使用gitee等库,但请注意:每个git库动态获取模版信息的地址拼接规则不同)

2.编写脚本

npm init -y 初始化

创建bin文件夹

bin/cli.js文件内容

#! /usr/bin/env node
const program = require("commander"); //cli命令工具
const package = require("../package.json");
const inquirer = require("inquirer"); //可以获取用户输入内容的交互工具
// const templates = require("./templates.js");//固定模版数据
const { getGitReposList } = require("./api.js"); // 动态获取git库的模版内容

const downloadGitRepo = require("download-git-repo"); //下载git模版的操作
const path = require("path");

const ora = require("ora"); // 引入ora-实现执行中的loading效果
const loading = ora("正在下载模版...");

const fs = require("fs-extra"); // 引入fs-extra

// 定义当前版本
program.version(`v${package.version}`);
//新建脚本的交互
program
  .command("create [projectName]") // [projectName]是可选 <projectName>是必填
  .option("-t, --template <template>", "模版名称") // 配置项 --template xxx
  .description("创建模版")
  .action(async (projectName, options) => {
    // //创建项目姓名
    // const { name } = await inquirer.prompt({
    //   type: "input",
    //   name: "name",
    //   message: "请输入项目名称:",
    // });
    // console.log("项目名称:", name);
    // // 新增选择模版代码
    // const { template } = await inquirer.prompt({
    //   type: "list",
    //   name: "template",
    //   message: "请选择模版:",
    //   choices: templates, // 模版列表
    // });
    // console.log("模版:", template);
    // 添加获取模版列表接口和loading
    const getRepoLoading = ora("获取模版列表...");
    getRepoLoading.start();
    const templates = await getGitReposList("wanwenxing");
    getRepoLoading.succeed("获取模版列表成功!");
    // 1. 从模版列表中找到对应的模版
    let project = templates.find(
      (template) => template.name === options.template
    );
    // 2. 如果匹配到模版就赋值,没有匹配到就是undefined
    let projectTemplate = project ? project.value : undefined;
    console.log("命令行参数:", projectName, projectTemplate);

    // 3. // 如果用户没有传入名称就交互式输入
    if (!projectName) {
      const { name } = await inquirer.prompt({
        type: "input",
        name: "name",
        message: "请输入项目名称:",
      });
      projectName = name; // 赋值输入的项目名称
    }
    console.log("项目名称:", projectName);

    // 4. 如果用户没有传入模版就交互式输入
    if (!projectTemplate) {
      const { template } = await inquirer.prompt({
        type: "list",
        name: "template",
        message: "请选择模版:",
        choices: templates, // 模版列表
      });
      projectTemplate = template; // 赋值选择的项目名称
    }
    console.log("模版:", projectTemplate);

    // 目标文件夹 = 用户命令行所在目录 + 项目名称
    const dest = path.join(process.cwd(), projectName);
    // 判断文件夹是否存在,存在就交互询问用户是否覆盖
    if (fs.existsSync(dest)) {
      const { force } = await inquirer.prompt({
        type: "confirm",
        name: "force",
        message: "目录已存在,是否覆盖?",
      });
      // 如果覆盖就删除文件夹继续往下执行,否的话就退出进程
      force ? fs.removeSync(dest) : process.exit(1);
    }

    loading.start();
    //下载git模版
    downloadGitRepo(projectTemplate, dest, function (err) {
      if (err) {
        loading.fail("创建模版失败:" + err.message); // 失败loading
      } else {
        loading.succeed("创建模版成功!"); // 成功loadings
        // 添加引导信息(每个模版可能都不一样,要按照模版具体情况来)
        console.log(`\ncd ${projectName}`);
        console.log("npm i");
        console.log("npm start\n");
      }
    });
  });
// 解析用户执行命令传入参数
program.parse(process.argv);

program.on("--help", () => {}); // 添加--help

bin/api.js内容
const https = require("https");
/** 获取用户git仓库列表信息 */
function getGitReposList(username) {
  return new Promise((resolve, reject) => {
    https
      .request(
        `https://api.github.com/users/${username}/repos`, //github官网提供的对应Api:https://api.github.com/
        {
          headers: {
            "User-Agent": username,
          },
        },
        (res) => {
          let data = "";
          res.on("data", (chunk) => {
            data += chunk.toString();
          });
          res.on("end", () => {
            const list = JSON.parse(data);
            // console.log("git返回的用户名下的模版信息》〉》〉",list)
            resolve(
              list.map((item) => ({
                // 组合成模版所需要的name,value结构
                name: item.name,
                value: `https://github.com:${username}/${item.name}`,//注意,每个git仓库的拼接规则可能有差异
              }))
            );
          });
          res.on("error", (err) => {
            reject(err);
          });
        }
      )
      .end();
  });
}

module.exports = {
  getGitReposList,
};

如果不想动态获取远程的框架模版,可以直接写死。

缺点:

1.如果远程仓库中新增或者删除模版,则需要去同步更新cli脚手架代码

bin/template.js内容
/** 暴露模版代码
 * 方法1:存在脚手架目录下
 * 方法2:存储在远程git上,每次都从远程拉取代码
 */
module.exports = [
    {
      name: 'webpack5-react-ts',
      value: 'https://github.com:guojiongwei/webpack5-react-ts'
    },
    {
      name: 'react18-vite2-ts',
      value: 'https://github.com:guojiongwei/react18-vite2-ts'
    },
    {
      name: 'dumi2-demo',
      value: 'https://github.com:guojiongwei/dumi2-demo'
    }
  ]
  

3.发布到npm

npm login指令,获取登录链接,登录后执行npm publish命令(注意:后期若修改cli脚本,重新发布需要去更改对应的version或者是cli的名称)

参考文章:

juejin.cn/post/723602…