从0-1实现一个前端脚手架

news2024/11/26 16:44:43

https://gitee.com/childe-jia/kfc-cli.git
gitee完整地址

介绍

为什么需要脚手架?

脚手架本质就是一个工具,作用是能够让使用者专注于写代码,它可以让我们只用一个命令就生成一个已经配置好的项目,而不用我们再花时间去配置和安装相关依赖,可以在很大程度上提升我们的开发效率。比如我们常用的create-vuecreate-react-app就是脚手架,很多大厂也都有自己的脚手架。

一个脚手架应该具备哪些功能?

我们以vue官方的脚手架create-vue为例来分析下一个脚手架应该具备哪些功能?

  1. 运行命令创建项目

    npm create vue@latest
    
  2. 用户根据自己需要选择一些配置项

在这里插入图片描述

  1. 根据选择的配置项会生成一个模版项目

在这里插入图片描述

通过分析create-vue,我们可以知道,一个脚手架如果想要创建一个项目,最少要有以下两点功能:

  1. 可以通过命令行和用户交互
  2. 根据交互的结果去生成对应的模版项目

脚手架实现

初始化项目
  1. 执行如下初始化命令
mkdir kfc-vme50
➜ cd kfc-vme50
➜ npm init -y
  1. 在根目录下创建bin/index.js文件作为入口文件,并添加如下代码
#!/usr/bin/env node
console.log('肯德基疯狂星期四v我50')
  1. 在package.json中添加bin字段
"bin": {
  "kfc-vme50": "/bin/index.js"
}
  1. 在根目录下执行npm link将项目链接到本地环境,就可以实现kfc-vme50命令全局调用
  2. 运行kfc-vme50并查看控制台输出

在这里插入图片描述

相关依赖

实现一个脚手架,通常会用到以下依赖包

  • commander:命令行处理工具
#!/usr/bin/env node

// #! 是shebang的标识,告诉操作系统这是一个脚本文件。
// /usr/bin/env 是一个程序,用来查找环境变量中定义的程序路径。在这个例子中,它用来查找node的路径。
// node 是Node.js的可执行文件名,它是运行JavaScript代码的运行时环境。

// 它用于处理命令行参数;
const { program } = require("commander");

/**
 *  .name 命令名称出现在帮助中,也用于定位独立的可执行子命令。
 *  .usage 通过这个选项可以修改帮助信息的首行提示
 */

program.name("kfc-creat").usage("<command> [option]");

/**
 * 「选项」 定义选项
 * 使用.option()方法来定义选项,同时可以附加选项的简介。每个选项可以定义一个短选项名称(-后面接单个字符)
 *    一个长选项名称(--后面接一个或多个单词),使用逗号、空格或|分隔。
 *    有两种最常用的选项,一类是 boolean 型选项,选项无需配置参数,
 *    另一类选项则可以设置参数(使用尖括号声明在该选项后,如--expect <value>)。
 *    如果在命令行中不指定具体的选项及参数,则会被定义为undefined
 */

program
  .option("-d, --debug", "output extra debugging")
  .option("-s, --small", "small pizza size")
  .option("-p, --pizza-type <type>", "flavour of pizza");

/**
 * 「命令」 通过.command()或.addCommand()可以配置命令,
 * .command()的第一个参数为命令名称。命令参数可以跟在名称后面,也可以用.argument()单独指定。
 * 参数可为必选的(尖括号表示)、可选的(方括号表示)或变长参数(点号表示,如果使用,只能是最后一个参数)。
 * ----------------------------------------------------------------
 * description 出现在命令的帮助中。
 * action 命令触发后的回调函数 [命令行的参数]
 */

program
  .command("clone <source> [destination]")
  .description("clone a repository into a newly created directory")
  .action((source, destination) => {
    console.log("clone command called");
    console.log(source, destination);
  });

/** 
 * 「parse」解析摩命令行参数
 * program.parse 它的作用是解析 process.argv 数组,将命令行参数转换为可操作的对象。
 * process.argv 是 Node.js 中的一个全局变量,它是一个数组,包含了命令行启动脚本时传递给 Node.js 进程的参数。
    数组的第一个元素 process.argv[0] 总是 node, 表示 Node.js 可执行文件的路径 (安装路径)
    接下来的元素是脚本文件的路径,即你正在运行的 JavaScript 文件的路径。
    之后的元素是传递给脚本的命令行参数
*/
program.parse(process.argv);

/**
 * 「opts」获取命令行参数
 * 解析后的选项可以通过Command对象上的.opts()方法获取,同时会被传递给命令处理函数。
 */
const options = program.opts();
console.log(options);

console.log("肯德基疯狂星期四v我500");

  • chalk:命令行输出美化工具
#!/usr/bin/env node

// 重要提示:Chalk 5 部分是 ESM。如果您想将 Chalk 与 TypeScript 或构建工具一起使用,您现在可能需要使用 Chalk 4

/**
ESM 是 "ECMAScript Module" 的缩写,它指的是一种 JavaScript 模块的规范,允许开发者将代码分割成可重用的模块。
ESM 是现代 JavaScript 的一个特性,它支持静态模块的导入和导出,这意味着模块的依赖关系可以在编译时就确定下来,从而提高代码的加载和执行效率。
然而,ESM 也有它的限制,比如它不支持 CommonJS 模块中的 require() 函数,而是使用 import() 来动态加载模块。
此外,ESM 需要在支持 ESM 的环境中使用,比如现代浏览器或者使用 Babel 等工具转换的 Node.js 环境。
 */

const chalk = require("chalk");

// 颜色
console.log(chalk.yellow("Welcome"));
// 加粗
console.log(chalk.red.bold("Welcome"));
// 背景色
console.log(chalk.yellow.bold.bgBlue("Welcome"));

  • inquirer:命令行交互工具
#!/usr/bin/env node
/**
 * !警告] Inquirer v9 及更高版本是本机 esm 模块,
 * 这意味着您不能再使用 commonjs 语法 require('inquirer') 
 * 或者,如果您需要 commonjs 模块,则应该依赖旧版本,直到准备好升级环境

 */
const inquirer = require("inquirer");

/**
 * inquirer.prompt(questions, answers) -> promise
 * 启动提示界面(查询会话)
 *    questions (Array) 包含 Question 对象(使用反应式接口,还可以传递 Rx.Observable 实例)
 *    答案(对象)包含已回答问题的值。询问者将避免询问此处已提供的答案。默认值 {}。
 *
 *「Question对象」 问题对象是包含问题相关值的哈希
      type  (字符串)提示的类型。【input, number, confirm, list, rawlist, expand, checkbox, password, editor】
      name:(字符串)将答案存储在答案哈希中时使用的名称。如果名称包含句点,它将在答案哈希中定义路径。
      message:(字符串|函数)要打印的问题。如果定义为函数,第一个参数将是当前询问者会话的答案。默认为 name 的值(后跟冒号)
      default:(字符串|数字|布尔值|数组|函数)未输入任何内容时使用的默认值,或返回默认值的函数。如果定义为函数,第一个参数将是当前询问者会话的答案
 *
 */

inquirer
  .prompt([
    // 将你的问题放在这
    {
      type: "input",
      name: "food",
      message: "你吃啥",
      default: "披萨",
    },
    {
      type: "confirm",
      name: "hot",
      message: "吃不吃辣",
      default: false,
    },
  ])
  .then((answers) => {
    //使用用户反馈。。。无论什么结果
    console.log(answers);
  })
  .catch((error) => {
    if (error.isTtyError) {
      // 无法在当前环境中呈现提示
    } else {
      //其他问题
    }
  });

  • ora:终端loading美化工具
#!/usr/bin/env node
const ora = require("ora");

// 启动旋转器。返回实例。如果提供了文本,则设置当前文本。
const spinner = ora("Loading unicorns").start();

setTimeout(() => {
  spinner.color = "yellow";
  spinner.text = "Loading rainbows";
}, 1000);
// 停止旋转器,将其更改为绿色 ✔ 并保留当前文本或文本(如果提供)。返回实例。请参阅下面的 GIF。

// setTimeout(() => {
//   spinner.succeed("succeed");
// }, 2000);

// 停止旋转器,将其更改为红色 ✖ 并保留当前文本或文本(如果提供)。返回实例。请参阅下面的 GIF。

setTimeout(() => {
  spinner.fail("fail");
}, 2000);

  • git-clone:下·载项目模版工具
  • figlet:终端生成艺术字
#!/usr/bin/env node
var figlet = require("figlet");

// 将 Figlet 对象作为函数调用是调用文本函数的简写。此方法允许您从文本创建 ASCII 艺术。
// 输入文本 - 要转换为 ASCII 艺术的文本字符串。
// 选项 - 指示字体名称的字符串或选项对象(如下所述)
// 回调 - 使用生成的 ASCII Art 执行的函数。

figlet("Hello World!!", function (err, data) {
  if (err) {
    console.log("Something went wrong...");
    console.dir(err);
    return;
  }
  console.log(data);
});

// 该方法是上述方法的同步版本
// 输入文本 - 要转换为 ASCII 艺术的文本字符串。
// 字体选项 - 指示字体名称的字符串或选项对象(如下所述)。
console.log(
  figlet.textSync("Boo!", {
    font: "Ghost", //类型:字符串 默认值:'标准' 指示要使用的 Figlet 字体的字符串值。
    horizontalLayout: "default", //指示要使用的水平布局的字符串值
    verticalLayout: "default", //指示要使用的垂直布局的字符串值
    width: 80, //宽度
    whitespaceBreak: true, //此选项与“宽度”结合使用。如果此选项设置为 true,则库在限制宽度时将尝试在空白处分解文本。
  })
);

  • fs-extra:用来操作本地目录
talk is cheap, show me the code
#!/usr/bin/env node

// 操作终端命令行
const { program } = require("commander");
// 艺术字
const figlet = require("figlet");
// 操作文件
const fs = require("fs-extra");
// 获取路径
const path = require("path");
// 命令行交互
const inquirer = require("inquirer");
// 彩色输出
const chalk = require("chalk");
//控制台loadding
const ora = require("ora");

// clone 项目
const gitClone = require("git-clone");

// 项目仓库
const projectList = {
  vue: "https://gitee.com/y_project/RuoYi-Vue.git",
  react: "https://gitee.com/whiteshader/ruoyi-react.git",
  "react&ts": "https://gitee.com/whiteshader/ruoyi-react.git",
  "vue&ts": "https://gitee.com/lyforvue/ruoyi_vue3_ts.git",
};

// 修改帮助信息的首行展示
program.usage("<command> [options]");

// 版本号
program.version(`v${require("../package.json").version}`);

// 艺术字展示 监听 help添加提示信息
program.on("--help", function () {
  console.log(
    figlet.textSync("kfc vme50", {
      font: "Ghost",
      horizontalLayout: "default",
      verticalLayout: "default",
      width: 100,
      whitespaceBreak: true,
    })
  );
});

// 创建项目的命令
program
  .command("create <app-name>") // 创建项目的命令 name必填
  .description("创建新项目") //描述
  //执行命令后的回调【命令后的值,】
  .action(async function (name, option) {
    //创建以一个名为name的文件夹,把代码放到文件夹下
    const cwd = process.cwd(); //获取命令执行的文件目录

    // 创建项目的位置
    const targetPath = path.join(cwd, name);
    // 如果文件夹存在
    if (fs.existsSync(targetPath)) {
      const res = await inquirer.prompt([
        {
          name: "action",
          type: "list",
          message: "是否覆盖已有文件夹?",
          choices: [
            {
              name: "YES",
              value: true,
            },
            {
              name: "NO",
              value: false,
            },
          ],
        },
      ]);
      //不覆盖 取一个新的名字
      if (!res.action) return;
      fs.remove(targetPath);
      console.log(chalk.red("已删除之前的文件夹"));
    }

    //新建项目
    const res = await inquirer.prompt([
      {
        name: "type",
        type: "list",
        message: "请选择使用的框架",
        choices: [
          {
            name: "Vue",
            value: "vue",
          },
          {
            name: "React",
            value: "react",
          },
        ],
      },
      {
        name: "ts",
        type: "list",
        message: "是否使用ts项目",
        choices: [
          {
            name: "YES",
            value: true,
          },
          {
            name: "NO",
            value: false,
          },
        ],
      },
    ]);
    // 是否为ts
    const rep = res.type + (res.ts ? "&ts" : "");

    // 拉取项目模板
    const spinner = ora("正在加载项目模板...").start();
    gitClone(
      projectList[rep], //拉去路径
      targetPath, //保存路径
      //分支
      {
        checkout: "master",
      },
      //回调函数
      (err) => {
        if (!err) {
          fs.remove(path.resolve(targetPath, ".git"));
          spinner.succeed("项目模板加载完成!");
          console.log("now run:");
          console.log(chalk.green(`\n  cd ${name}`));
          console.log(chalk.green("  npm install"));
          console.log(
            chalk.green(`  npm run ${res.type === "react" ? "start" : "dev"}\n`)
          );
        } else {
          spinner.fail(chalk.red("项目模板加载失败,请重新获取!", err));
        }
      }
    );
  });

//解析控制台参数
program.parse(process.argv);

发布
  1. 注册npm账号
  2. 在本地登录并发布
# 登录刚注册的账号npm login
Username: 用户名
Password: 密码
Email: 注册邮箱
Enter one-time password: 一次性密码  邮箱会收到邮件

# 在我们脚手架的根目录下执行发布命令npm publish

注意:

  1. 登录和发包前一定要先查看npm的源,需要修改为https://registry.npmjs.org/

  2. 在发布时包名不能重复,所以可以先在线上搜索下看看有没有存在的包,如果出现403错误可能是包名和线上的包重复了,修改package.json中的name即可

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1903665.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【python教程】数据分析——numpy、pandas、matplotlib

【python教程】数据分析——numpy、pandas、matplotlib 文章目录 什么是matplotlib安装matplotlib&#xff0c;画个折线 什么是matplotlib matplotlib:最流行的Python底层绘图库&#xff0c;主要做数据可视化图表,名字取材于MATLAB&#xff0c;模仿MATLAB构建 安装matplotlib&…

Idea-Idea配置gitIgnore忽略文件

背景 在项目提交到Git过程中&#xff0c;总有一些文件&#xff0c;例如.idea和.iml等这些我们不想提交的&#xff0c;直接添加进入gitIgnore文件中自动忽略掉。 Idea安装插件 1、在File->Setting->Plugins中搜索gitIgnore并安装插件 2、项目右键new->.ignore File-…

树状数组求三元上升子序列

分析一下&#xff0c;感觉没什么思路&#xff0c;再想一下&#xff0c;结果不就是每一位的数小于它的数乘以大于大于这位数的相乘之和吗&#xff0c;我们可以利用逆序对的思维求得 关键点在于求解逆序对的时候值相同的时候&#xff0c;位置大的优先级更高处理 #define _CRT_SEC…

Android OpenGL ES 离屏幕渲染1——EGL环境的创建,以及基础概念的理解

创建EGL上下文、配置EGL环境、创建EGL DISPLAY 什么是EGL&#xff1a; 由于OpenGL ES并不负责窗口管理以及上下文管理&#xff0c;该职责由各个平台自行完成&#xff1b;在Android平台下OpenGL ES的上下文环境是依赖EGL的API进行搭建的。 对于EGL这个框架&#xff0c;谷歌已经提…

抽象类和接口及内部类

1.抽象类 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c; 如果 一个类中没有包含足够的信息来描绘一个具体的对象&#xff0c;这样的类就是抽象类 2.接口 1.概念 接口就是公共…

树莓派5安装冬瓜HAOS教程

原文来自瀚思彼岸和hasshome 一、安装前准备 &#xff08;1&#xff09;软件 1、树莓派烧录软件Imager 2、冬瓜HAOS镜像 &#xff08;2&#xff09;硬件 1、树莓派5 2、TF卡&#xff08;SanDisk Extreme PRO 64GB U3 A2 V30 4k&#xff09; 3、读卡器 4、键盘和鼠标 5、显…

第一节 网络安全概述

一.网络空间安全 网络空间&#xff1a;一个由信息基础设施组成相互依赖的网络。 ---- 海陆空天&#xff08;大海、陆 地、天空、航天&#xff09; 通信保密阶段 ---- 计算机安全 ----- 信息系统安全 ----- 网络空间安全 计算机安全&#xff1a;开始秉持着“严于律己&#x…

网络防御保护——网络安全概述

一.网络安全概念 1.网络空间---一个由信息基础设施组成相互依赖的网络 。 网络空间&#xff0c;它跟以前我们所理解的网络不一样了&#xff0c;它不光是一个虚无缥缈的&#xff0c;虚拟的东西&#xff0c;它更多的是融入了我们这些真实的物理设备&#xff0c;也就意味着这个网…

synchronized和ReentrantLock

ReentrantLock ReentrantLock fairLock new ReentrantLock(true);// 这里是演示创建公平锁&#xff0c;一般情况不需要。 fairLock.lock(); try {// do something } finally {fairLock.unlock(); }

Google Earth Engine(GEE)——ui.Panel添加到地图上

结果 函数 ui.root.add(widget) 将一个widget添加到根面板上。 返回根面板。 参数。 widget&#xff08;ui.Widget&#xff09;。 要添加的widget。 返回&#xff1a; ui.Panel 代码 //label var label ui.Label({ value: "text label", style: {fontSi…

最近看English the American way一点小结

这个书还行吧&#xff0c;就是没很多时间去学。最后就是总结一些觉得还有用的短语和单词。 hang out drop by/in 来访 what are you up to? Thanks a bunch. tied up Stay tuned 敬请期待 hop on/into the bus/car. hail a cab off track 偏题了 in the same boat f…

代码随想录算法训练Day58|LeetCode417-太平洋大西洋水流问题、LeetCode827-最大人工岛

太平洋大西洋水流问题 力扣417-太平洋大西洋水流问题 有一个 m n 的矩形岛屿&#xff0c;与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界&#xff0c;而 “大西洋” 处于大陆的右边界和下边界。 这个岛被分割成一个由若干方形单元格组成的网格。给定一个…

SQL 对一个经常有数据更新和删除操作的表,怎样优化以减少磁盘空间的占用?

文章目录 一、定期清理不再需要的数据二、使用合适的数据类型三、压缩数据四、删除重复数据五、分区表六、索引优化七、碎片整理八、归档历史数据九、监控和评估 在数据库管理中&#xff0c;当面对一个经常进行数据更新和删除操作的表时&#xff0c;磁盘空间的有效利用是一个重…

【国产开源可视化引擎Meta2d.js】钢笔

钢笔 钢笔是和其他众多绘图工具&#xff08;Photoshop、Sketch、Illustrator&#xff09;中一致的钢笔工具&#xff0c;能够很方便的在线绘制各种小图标 在线体验&#xff1a; 乐吾乐2D可视化 示例&#xff1a; // 开始绘画&#xff1a;curve。除了curve&#xff0c;还有poly…

【HTML入门】第三课 - 标题、段落、空格

这一小节&#xff0c;我们说一些比较零散的知识&#xff0c;HTML课程中呢&#xff0c;其实就是一些标签&#xff0c;正是这些标签组成了前端网页的各种元素&#xff0c;所以你也可以叫他们标签元素。 像前两节我们说的&#xff0c;html head body title meta style 。这些都是…

【Unity性能消耗】ScriptableObject复用数据节省内存占用

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 专栏交流&#x1f9e7;&…

【电商系统开发实用接口指南】包含国内国外多电商平台商品数据对接(附文档)

关于电商数据接口 开发电商系统的朋友对于电商平台API肯定不陌生&#xff0c;API接口即应用程序编程接口&#xff0c;电商平台开放部分API接口&#xff0c;供商家和服务商调用&#xff0c;以满足电商业务管理需求。随着电商市场需求的日益增长以及技术手段的不断成熟&#xf…

深度学习与CV入门

文章目录 前言历史 前言 历史 tensorflow可以安装Tensorboard第三方库用于展示效果 TensorFlow工作流程&#xff1a;p6-4:20 使用tf.data加载数据。使用tf.data实例化读取训练数据和测试数据模型的建立与调试:使用动态图模式Eager Execution和著名的神经网络高层API框架Ker…

【Python】已解决:ModuleNotFoundError: No module named ‘nltk’

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;ModuleNotFoundError: No module named ‘nltk’ 一、分析问题背景 在使用Python进行自然语言处理或文本分析时&#xff0c;我们经常会用到各种库来辅助我们的工…

旅游计划定制小程序网页模板源码

手机在线旅游定制服务&#xff0c;定制旅游出行app小程序模板。包含&#xff1a;定制介绍、定制表单填写、我的订单等。 旅游计划定制小程序网页模板源码