一个前端cli脚手架工具是如何诞生的

我心飞翔 分类:javascript

前言

笔者最近在倒腾项目上一些工程化的业务,之前基于create-react-app搭好了两个工程架子,一个面向的是pc web,一个是已经做了移动端适配面向mobile h5,所以存在两个项目挂在gitlab上面,如果新来了个业务需求,需要做pc或者mobile的业务,这个时候会打开终端,复制相应的gitlab地址 git clone xxxx一下,一个已经打造好的工程就拉取下来了,可以美滋滋的开发了。

这样的做法还是比较原始,面对一些场景难免clone好了项目还得修修改改,比如拉了个mobile的工程,有的业务需要引入wx sdk.js,有的又不需要,以此类推,如果把这类的引入需求在clone之前就做好了,那么我们才算真正可以美滋滋的开发。

那么如何能在clone之前就做好呢,这便是cli工具诞生的由来。先来看看笔者的cli工具是怎么工作的👇

QQ20210414-225227.gif

实现

看着好像很高大上的样子,其实一个cli无非就是做到以下几件事。

image.png
咱们一步一步来。

获取命令参数+新建

// 根据输入,获取项目名称
let projectName = program.args[0];

// 返回 Node.js 进程的当前工作目录
let rootName = path.basename(process.cwd());

fs.mkdirSync(projectName); // 根据输入创造一个文件夹
 

配置模版

先新建一个文件保存模版的数据

// template.json
{
  "cra": {
    "name": "create react app模版1",
    "value": "tmp1",
    "git": "gitlab:xxxxxxxx",
    "options": []
  },
  "mini": {
    "name": "Taro小程序",
    "value": "mini",
    "git": "gitlab:xxxxxxxx",
    "options": []
  }
}
 

选择模版类型

这里用的是inquirer这个库实现的常见的交互式命令行用户接口

image.png

/**
 * 模板选择
 */
function selectTemplate() {
    return new Promise((resolve, reject) => {
        let choices = Object.values(templateConfig).map(item => {
            return {
                name: item.name,
                value: item.value
            };
        });
        let config = {
            // type: 'checkbox',
            type: "list",
            message: "请选择创建项目类型",
            name: "select",
            choices: [new inquirer.Separator("模板类型"), ...choices]
        };
        inquirer.prompt(config).then(data => {
            let { select } = data;
            let { value, git } = templateConfig[select];
            resolve({
                git,
                // templateValue: value
            });
        });
    });
}

// 选择模板 拿到了git地址
let { git } = await selectTemplate(); 

 

下载模版

这里的核心功能用的是download-git-repo这个库

function download (target, url) {
  // 这里先把模版下载到download-temp
  // 以备后续ejs合成使用
  target = path.join('./download-temp'); 

  return new Promise((resolve,reject) => {
    download(`direct:${url}`,
    target, { clone: true }, (err) => {
    
      if (err) {
        console.log(chalk.red("模板下载失败:("));
        reject(err)
      } else {
        console.log(chalk.green("模板下载完毕:)"));
        resolve(target)
      }
    })
  })
}

templateName = await download(rootName, git);
 

获取本地配置

function getCustomizePrompt(target, fileName) {
    return new Promise((resolve) => {
        const filePath = path.join(process.cwd(), target, fileName)

        if (fs.existsSync(filePath)) {
            console.log('读取模板配置文件')
            let file = require(filePath)
            resolve(file)
        } else {
            console.log('该文件没有配置文件')
            resolve([])
        }
    })
}

// 获取模版中自定义的配置项目
// cli提供基础的配置项目:项目名称/作者/描述
// 而业务自己需要的配置项则保存在模版项目中
let customizePrompt = await getCustomizePrompt(templateName, 'customize_prompt.js')

 

配置项合成

function render(projectRoot, templateName, customizePrompt) {

    return new Promise(async (resolve, reject) => {
        try {
            let context = {
                name: projectRoot, // 项目文件名
                root: projectRoot, // 项目文件路径
                downloadTemp: templateName // 模板位置
            };

            // 获取默认配置
            const promptArr = configDefault.getDefaultPrompt(context);

            // 添加模板自定义配置
            promptArr.push(...customizePrompt);
            let answer = await inquirer.prompt(promptArr); // 获取自定义配置

            let generatorParam = {
                metadata: {
                    ...answer
                },
                src: context.downloadTemp,
                dest: context.root
            };

            // 获取完配置后传入合成方法
            await generator(generatorParam);
            resolve();
        } catch (err) {
            reject(err);
        }
    });
}
 

ejs合成

这里的核心功能使用的metalsmith这里,负责遍历文件模块,并且把文件模块复制到我们的目标目录下,同时通过use我们可以操作转移的文件内容

依赖安装

配置命令

把以上所有的代码都放在index.js文件中,通过

应该是跑的起来的(我们先假设可以跑起来吧^_^。这种做法不够方便 也不好维护。于是我们可以把整个项目当作一个npm包,可以随时随地更新项目发布版本。

package json中配置

这里的核心功能借助commander实现命令,此时我们把以上的index.js内容放到lemon-init中,至于lemon内容则借助commander帮我们触发init命令执行index脚本

此时工程目录如下

等我们需要使用的时候可以直接命令行初始化

总结

cli工具其实做的内容相对还比较简单些,至于后续的工作就是根据不同的配置去编写不同的ejs模版代码。笔者一开始也摸不着头脑,但是翻看了allen-cli的源码,了解了大体的流程,进行了bug修复和功能删减改进。一个适合自己项目的脚手架工具就呼之欲出了。

回复

我来回复
  • 暂无回复内容