手把手教你搭建自己的脚手架并上传npm

2,229 阅读5分钟

前段时间因为公司业务比较忙,前端人手也不是很多,每次新项目来了之后都需要从0搭建项目,也没有现成的模板,之前有的都是用的若一,但是也是需要改,也很浪费时间,然后老板就说我们要形成一套自己的开发体系,说让我们搞一个脚手架,然后开发一套通用的模板,我想这不雪上加霜吗😭,这日常的工作都做不完那,还要弄个什么脚手架😞,眼看这也快年底了,为了弄个优秀员工当,真不容易,那就拼一下吧。就根据公司的需求搭建了公司第一套脚手架,上传到了我们公司的npm私库上,虽然比较简单,但是够用了,后期公司不忙了在优化吧。下面将我搭建脚手架的基本过程记录了下来,拿出来分享。

首先我们在开始之前我们需要先了解几个问题?

为什么需要脚手架?

随着业务的繁多,每次搭建项目都比较麻烦,如果我们形成一套自动搭建的方案就会节省很多时间,从而使我们不会吧时间全部浪费到搭建项目上,并且我们可以省出大量的时间去开发项目,这就是脚手架存在的意义

为什么要自己搭建脚手架?

目前我们前端存在很多的脚手架,比如:Vue的Vue-cli,create-vue,react的create-react-app等,都是比较优秀的脚手架,但是这几种脚手架选择性比较固定,虽然有的脚手架已经实现了插拔式的构建,但是生成的模板可能还是不会满足我们的项目需求,通过脚手架生成后又加入我们自己的需求,这样很浪费时间,所以根据自己需求去定制一套自己脚手架,做到随时下载,开箱即用的效果,既节省时间,效率也会提升的。

开发脚手架需要用到的插件

  • commander 解析命令行
  • chalk 可以给终端字体样式
  • ora 显示loading动画效果
  • inquirer 通用的命令行用户界面集合,用于和用户进行交互
  • handlebars 模版引擎,将用户提交的动态信息填充到文件中
  • download-git-repo 下载并提取git仓库,用于下载模版
  • fs-extra 文件处理

需求

  1. 用户可以通过不同版本去搭建教授架
  2. 通过git拉取代码

开始搭建

  1. 创建目录mycli
  2. 初始化package.json文件
  • 创建package.json文件并配置bin
  • 首先说一下bin配置主要作用就是我们可以以名称去执行当前文件脚本,如果你本地已经安装了其他vue的脚手架,那你可以打开它的package.json文件,就会发现它也配置了bin,
  • 下面解释一下bin执行的原理,我们就那vue-cli举例子,首先打开我们全局node_modules\bin 生成一个以bin下面定义的相同名称的可执行文件,当我们执行vue create xxx的时候首先它会找到这个全局的bin目录然后找到里面的vue可执行文件,这样脚手架就可以创建我们的项目了。这就是我们为什么在使用vue-cli 创建项目的时候可以直接使用vue命令的原理。
  • package.json文件
{
  "name": "cvue",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "bin": {
    "mycli": "./bin/mycli.cjs", 
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": { 
    "axios": "^1.1.3",
    "chalk": "^4.0.0",
    "commander": "^9.4.1",
    "download-git-repo": "^3.0.2",
    "fs-extra": "^10.1.0",
    "inquirer": "^8.0.0",
    "ora": "^4.0.0"
  }
}
  1. 创建文件
  • bin/mycli.cjs因为部分插件的低版本不支持esm所以使用cjs去开发教授架
  • lib/create.js

这里根据自己的需求去配置

#! /usr/bin/env node  
console.log('hello mycli');
  1. 打开终端执行npm link . 将当前的文件设置为软连接,并安装到全局npm下。
  2. 运行mycli,在下面如果打印出hello mycli,说明软连接成功了。

创建文件

首先我们观察vue脚手架创建命令都是vue create xxx,那我们如何去做呢?,下面就会用到一个我们上面已经提过的工具commander,通过commander去解析我们在命令行中执行的命令,并且我们可以通过commander预设一些命令,具体看下面的代码

// bin/mycli.cjs  
const create = require('../lib/create.js');
program
.command(`create <app-name>`)
.description(`create a new project`)
.option(`-f,--force`,`overwrite target directory if it exists`)
// 动作
.action((name,cmd)=>{
    // create 方法看下面的内容
    create(name,cmd) 
})
// --help
program
.on('--help',()=>{
      console.log('');
      console.log(`Run ${chalk.cyan(' mycli <command> --help')} show details `);
      console.log('');
}) 
program.parse(process.argv);
// 后面根据自己的需求去自定义就可以了

我们在命令行上执行mycli create xxx 的时候就会触发action,将参数传进去,name就是xxx(也就是说项目名称),cmd 就是options中配置的命令。

项目文件创建

创建项目需要考虑两件事情,第一就是当我们遇到重名的文件,是选择覆盖还是不覆盖,第二就是如果没有重名的文件就直接创建,下面实现一下这个方法

  • 安装依赖npm install fs-extra inquirer download-git-repo
const chalk = require('chalk');
const path = require('path');
const util = require('util');
const downloadgitrepo = require('download-git-repo'); 
// 文件处理
const fs = require('fs-extra');
module.exports = async function (projectName, option) { 
    //  创建项目
    //  命名重复
    // 1. 获取当前执行目录
    const cwd = process.cwd(); 
    const targetDir = path.join(cwd, projectName)
    if (fs.existsSync(targetDir)) {
        if (option.force) {
            // 强制创建,加入 -f 命令后如果文件存在重名会强制移除上一个文件,新建一个文件
            await fs.remove(targetDir);
        } else {
            // 这个是用户没有加入 -f强制命令进行是否覆盖处理 提示用户是否确定要覆盖,用户选项提示
            let { action } = await inquirer.prompt([
                {
                    name: 'action',
                    type: 'list',
                    massage: '当前文件名重复是否覆盖?',
                    choices: [
                        { name: '覆盖', value: 'overwrite' },
                        { name: '取消', value: false }
                    ]
                }
            ])
            // 如果用户不覆盖,直接取消
            if (!action) {
                console.log(`\r\n 取消构建`);
                return 
            } else if (action == 'overwrite') {
                // 如果要覆盖直接将之前文件进行移除 然后覆盖
                console.log(`\r\n 正在移除...`);
                await fs.remove(targetDir);
            } 
        }
    }
       // fetchRepo,fetchTag俩个方法写在下面
      let Temp = await fetchRepo();
      let tag = await fetchTag();
      downloadtemp(Temp,tag); 
      console.log(`\r\n ${chalk.blue('创建成功!')}`);
}

选择版本号

function fetchVersion(){ 
        
        let {tag} = await inquirer.prompt({
             name:'tag',
             type:'list',
             choices:['v1.0','v2.0'],
             message:'请选择版本号!'
         })
         return tag;
     }

选择模板

function fetchTag(){  
        let {tag} = await inquirer.prompt({
             name:'tag',
             type:'list',
             choices:['temp1','temp2'],
             message:'请选择模板!'
         })
         return tag;
     }

拉取远程仓库源码

const downloadGitRepofn = util.promisify(downloadgitrepo);
// 在这里写入你的git仓库地址
// 注意:需要使用github,或者是gitlab的地址
const gitbseurl = 'https://github.com/xxx';
function downloadtemp(temp,tag){
        //  1. 拼接下载路径
        let requestUrl = `${gitbseurl}/${temp}/${tag?'#'+tag:''}`; 
        //  2. 把资源下载到某个路径上,(后续增加缓存功能)
        await downloadGitRepofn(requestUrl,path.resolve(process.cwd(),`${temp}@${tag}`));
        return target;
     }

还需要自己本地测试一下之前我们已经发布了软连接所以直接运行是没有问题的

直接在控制台执行mycli create myproject,如果本地已经把你选择的模板克隆下来了 说明没有问题。

如何上传到npm

注册npm账户

image.png

这里就根据npm官方提示走就可以

版本号与包名称

  "name": "mycli",
  "version": "1.0.0", 

如果你的npm 包更新了

  • 注意:登录之前需要更换为npm源,不然会出错
  • 本地登录npm

image.png

输入你注册的账户,后面npm还会给你发送验证码需要验证邮箱,输入你绑定邮箱即可

  • 上传npm

检查你package.json文件信息如果没有问题执行npm publish 进行上传

最后

因为脚手架的实现一个公司一套体系,一个人一个方案等等,以上流程只是最基本的搭建写法,不是很全面,整体还需要自己根据公司的需求去实现,好了,本篇文章就到这了,如果文章中有什么问题,请在评论区留言,感谢!