zoukankan      html  css  js  c++  java
  • 从零开始搭建前端脚手架

    一、功能设计

    每个前端小组都会有自己的独特的业务场景,从这些业务场景从提取公共部分,并打造一个前端项目模版,是非常有必要的

    为了能够基于这个项目模版快速创建一个新项目,就需要脚手架工具登场

    所以这里至少有两个项目仓库:前端模版项目、脚手架工具

    而对于脚手架工具,它应当具备这样的功能:输入一个命令和项目名称,创建对应的项目目录,其内容就是模版项目

    my-cli create my-project

    基于此,脚手架工具的内部逻辑也就很清晰了:

    创建一个 create 命令,其行为是拉取模版项目的代码(用 git clone 即可实现)

    为了实现这个命令,我们需要借助一个神奇的工具:commander

     

     

    二、Commander

    Commander.js 是完整的 node.js 命令行解决方案

    可以通过它创建一个 program 对象,作为程序的主体

    const { Command } = require('commander');
    const program = new Command();
    program.version('0.0.1');

    commander 常用的功能有创建选项和命令


    选项 option 需要基于短线 " - " 声明,常见的  -h 、 -V  都是选项

    每个选项可以定义一个短选项名称(" - " 后面接单个字符)和一个长选项名称(" -- "后面接一个或多个单词)

    解析后的选项可以通过 .opts() 方法获取,从而执行对应的操作

    比如创建一个 -d 选项:

    program
      .option('-d, --debug', 'output extra debugging')
    
    program.parse(process.argv);
    
    const options = program.opts();
    if (options.debug) console.log(options);

    还可以对选项定义参数及默认值:

    program
      .option('-d, --debug <type>', 'output extra debugging', 'hard')

    这里就对 debug 选项定义了 type 参数,并设置其默认值为 hard

    用尖括号   <>  定义的参数为必填,用中括号   []  创建的参数为选填


    命令 command 可以通过  .command()  创建,并通过链式调用 .description() 和 .action() 定义该命令的描述和具体行为

    program
      .command('create <name> [type]')
      .description('create a new project')
      .action((name, type) => {
        console.info('project', name);
      });

    和选项类似,命令也可以通过  <>  和  []  定义参数

    除了像上面那样通过链式调用创建命令行为(description、action)之外,还可以用单文件的形式描述命令,不过我没有跑通


    commander 还有更多更强大的功能,这里就不逐一介绍了,详情可以参考中文文档

     

     

    三、正式开始

    学习了 commander 之后,就可以着手脚手架的开发了 

    首先创建脚手架工具的目录,如 my-cli,然后在目录下创建一个 .gitignore 文件

    # Dependency directories
    node_modules/

    接着通过  npm init 创建 package.json

    创建之后可以删除其中的 main 和 scripts,然后安装 commander

    npm install commander --save

    然后创建 bin 目录及 bin/cli.js 文件:

    #!/usr/bin/env node
    
    const { Command } = require('commander');
    const { name, version } = require('../package.json');
    const program = new Command();
    
    program.name(name).version(version);
    
    program
      .command("create <project-name>")
      .description("create a new project")
      .action((name) => {
        console.info('project', name);
      });
    
    program.parse();

    顶部的  #!/usr/bin/env node  是告诉操作系统,用 /usr/bin 下的 node 来执行这个脚本

     

    一个简单的 cli 命令就创建好了,可以回到根目录,用 node 执行 cli.js 试试:

    node bin/cli.js create my-project

    可以看到 create 命令正常执行 

    但直接通过 node + 路径 的形式运行代码还是太死板了,可以通过  npm link 将项目挂到全局,就能像正常的脚手架工具那样用了

    首先需要在 package.json 里添加 bin 命令:

    {
      ...
      "bin": {
        "my-cli": "bin/cli.js"
      }
    }

    这里的 my-cli 是包的名称,后面的路径需要写全后缀

    然后在根目录执行  npm link 就能将 my-cli 链接到全局

    npm link 是一个很好的本地测试的手段,调试完成后可以通过 npm unlink 卸载

     

     

    四、完善 create 命令 

    据文档介绍, commander 支持将命令拆成单文件进行维护,但我没有搞出来,最后只好加了一层 map 来拆文件

    在根目录下新建一个 command 目录,并创建 command/create.js 和 command/index.js 两个文件:

    然后将 create 命令的相关逻辑移到 command/create.js 中

    // create.js
    
    function createAction(name) {
      console.log('project', name);
    }
    
    const create = {
      alias: 'c',
      params: '<project-name>',
      description: 'create a new project',
      action: createAction,
    }
    
    module.exports = create;

    并在 command/index.js 中导出

    // index.js
    
    const create = require('./create.js');
    
    module.exports = {
      create,
    };

    最后来改造 bin/cli.js

    #!/usr/bin/env node
    
    const { Command } = require('commander');
    const { name, version } = require('../package.json');
    const commands = require('../command/index.js');
    const program = new Command();
    
    program.name(name).version(version);
    
    // 创建命令
    Reflect.ownKeys(commands).map((name) => {
      const { params, alias, action, description } = commands[name] || {};
      program.command(`${name} ${params || ''}`) 
        .alias(alias) 
        .description(description) 
        .action((...args) => {
          typeof action === 'function' && action(...args);
        })
    });
    
    program.parse(process.argv);

    项目的结构基本成型,接下来完善 create 命令

    在文章的开头就已经分析过了,create 命令只需要做一个事情,就是将项目 clone 到当前目录

    const { exec } = require("child_process");
    
    function createAction(name) {
      // 这是模板项目的仓库地址
      const url = "http://github.com/xxx/template.git";
      // 克隆项目
      exec(`git clone ${url} ${name}`, (error, stdout, stderr) => {
        if (error) {
          console.log(error);
          process.exit();
        }
        console.log("Success");
        process.exit();
      });
    }

    如果模板项目是 github 上的项目,应该没什么大问题

    而如果模板项目是小组内部的 gitlab 项目,就需要放开模板项目的访问权限,至少让小组人员都能够访问

     

     

    五、优化体验

    在完成了 create 命令之后,我们的脚手架工具就可以算是开发完成

    但为了更好的体验,可以借助这些工具加以改造:chalkinquirer

    1. chalk

    它可以在命令行打印彩色文字:

    import chalk from 'chalk';
    
    const error = chalk.bold.red;
    const warning = chalk.hex('#FFA500');
    
    console.log(chalk.blue('Hello world!'));
    console.log(error('Error!'));
    console.log(warning('Warning!'));

    2. inquirer

    这是一个让用户与命令行交互的工具

    它提供了很多 api,让用户可以在程序运行的过程中输入内容,从而影响程序运行的结果

    const inquirer = require('inquirer');
    
    inquirer
      .prompt([
        /* Pass your questions in here */
      ])
      .then((answers) => {
        console.log('success', answers);
      })
      .catch((error) => {
        console.log('error');
      });

    在上面的 prompt 中配置需要用户输入/选择的(表单)内容

    比如让用户输入项目名称:

    .prompt([
      {
        type: "input",
        message: "项目名称:",
        name: "projectName",
        validate: (val) => {
          // 对输入的值做判断
          if (!val || !val.trim()) {
            return chalk.red("项目名不能为空,请重新输入");
          } else if (val.includes(" ")) {
            return chalk.red("项目名不能包含空格,请重新输入");
          }
          return true;
        },
      },
    ])

    除了这里的 input 类型外,inquirer 还提供了很多交互类型,如单选列表 list、多选 checkbox、确认项 confirm 等


    在了解了 chalk 和 inquirer 之后,我们就可以进一步改造 create 命令

    npm install inquirer chalk --save
    // create.js
    const inquirer = require("inquirer");
    const chalk = require("chalk");
    const { exec } = require("child_process");
    
    function createProject(name) {
      // 这是模板项目的仓库地址
      const url = "git@github.com:wisewrong/chart-admin.git";
      exec(`git clone ${url} ${name}`, (error, stdout, stderr) => {
        if (error) {
          console.log(chalk.red(error));
          process.exit();
        }
        console.log(chalk.green("Success"));
        process.exit();
      });
    }
    
    const create = {
      alias: "c",
      params: "[project-name]",
      description: "create a new project",
      action: (project) => {
        project
          ? createProject(project)
          : inquirer
              .prompt([
                {
                  type: "input",
                  message: "项目名称:",
                  name: "projectName",
                  validate: (val) => {
                    // 对输入的值做判断
                    if (!val || !val.trim()) {
                      return chalk.red("项目名不能为空,请重新输入");
                    } else if (val.includes(" ")) {
                      return chalk.red("项目名不能包含空格,请重新输入");
                    }
                    return true;
                  },
                },
              ])
              .then((answer) => {
                createProject(answer.projectName);
              });
      },
    };
    
    module.exports = create;

    麻雀虽小五脏俱全,这样一个简单的脚手架就开发完了

    如果需要发布到 npm,可以参考我之前的文章《vue-cli 3.x 开发插件并发布到 npm》

    后面我也会继续完善这个脚手架,添加更多的交互,以及展示进度条(专业挖坑,从来不填...)

  • 相关阅读:
    我的vim开发环境搭建:C/C++/Go,持续更新中
    MFC的组合框(ComboBox)控件切换下拉样式
    回顾下杂乱的10月
    C++将整型数据转换成大端或小端存储顺序
    C/C++动态分配连续空间,下标越界导致的free():invalid next size问题
    O(n)空间复杂度,打印杨辉三角形的前n行
    C指针笔试题,蛋疼的多重指针运算,谭浩强的阴影
    2017滴滴出行笔试题:异或和为0的最大区间个数
    manjaro安装
    关于top命令
  • 原文地址:https://www.cnblogs.com/wisewrong/p/15084250.html
Copyright © 2011-2022 走看看