前端从零到一搭建脚手架并发布到npm

news2025/1/12 18:04:04

这里写自定义目录标题

  • 一、为什么需要脚手架?
  • 二、前置-第三方工具的使用
    • 1. 创建demo并运行-4步
      • 新建文件夹 zyfcli,并初始化npm init -y
      • 配置入口文件
    • 2.commander-命令行指令
    • 3. chalk-命令行美化工具
    • 4. inquirer-命令行交互工具
    • 5. figlet-艺术字
    • 6. ora-loading工具
    • 7. npm link 本地调试npm包的神器
    • 8. 小demo的完整代码
  • 三、正式版走起
    • 1. 处理bin的index文件
    • 2.处理create.js
    • 3. 添加utils工具函数
    • 4. 查看效果
  • 四、发布到npm
  • 五、参考文章
  • 六、源码仓库地址
  • 七、踩过的坑
  • 其他

在这里插入图片描述

好多前端童鞋工作多年依然不会搭建脚手架,本文就介绍下如何从零开始搭建一个属于你自己的前端脚手架,提高自己的工程化实力,同时也提高团队的开发效率。

先看下github的dipper-cli仓库和npm上的成果:
在这里插入图片描述
在这里插入图片描述
欢迎大家到github上点赞,项目已开源,欢迎大家加入。大家可以加我微信哈 zyfts1,一块讨论前端发展。

好了,下面开始开发您的第一个前端脚手架吧

一、为什么需要脚手架?

  1. 减少重复性的工作,不再从零创建一个项目,或者复制粘贴另一个项目的代码 。
  2. 根据动态交互生成项目结构和配置文件,具备更高的灵活性和人性化定制的能力 。
  3. 有利于多人开发协作,避免了人工传递文件的繁琐。
  4. 可以集成多套开发模板,根据项目需要选择合适的模板。
    在这里插入图片描述

二、前置-第三方工具的使用

实现一个脚手架,通常需要以下工具

  1. commander: 命令行工具
  2. chalk: chalk是一个颜色的插件。可以通过chalk.green(‘success’)来改变颜色。修改控制台输出内容样式
  3. inquirer: 用于命令行交互问询等
  4. download-git-repo: 来通过git下载项目模板的插件
  5. figlet: 生成好看的艺术字,增加终端美观度
  6. ora: 用于实现node命令环境的loading效果,并显示各种状态的图标,显示 loading 动画
  7. npm link: 本地调试npm包的神器。

**注意:**插件的版本。

为了演示先创建一个小项目

1. 创建demo并运行-4步

新建文件夹 zyfcli,并初始化npm init -y

装包-注意版本

pnpm i commander@9.5.0 chalk@4.0.0 inquirer@8.2.1 ora@4.0.0 figlet download-git-repo  ora 

注意:版本过高会报错,已踩坑…
在这里插入图片描述

配置入口文件

在根目录下新建bin/index.js【整个脚手架的入口文件】

#! /usr/bin/env node
console.log('hello world')

验证结果:在命令行中输入node ./bin/index.js,如果能打印出hello即成功

将入口文件配置到package.json 的bin字段

{
  "name": "zyfcli",
  "bin": "bin/index.js",  
}
// 写法2,注意bin里key,需要和nage保持一致
{
  "name": "zyfcli",
  "bin": {
    "zyfcli": "bin/www"
  }
}

npm link将命令挂载到全局
执行 npm link将命令挂载到全局,然后再输入 zyfcli 就可以到达刚才node ./bin/index.js 的效果了

2.commander-命令行指令

引入commander

const program = require("commander");

program.name('zyfcli').usage(`<command>[option]`).version(`1.0.0`)
// 解析用户执行命令传入参数
program.parse(process.argv);

在命令行输入commander --help,即可看到简单的效果
在这里插入图片描述

3. chalk-命令行美化工具

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

program.name('zyfcli').usage(`<command>[option]`).version(`1.0.0`)

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

// 演示美化工具
console.log(`${chalk.green("hello")} zyf`);

输入 zyfcli,看hello显示颜色
在这里插入图片描述
其他用法

console.log(`${chalk
               .green  --颜色
               .bold   --加粗
               .underline  --下划线
               ("hello")} zyf`);

4. inquirer-命令行交互工具

const Inquirer = require('inquirer');

// 命令行交互
new Inquirer.prompt([
  {
    name: 'zyfcli',
    type: "checkbox",
    message: "Check the features needed for your project",
    choices: [
      {
        name: 'Babel',
        checked: 'true',
      },
      {
        name: 'TypeScript',
      }
    ]
  }
]).then((data) => {
  console.log(data);
})

在这里插入图片描述

5. figlet-艺术字

  1. 安装 npm i figlet
  2. 使用
const figlet = require('figlet')

figlet.textSync("dipper-cli", {
      font: "3D-ASCII",
      horizontalLayout: "default",
      verticalLayout: "default",
      whitespaceBreak: true,
    })
  1. 效果 在这里插入图片描述

6. ora-loading工具

注意版本

  1. 使用
const ora = require('ora');
const spinner = ora('Loading unicorns').start();

setTimeout(() => {
  // spinner.color = 'yellow';
  spinner.text = 'Loading rainbows';
  spinner.succeed()
  // spinner.stop()
}, 1000);

效果
在这里插入图片描述

7. npm link 本地调试npm包的神器

npm link:可以在本地调试我们正在开发的脚手架或组件库的神器,不用费事的发布到npm后再调试。

详细的用法教程很多,就不赘述了,没用过的小伙伴可以参考这篇文章,下面介绍一下常用方法 :

  1. 创建: npm link,软后就可在本地调试了
  2. 查看所有的软连接: npm ls -g
  3. 取消软连:npm unlink
  4. 卸载npm包:npm uninstall -g 简写 npm un -g <name>

还有个实用的命令:
npm ls -g : 可以查看全局安装的npm包,

在这里插入图片描述
例如:如上图所指,即为我们本地包,其他的是npm包,如果要从全局删除需要用npm un -g <name>指令。

8. 小demo的完整代码

#! /usr/bin/env node
// 演示工具的使用
const program = require("commander");
const chalk = require('chalk');
const Inquirer = require('inquirer');
const figlet = require('figlet')
const ora = require('ora');


program.name('zyfcli').usage(`<command>[option]`).version(`1.0.0`)

// 演示美化工具
console.log(`${chalk.green.bold.underline("hello")} zyf`);

// 命令行交互
// new Inquirer.prompt([
//   {
//     name: 'zyfcli',
//     type: "checkbox",
//     message: "Check the features needed for your project",
//     choices: [
//       {
//         name: 'Babel',
//         checked: 'true',
//       },
//       {
//         name: 'TypeScript',
//       }
//     ]
//   }
// ]).then((data) => {
//   console.log(data);
// })

// 艺术字
console.log(figlet.textSync('Hello Word'));

// loading
const spinner = ora('Loading unicorns').start();

setTimeout(() => {
  // spinner.color = 'yellow';
  spinner.text = 'Loading rainbows';
  // spinner.succeed()
  // spinner.stop()
}, 1000);

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

三、正式版走起

先看下目录结构
在这里插入图片描述

1. 处理bin的index文件

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

program
  .name("zyfcli")
  .usage(`zyfcli <command> [option]`)
  .version(`zyfcli ${require("../package.json").version}`);


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, --delete <key>", "delete option by key")
  .action((value, keys) => {
    console.log(value, keys);
  });

program.on("--help", function () {
  console.log(
    "\r\n" +
    figlet.textSync("zyf-cli", {
      font: "3D-ASCII",
      horizontalLayout: "default",
      verticalLayout: "default",
      width: 80,
      whitespaceBreak: true,
    })
  );
  // 前后两个空行调整格式,更舒适
  console.log();
  console.log(
    `Run ${chalk.cyan(
      "zyfcli <command> --help"
    )} for detailed usage of given command.`
  );
  console.log();
});


program.parse(process.argv);

create.js先不写东西

看下效果
在这里插入图片描述

2.处理create.js

路径:根目录/lib/create.js

const path = require("path");
const fs = require("fs-extra");
const Inquirer = require("inquirer");
const downloadGitRepo = require("download-git-repo");
const chalk = require("chalk");
const util = require("util");
const { loading } = require("./util");

module.exports = async function (projectName, options) {
  // 获取当前工作目录
  const cwd = process.cwd();
  const targetDirectory = path.join(cwd, projectName);

  // 处理文件夹
  await handleFolder(projectName, options, targetDirectory);

  // 1.选择模版
  const { template } = await new Inquirer.prompt([
    {
      name: "template",
      type: "list",
      message: "Please choose a template to create project",
      choices: [
        { name: 'react', value: 'zyf118725/reactTs' },
        { name: 'vue', value: 'https://vue仓库' }, // 演示
        { name: 'angular', value: 'https://angular仓库' },
      ],
    },
  ]);

  // 2.下载
  await download(template, targetDirectory);

  // 3.模板使用提示
  console.log(`\r\nSuccessfully created project ${chalk.cyan(projectName)}`);
  console.log(`\r\n  cd ${chalk.cyan(projectName)}`);
  console.log("  npm install");
  // console.log("  npm run serve\r\n");
};

// 处理文件夹创建重名问题
async function handleFolder(projectName, options, targetDirectory) {
  if (fs.existsSync(targetDirectory)) {
    if (options.force) {
      // 删除重名目录
      await fs.remove(targetDirectory);
    } else {
      let { isOverwrite } = await new Inquirer.prompt([
        {
          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
        );
      }
    }
  }
}

// 下载git仓库
async function download(templateUrl, targetDirectory) {
  const downloadGitRepoPromise = util.promisify(downloadGitRepo);
  await loading(
    "downloading template, please wait",
    downloadGitRepoPromise,
    templateUrl,
    targetDirectory // 项目创建位置
  );
}

3. 添加utils工具函数

封装axios等函数。

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 };

4. 查看效果

创建一个democli项目

在这里插入图片描述
在这里插入图片描述

四、发布到npm

npm包的发布比较简单,就不在赘述了,没整过的小伙伴可以查下教程

  1. npm login
  2. npm publish

在这里插入图片描述
额,名字太简单了改下名字,就叫北斗cli吧 dipper-cli
修改下package继续发包,注意package的name和bin中的名称。
小技巧,大家可以先到npm.com中输入名字看下有没有相似的名字。

在这里插入图片描述

发布成功
在这里插入图片描述

五、参考文章

  1. https://blog.csdn.net/gao_xu_520/article/details/120505635
  2. 掘金-工具详解:https://juejin.cn/post/7077717940941881358
  3. commander中文文档(github):https://github.com/tj/commander.js/blob/master/Readme_zh-CN.md

六、源码仓库地址

  1. dipper-cli: https://github.com/zyf118725/dipper-cli
  2. react模版-还在丰富中: https://github.com/zyf118725/reactTs
  3. 未来其他的模版也一并放在这个仓库。

七、踩过的坑

在这里插入图片描述

1.注意npm包的版本。
万年大坑,好多同学跟着博客一路敲代码,结果一启动就报错,简直怀疑人生。
注意,这可能就是依赖包版本的问题。比如commander 最新版用的是es模块化方式,而大多数博客上用的的v9.0版本,博主也没注意提醒版本问题,结果就是一启动就报错。

2.npm包名导致的npm发包失败问题
如果npm包的名称有重复或者类似,注意名字类似也会提交失败。

一个小妙招,大家起名字前可以先到 https://www.npmjs.com/ 网站上搜索下有没有类似的名称,省的后续再改名。
在这里插入图片描述

下班码字不易,如果喜欢请点赞关注,谢谢
在这里插入图片描述


其他

2024.4.16 号,进入热榜21,记录一下😄
在这里插入图片描述

感谢CSDN官方的推荐,粉丝量一下涨了好几十 😄,未来将持续产出高质量的文章,将枯燥的知识写的有趣生动。再远一点试试能否在退休之前写本书 🤠。

在这里插入图片描述

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

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

相关文章

QT跨平台读写Excel

QT跨平台读写Excel 背景Excel工具CMakeLists.txt工程目录 背景 开发框架QT&#xff0c;makefile构建工具CMake&#xff0c;编译器MinGW Excel工具 考虑跨平台则不能使用针对微软COM组件的QAxObject来读写Excel&#xff0c;因此使用开源QtXlsx。 这里是将QXlsx当做源码嵌入使…

门禁管理系统服务器如何内网映射让外网访问?

禁管理系统整体解决方案,可实现请假出入联动、门状态监控、电子地图、非法闯入报警、远程开门、红外防夹、智能统计等功能&#xff0c;应用非常广泛。 如果门禁管理系统部署在没有公网IP的本地服务器上&#xff0c;如何设置&#xff0c;能让外网互联网上也能登录访问内部的管理…

亚马逊云科技AWS CloudUp for Her送亚马逊认证考试50%优惠券活动

最近总有小伙伴问小李哥&#xff0c;有没有送AWS考试50%优惠券的活动&#xff1f;这次送云从业者(cloud practitioner)、助理级架构师(SAA)考试50%优惠券活动就来了&#xff01; 本次活动叫AWS CloudUp for Her&#xff0c;完成免费在线培训课程即可获得AWS证书考试50%折扣券&a…

ZooKeeper设置监听器

ZooKeeper设置监听器&#xff0c;通过getData()/getChildern()/xists()方法。 步骤&#xff1a; 1.创建监听器&#xff1a;创建一个实现Watcher接口的类&#xff0c;实现process()方法。这个方法会在ZooKeeper向客户端发送一个Watcher事件通知的时候被调用。 2.注册监听器&…

Servlet第四篇【request对象常用方法、应用】

什么是HttpServletRequest HttpServletRequest对象代表客户端的请求&#xff0c;当客户端通过HTTP协议访问服务器时&#xff0c;HTTP请求头中的所有信息都封装在这个对象中&#xff0c;开发人员通过这个对象的方法&#xff0c;可以获得客户这些信息。 简单来说&#xff0c;要得…

蓝桥杯竞赛类型:Web应用开发 全程详解

既然大家准备报名蓝桥杯&#xff0c;那么对蓝桥杯就应该有一定的了解了。没有了解也没关系&#xff0c;简单来说&#xff0c;蓝桥杯就是一个计算机竞赛&#xff0c;竞赛类型大多是使用各种语言写算法&#xff0c;当然还有本文的主体——Web应用开发。对蓝桥杯有了基本了解之后&…

C#使用PaddleOCR进行图片文字识别✨

PaddlePaddle介绍✨ PaddlePaddle&#xff08;飞桨&#xff09;是百度开发的深度学习平台&#xff0c;旨在为开发者提供全面、灵活的工具集&#xff0c;用于构建、训练和部署各种深度学习模型。它具有开放源代码、高度灵活性、可扩展性和分布式训练等特点。PaddlePaddle支持端…

IntelliJ IDEA2020下使用Maven构建Scala 项目

1.创建maven文件 2.进入pom.xml导入依赖 <!--添加spark的依赖--><dependency><groupId>org.apache.spark</groupId><artifactId>spark-core_2.12</artifactId><version>3.2.1</version></dependency><!--添加scala依…

【ONE·基础算法 || 栈 】

总言 主要内容&#xff1a;编程题举例&#xff0c;熟悉理解以栈此类数据结构为主的题型。       文章目录 总言1、栈2、删除字符中的所有相邻重复项&#xff08;easy&#xff09;2.1、题解 3、比较含退格的字符串&#xff08;easy&#xff09;3.1、题解 4、基本计算器 II&a…

Buildroot系统构建学习笔记(以百问网imx6ullPro开发板为例)

一、Builroot是什么&#xff1f; Buildroot是一组Makefile和补丁&#xff0c;可简化并自动化地为嵌入式系统构建完整的、可启动的Linux环境(包括bootloader、Linux内核、包含各种APP的文件系统)。Buildroot运行于Linux平台&#xff0c;可以使用交叉编译工具为多个目标板构建嵌…

c++|list使用及深度剖析模拟实现

目录 一、list介绍与使用 1.1 list介绍 1.2 list的使用 1.2.1list的构造 1.2.2iterator 1.2.3容量 1.2.4元素访问 1.2.5 元素修改 二、list的深度剖析及模拟实现 三、list与vector的对比 一、list介绍与使用 1.1 list介绍 ①list底层是带头双向循环链表&#xff0c;在…

Redis进阶——BitMap用户签到HyperLogLog实现UV统计

目录 用户签到实现签到功能 签到统计HyperLogLog实现UV统计UV和PV的概述测试百万数据的统计 用户签到 BitMap功能演示 我们针对签到功能完全可以通过MySQL来完成&#xff0c;例如下面这张表 用户签到一次&#xff0c;就是一条记录&#xff0c;假如有1000W用户&#xff0c;平…

RCE漏洞及其绕过——[SWPUCTF 2021 新生赛]easyrce、caidao、babyrce

目录 什么是Shell 1、Shell简介 2、印刷约定 一、什么是RCE 漏洞产生条件&#xff1a; 漏洞检测&#xff1a; 1.远程命令执行 system()函数&#xff1a; passthru()函数&#xff1a; exec()函数&#xff1a; 无回显 shell_exec()函数&#xff1a; 2.远程代码执行 e…

我们一起看看《看漫画学C++》中如何讲解对象的动态创建与销毁

《看漫画学C》这本书中会用图文的方式生动地解释对象的动态创建与销毁。在C中&#xff0c;动态创建对象是通过new运算符来实现的&#xff0c;而销毁对象则是通过delete运算符来完成的。这种方式可以让程序在需要时分配内存给对象&#xff0c;并在对象不再需要时释放内存&#x…

「Word 论文排版」插入分节符导致word转PDF后出现空白页

问题 word转PDF后出现空白页 解决 但是此方法会让页面页脚标记出错 TODO 如下图所示 在论文目录后有一个分节符&#xff0c;转成PDF之后就多了一个空白页 文件-打印-页面设置-选中封面那一页-版式-从偶数页开始 再导出空白页就没了

Nginx莫名奇妙返回了404

描述 nginx作为反向代理&#xff0c;代理python的服务&#xff0c;但是通过代理访问服务的时候&#xff0c;报了404的错误。 难受的是客户现场没有查看日志的权限&#xff0c;只有查看配置文件的权限&#xff0c;我们检测了几遍配置文件也没有找到问题&#xff0c;哎~ 问题引…

34. 【Android教程】菜单:Menu

作为 Android 用户&#xff0c;你一定见过类似这样的页面&#xff1a; 它就是我们今天的主角——菜单&#xff0c;它的使用场景和作用不用多说&#xff0c;几乎每个 App 都会用到它&#xff0c;今天我们就一起来看看 Android 提供的几种菜单类型及用法。 1. 菜单的几种类型 根…

[Algorithm][滑动窗口][无重复字符的最长字串][最大连续的一个数 Ⅲ][将x减到0的最小操作数]详细讲解

目录 1.无重复字符的最长字串1.题目链接2.算法原理详解3.代码实现 2.最大连续的一个数 Ⅲ1.题目链接2.算法原理详解3.代码实现 3.将x减到0的最小操作数1.题目链接2.算法原理详解3.代码实现 1.无重复字符的最长字串 1.题目链接 无重复字符的最长字串 2.算法原理详解 研究的对…

美化博客文章(持续更新)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;游戏实现&#xff1a;贪吃蛇​​​​​​ &#x1f337;追光的人&#xff0c;终会万丈光芒 前言&#xff1a; 该文提供我的一些文章设计的一些方法 目录 1.应用超链接 1.应用超链接

URL GET +号后台接收成空格

问题&#xff1a;参数spdmwhbs001 其中包含URL特殊符号 如果用GET请求方式不做任何不处理那么浏览器自动将转为%20 请求链接为 details?spdmwhbs%20001&limitKcysType1 后台接收到的参数为 whbs 001 &#xff0c;自动将号转成空格了。 尝试解决&#xff08;失败&#…