title: 前端开发系列132-进阶篇之脚手架Yue-cli的实现01-commander模块
tags:
categories: []
date: 2019-11-02 00:00:08
这是系列文章`前端脚手架实现`的第一篇,主要讲解脚手架工具中的核心模块命令行参数解析功能的实现,重点讲解 Node 模块 [commander]()的使用。

在前端开发中我们已经接触过各种各样的脚手架工具,从Yeomanvue-clidva-cli等,这几篇文章将讲解脚手架工具的核心功能以及具体实现细节,本文探讨哪些功能呢?我们先随便拿一个现成的脚手架工具vue-cli来作为参考。

当我们通过npm install -g @vue/cli 来全局安装 @vue/cli之后,就可以在终端中使用 vue 指令了。

wendingding:vue-test wendingding$ vue --version
@vue/cli 4.3.1
wendingding:vue-test wendingding$ vue --help
Usage: vue <command> [options]

Options:
  -V, --version                              output the version number
  -h, --help                                 output usage information

Commands:
  create [options] <app-name>                create a new project powered by vue-cli-service
  add [options] <plugin> [pluginOptions]     install a plugin and invoke its generator in an already created project
  invoke [options] <plugin> [pluginOptions]  invoke the generator of a plugin in an already created project
  inspect [options] [paths...]               inspect the webpack config in a project with vue-cli-service
  serve [options] [entry]                    serve a .js or .vue file in development mode with zero config
  build [options] [entry]                    build a .js or .vue file in production mode with zero config
  ui [options]                               start and open the vue-cli ui
  init [options] <template> <app-name>       generate a project from a remote template (legacy API, requires @vue/cli-init)
  config [options] [value]                   inspect and modify the config
  outdated [options]                         (experimental) check for outdated vue cli service / plugins
  upgrade [options] [plugin-name]            (experimental) upgrade vue cli service / plugins
  migrate [options] [plugin-name]            (experimental) run migrator for an already-installed cli plugin
  info                                       print debugging information about your environment

  Run vue <command> --help for detailed usage of given command.
  
  wendingding:vue-test wendingding$ abc 
  -bash: abc: command not found

观察上面的终端命令和显示输出,我们总共输入了三个命令
$ vue --version 查看版本信息
$ vue --help 获取帮助信息
$ abc 随意输入的指令,显示command not found该指令不存在。

本文将专注实现上面的功能,假设我们自己实现的脚手架名为Yue-cli那么当我在终端中使用Yue-cli的时候,系统应该认识该指令,且能够获取当前脚手架的版本并能够获取帮助信息,下面给出具体的实现过程。

项目准备

在电脑中新创建文件夹,假设为 Yue-cli ,在该文件夹下面执行下面的命令先做一些准备工作。

npm init -y                         # 初始化package.json
npm install eslint husky --save-dev # eslint是负责代码校验工作,husky提供了git钩子功能
npx eslint --init                   # 初始化eslint配置文件,用于语法检查
目录结构

参考下面的目录结构来创建文件和文件夹,关键。

.
├── bin
│   └── www            <!-- 全局命令执行的根文件 -->
├── node_modules       <!-- 安装的包文件 -->
│   ├── @babel
│   ...
│   └── yaml
├── package-lock.json
├── package.json       <!-- 元信息文件 -->
├── src       
│   └── main.js        <!-- 项目入口文件 -->
│── .huskyrc           <!-- git hook -->
│── .eslintrc.json     <!-- 代码规范校验文件 -->
└── util
    └── constants.js   <!-- 该文件用于存放公共常量数据 -->
配置和链接

❏ 配置 package.json 校验src文件夹下的代码

"scripts": {
    "lint":"eslint src"
}

❏ 配置 husky 文件,当使用git提交前校验代码是否符合规范

{
  "hooks": {
    "pre-commit": "npm run lint"
  }
}

❏ 链接全局包,编写 package.json 文件设置在终端中执行 Yue-cli 时调用 bin 目录下的 www 文件

 "bin": {
        "Yue-cli": "./bin/www"
    },

bin 目录下面的 www 文件设置使用 main.js 作为入口文件,并且以 node 环境 执行此文件

#! /usr/bin/env node
require('../src/main.js');

❏ 链接包到全局环境下使用

npm link

到现在这一步,我们就已经可以成功的在命令行中使用Yue-cli命令了,当在终端中执行Yue-cli命令的时候其内部会执行main.js文件,如果我们在该文件中加上一行打印代码console.log("hello Yue-cli"),那么在终端中可以看到对应的输出。

使用 commander 解析命令行参数

commander 模块可以帮助我们自动的生成 help 信息,解析选项参数大家可以点击到 npmjs网站 查看包模块的详细情况。

先在系统中安装 commander 模块

npm install commander

在入口文件 main.js 文件中引入该模块并测试

const program = require("commander")

// process.argv就是用户在命令行中传入的参数
program.version('1.0.1').parse(process.argv);   

此时,我们终端使用 Yue-cli --help 或者是 Yue-cli --version 就能看到对应的提示信息。

wendingding$ Yue-cli --version
1.0.1

wendingding$ Yue-cli --help
Usage: Yue-cli [options]

Options:
  -V, --version  output the version number
  -h, --help     display help for command

注意:脚手架的这个版本号应该使用的是当前cli项目的版本号,我们需要动态的来获取,具体实现方式是直接把package.json 文件中的 version 字段值导入到main.js文件中直接使用即可。

 const { name, version } = require("../package");

另外,当我们使用脚手架工具的时候,往往不同的指令会对应不同的功能,譬如vue create app的作用是创建项目,而vue ui的作用是开启一个服务以 UI 界面的方式来创建项目,也就是说不能的 命令 它的功能、别名以及使用示例这些信息都是不同的,如何实现呢?

commander 模块我为了提供了对应的方法,下面给出具体的示例(演示使用,实际功能暂欠缺)。

    /* main.js 文件的内容 */
    /* 0.导入模块 */
    const program = require("commander")

    /* 导入常量(package 包中的名称和版本号) */
    const { name, version } = require("../package");

    /* 1.Yue-cli crete */
    program
        /*  命令的名称 */
        .command("create") 
        /*  命令的别名 */
        .alias("c") 
        /* 命令的描述 */
        .description("create a project whit Yue-cli....") 
        /* 该命令的具体功能(动作) */
        .action(() => { 
            console.log(`执行 action-> create`);
        });

    /* 2.Yue-cli config */
    program
        .command("config")
        .alias("conf")
        .description("config info....")
        .action(() => { 
            console.log(`执行 action-> config`);
        });

    /* 3.Yue-cli xxx  (其它未定义指令) */
    program
        .command("*")
        .alias("")
        .description("command not found")
        .action(() => { 
            console.log(`执行 action-> nothing`);
        });

    /* 4.示例信息 */
    const examples = [
        "Yue-cli create <project-name>",
        "Yue-cli config get <k>",
        "Yue-cli config set <k> <v>"
    ];

    // 5.监听用户的help 事件
    program.on('--help', () => {
        /* 当终端输入 Yue-cli --help指令的时候打印nExamples信息 */
        console.log('\nExamples:');
        examples.forEach(example => console.log(`   ${example}`))
    });

    /* 6.版本信息 + 命令行参数解析 */
    program.version(`version = ${version}`).parse(process.argv);

我们来看看此时,我们的工具拥有了哪些功能?

wendingding$ Yue-cli --help
Usage: Yue-cli [options] [command]

Options:
  -V, --version   output the version number
  -h, --help      display help for command

Commands:
  create|c        create a project whit Yue-cli....
  config|conf     config info....
  *               command not found
  help [command]  display help for command

Examples:
   Yue-cli create <project-name>
   Yue-cli config get <k>
   Yue-cli config set <k> <v>

wendingding$ Yue-cli --version
version = 1.0.1

wendingding$ Yue-cli create myapp
执行 action-> create

wendingding$ Yue-cli config
执行 action-> config

wendingding$ Yue-cli c app
执行 action-> create

写到这里,脚手架工具的基本提示功能就已经实现了,但代码较长且脚手架的指令肯定不止 createconfig 这么两个,因此这里适当调整下代码结构让其可维护性更高一些。

    /* main.js 文件的内容 */
    /* 导入模块 */
    const program = require("commander");
    /* 导入常量(package 包中的名称和版本号) */
    const { name, version } = require("../util/constants");

    /* 组织映射结构 */
    const actions = {
        create: { // 项目创建(初始化)指令
            description: 'create project with Yue-cli',
            alias: 'c',
            examples: [
                'Yue-cli create <project-name>',
            ],
        },
        config: { // 设置项目配置文件指令
            description: 'config info',
            alias: 'conf',
            examples: [
                'Yue-cli config get <k>',
                'Yue-cli config set <k> <v>',
            ],
        },
        '*': {
            description: 'command not found',
            alias: '',
            examples: [],
        },
    };

    Object.keys(actions).forEach((action) => {
        program
            /* 命名的名称 */
            .command(action)
            /* 命名的别名 */
            .alias(actions[action].alias)
            /* 命令的描述信息 */
            .description(actions[action].description)
            /* 命令的任务(功能) */
            .action(() => { // 动作
                console.log(`执行 action->`, action);
            });
    });

    // 监听用户的help 事件
    program.on('--help', () => {
        console.log('\nExamples:');
        Reflect.ownKeys(actions).forEach((action) => {

            actions[action].examples.forEach((example) => {
                console.log(`  ${example}`);
            });
        });
    });

    /* 版本信息 + 命令行参数解析 */
    program.version(`version = ${version}`).parse(process.argv);