本篇是前端工程化之依托模板搭建企业级脚手架(cli)。通过脚手架,我们可以快速初始化一个项目,无需自己从零开始一步步…


一、背景

1.为什么

通过脚手架,我们可以快速初始化一个项目,无需自己从零开始一步步配置,有效提升开发体验。

当然,社区已经贡献了vue-clicreate-react-appreact-native-cli等非常优秀的脚手架,

但是,这些脚手架未必完全符合我们的实际应用,特别是作为企业通用脚手架而言,所以我们需要定制自己的脚手架,来提升开发效率。

2.脚手架的作用

  • 减少重复性工作:不需要复制其他项目再删除无关代码,或者从零创建一个项目和文件。
  • 多套模板:可以根据交互动态生成项目结构和配置文件。
  • 协作:多人协作更为方便,不需要把文件传来传去。

本项目完整代码地址,在文章末尾。


二、提供的功能

明确脚手架(@lianpf/create-app-cli)需要提供的功能:

  • ca init <template-name> <project-name> 根据远程模板,初始化一个项目(远程模板可配置)
  • ca config set <template-name> <repository> 设置模板信息(暂未实现)
  • ca config get templates 查看模板配置信息
  • ca cfg get templates 同上
  • ca -v 查看当前版本号
  • ca -h 帮助

如果有其他需求,可根据需求,自行拓展commander


三、配置搭建cli

1.依赖的第三方库

  • @babel/cli@babel/core@babel/preset-env: 语法转换
  • commander: 命令行工具
  • download-git-repo: 用来下载远程模板
  • ini: 配置项格式转换
    • 『暂时没用』一般用于物理机本地配置项
  • inquirer: 交互式命令行工具
  • ora: 显示node命令环境loading动画
  • chalk: 修改控制台输出内容样式
  • log-symbols: 显示出×等的图标
  • ascii-table: 以表格形式展示数据,比如:模板列表

关于以上第三方package的介绍,可直接npm.org上查看相应的说明

2.搭建cli

2.1 初始化项目

创建一个空项目(create-app-cli)

create-app-cli $ npm init // 项目初始化

安装依赖

create-app-cli $ npm install @babel/cli @babel/core @babel/preset-env chalk commander download-git-repo ini inquirer log-symbols ora -D

目录结构

.
├── bin
│   └── www               // 可执行文件
├── lib
    ├── ...               // 生成文件
├── src
│   ├── config.js         // 管理 @lianpf/create-app-cli 配置文件
│   ├── index.js          // 主流程入口文件
│   ├── init.js           // init command
│   ├── main.js           // 入口文件
│   └── utils
│       ├── constants.js  // 定义常量
│       ├── download.js   // 模板远程仓库下载
│       └── rc.js         // 配置文件
├── templates.json        // 模板配置文件
├── .babelrc              // babel配置文件
├── package.json
└── README.md

babel配置:开发使用了ES6语法,使用 babel 进行转义

.bablerc

{
    "presets": [
        [
            "@babel/preset-env",
            {
                "targets": {
                    "node": "current"
                }
            },
            "@babel/preset-react" // react 语法
        ]
    ],
    "plugins": [
        "transform-class-properties",
        "transform-decorators",
        "transform-react-constant-elements",
        "transform-react-inline-elements"
    ]
}

2.2 @lianpf/create-app-cli开发环境搭建

2.2.1 脚手架开发:启动命令配置

node.js内置了对命令行操作的支持,package.json中的bin 字段可以定义命令名和关联的执行文件。在package.json中添加 bin字段

package.json

{
    "name": "@lianpf/create-app-cli",
    "version": "0.2.1",
    "description": "A cli to help create a project",
    "main": "index.js",
    "bin": {
        "ca": "./bin/www"
    },
    "scripts": {
        "compile": "babel src -d lib",
        "watch": "npm run compile -- --watch"
    }
}

www 文件

行首加入一行#!/usr/bin/env node指定当前脚本由node.js进行解析

#! /usr/bin/env node
require('../lib/main.js');
2.2.2 脚手架测试:链接到全局环境

开发过程中为了方便调试,在当前的create-app-cli目录下执行 npm link,将ca命令链接到全局环境

2.2.3 脚手架开发:启动项目
create-app-cli $ npm run watch

2.3 @lianpf/create-app-cli命令行处理

利用commander来处理命令行

/create-app-cli/src/main.js

import program from 'commander';
import { VERSION, make_success, make_fail } from './utils/constants';
import apply from './index';

/**
 * ca commands
 * - config
 * - init 
 * - v
 * - h
 */

let actionMap = {
    // init 命令配置
    init: {
        ...
    },
    // config 命令配置
    config: {
        ...
    }
}

// 遍历预设命令,进行处理配置
Object.keys(actionMap).forEach((action) => {
    program.command(action)
    .description(actionMap[action].description)
    .alias(actionMap[action].alias)
    .action(() => {
        switch (action) {
            case 'config':
                apply(action, ...process.argv.slice(3));
                break;
            case 'init':
                apply(action, ...process.argv.slice(3));
                break;
            default:
                break;
        }
    });
});

function help() {
    // -h 参数是,遍历列出所有命令
    ...
}

program.usage('<command> [options]');
program.on('-h', help);
program.on('--help', help);
program.version(VERSION, '-v, --version').parse(process.argv);

// ca 不带参数时
if (!process.argv.slice(2).length) {
    program.outputHelp(make_success);
}

2.4 @lianpf/create-app-cli下载模板

download-git-repo支持从Github、Gitlab下载远程仓库到本地

/create-app-cli/src/util/download.js

import downloadGit from 'download-git-repo';
import { templateConfig, hasTemplate } from './constants';

export const downloadLocal = async (templateName, projectName) => {
    const _hasTemplate = hasTemplate(templateName)
    let api = ''
    // 判断是否存在模板
    if (_hasTemplate) {
        // 获取 模板下载地址
        api = `${templateConfig[templateName].type}:${templateConfig[templateName].user}/${templateConfig[templateName].repository}#${templateConfig[templateName].branch}`;
    }
    return new Promise((resolve, reject) => {
        // projectName 为下载到的本地目录
        downloadGit(api, projectName, { clone: true }, (err) => {
            if (err) {
                reject(err);
            } else {
                resolve();
            }
        });
    });
}

constants.js获取template配置

/create-app-cli/src/util/constants.js

...
import templates from '../../templates.json'

...

// template config
export const templateConfig = templates

// 判断是否存在当前模板
export const hasTemplate = (templateName) => {
    return Object.keys(templateConfig).indexOf(templateName) > -1
}

...

静态template配置

/create-app-cli/template.json

{
  "react-template": {
    "type": "https://github.com",
    "user": "xxx",
    "repository": "xxx",
    "branch": "master",
    "templateName": "react-template",
    "keyWords": "react、webpack"
  },
  ...
}

注意,这里没有实现ca config set <template-name> <repository>设置模板命令,且企业级脚手架对应模板相对固定,故我们将模板预先内置到template.json文件中

2.5 @lianpf/create-app-cli交互式命令和美化

2.5.1 命令行交互

用户执行init命令后,向用户提出问题,接收用户的输入并作出相应的处理。命令行交互利用inquirer实现:

/create-app-cli/src/init.js

inquirer.prompt([
    {
        name: 'description',
        message: 'Please enter the project description: '
    },
    {
        name: 'author',
        message: 'Please enter the author name: '
    }
]).then((answer) => {
    //...
});

类似于:npm init命令执行后,项目初始化,会问询你作者、版本信息、描述等添加到package.json配置文件的交互操作。

2.5.2 视觉美化

在上一步,用户输入后,开始下载模板,此时使用ora提示用户正在下载模板,以及下载结束后给出相应提示。

/create-app-cli/src/init.js

...
import ora from 'ora';

...
let loading = ora('downloading template ...');
loading.start();
//download
loading.succeed(); //或 loading.fail();

2.6 @lianpf/create-app-cli获取模板配置

config配置支持使用其它仓库作为模板。这样使用者可以自由选择下载目标

/create-app-cli/src/config.js

import { get } from './utils/rc';
import { make_success, make_fail, make_warn } from './utils/constants';

let config = async (action, key) => {
    switch (action) {
        case 'get':
            if (key) {
                let result = await get(key);
                if (result.code === 0) {
                    console.log(make_success(result.message));
                } else {
                    console.log(make_fail(result.message));
                }
                
            } else {
                console.log(make_warn('Command does not exist!'));
            }
            break;
        // set 模板命令暂未实现,需通过 template.json 配置
        // case 'set':
            // set(key, value);
            // break;
        // case 'remove':
            // remove(key);
            // break;
        default:
            console.log(make_warn('Command does not exist!'));
            break;
    }
}

module.exports = config;

rc.js负责对本地物理机『模板配置文件』的增删改查,这里改造为对脚手架模板配置文件template.json的查询

/create-app-cli/src/util/rc.js

import { configCommand, make_success, make_warn, templateConfig } from './constants';
import AsciiTable from 'ascii-table'

// constants 配置文件
export const get = async (key) => {
    ...
    if (opts.indexOf(key) !== -1) {
        switch (key) {
            case 'templates':
                // code、message、data处理
                ...
                break;
            default:
                ...
                break;
        }
    }
    ...
    console.log(make_success(templateTable.toString()))
    return {
        code,
        data: {},
        message
    };
}

四、发布cli

  • npm publish脚手架发布。
  • 其它用户可通过npm install @lianpf/create-app-cli -g全局安装,即可使用ca命令。

仓库源码

谢谢各位花费宝贵的时间阅读本文,以下是源码仓库地址,如果本文给了您一点帮助或者是启发,请动动小手点进Github仓库,不要吝啬你的Star,您的肯定是我前进的最大动力


参考


最后, 希望大家早日实现:成为编程高手的伟大梦想!
欢迎交流~

微信公众号

本文版权归原作者曜灵所有!未经允许,严禁转载!对非法转载者, 原作者保留采用法律手段追究的权利!
若需转载,请联系微信公众号:连先生有猫病,可获取作者联系方式!