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

2024-05-13 1327阅读

这里写自定义目录标题

  • 一、为什么需要脚手架?
  • 二、前置-第三方工具的使用
    • 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
        • 五、参考文章
        • 六、源码仓库地址
        • 七、踩过的坑
        • 其他

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

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

          先看下github的dipper-cli仓库和npm上的成果:

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

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

          欢迎大家到github上点赞,项目已开源,欢迎大家加入。大家可以加我微信哈 zyfts1,一块讨论前端发展。

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

          一、为什么需要脚手架?

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

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

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

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

          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 
          

          注意:版本过高会报错,已踩坑…

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

          配置入口文件

          在根目录下新建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(`[option]`).version(`1.0.0`)
          // 解析用户执行命令传入参数
          program.parse(process.argv);
          

          在命令行输入commander --help,即可看到简单的效果

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

          3. chalk-命令行美化工具

          #! /usr/bin/env node
          const program = require("commander");
          const chalk = require('chalk')
          program.name('zyfcli').usage(`[option]`).version(`1.0.0`)
          // 解析用户执行命令传入参数
          program.parse(process.argv);
          // 演示美化工具
          console.log(`${chalk.green("hello")} zyf`);
          

          输入 zyfcli,看hello显示颜色

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

          其他用法

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

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

          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. 效果 前端从零到一搭建脚手架并发布到npm

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

          效果

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

          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

          还有个实用的命令:

          npm ls -g : 可以查看全局安装的npm包,

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

          例如:如上图所指,即为我们本地包,其他的是npm包,如果要从全局删除需要用npm un -g 指令。

          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(`[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);
          

          三、正式版走起

          先看下目录结构

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

          1. 处理bin的index文件

          #! /usr/bin/env node
          const program = require("commander");
          const chalk = require("chalk");
          const figlet = require("figlet");
          program
            .name("zyfcli")
            .usage(`zyfcli  [option]`)
            .version(`zyfcli ${require("../package.json").version}`);
          program
            .command("create ") // 增加创建指令
            .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 ", "get value by key")
            .option("-s, --set  ", "set option[key] is value")
            .option("-d, --delete ", "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  --help"
              )} for detailed usage of given command.`
            );
            console.log();
          });
          program.parse(process.argv);
          

          create.js先不写东西

          看下效果

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

          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

          四、发布到npm

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

          1. npm login
          2. npm publish

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

          额,名字太简单了改下名字,就叫北斗cli吧 dipper-cli

          修改下package继续发包,注意package的name和bin中的名称。

          小技巧,大家可以先到npm.com中输入名字看下有没有相似的名字。

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

          发布成功

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

          五、参考文章

          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. 未来其他的模版也一并放在这个仓库。

          七、踩过的坑

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

          1.注意npm包的版本。

          万年大坑,好多同学跟着博客一路敲代码,结果一启动就报错,简直怀疑人生。

          注意,这可能就是依赖包版本的问题。比如commander 最新版用的是es模块化方式,而大多数博客上用的的v9.0版本,博主也没注意提醒版本问题,结果就是一启动就报错。

          2.npm包名导致的npm发包失败问题

          如果npm包的名称有重复或者类似,注意名字类似也会提交失败。

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

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

          下班码字不易,如果喜欢请点赞关注,谢谢

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


          其他

          2024.4.16 号,进入热榜21,记录一下😄

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

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

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

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]