【架构师(第十三篇)】脚手架之创建项目准备阶段开发

1,330 阅读3分钟

将收获什么

  • 掌握命令行交互方法
  • 服务端框架 egg.js 的应用和 api 开发方法
  • egg.js 集成云 mongodb 实现数据存储

主要内容

  • 脚手架项目创建功能架构设计
  • 通过命令行交互获取项目基本信息
  • egg.js + 云 mongodb 的集成
  • 开发前端项目模板
  • egg.js 获取项目模板 API 开发
  • 项目模板下载功能开发
  • inquirer 源码解析,彻底搞懂命令行交互

创建功能架构设计

  • 可拓展:能够快速复用到不同团队,适应不同团队之间的差异
  • 低成本:在不改动脚手架源码的情况下新增模板,且新增模板的成本很低
  • 高性能:控制存储空间,安装时充分利用 node 多进程提升安装性能

创建功能架构设计图

准备阶段

image.png

下载模板阶段

image.png

安装模板阶段

image.png

准备阶段开发

// commands\init\lib\index.js
'use strict';

const fs = require('fs');
const Command = require('@hzw-cli-dev/command');
const log = require('@hzw-cli-dev/log');
const inquirer = require('inquirer');
const fse = require('fs-extra');
const semver = require('semver');
const TYPE_PROJECT = 'project';
const TYPE_COMPONENT = 'component';

/**
 * @description: init 命令 , 继承于 Command
 * @param {*}
 * @return {*}
 */
class InitCommand extends Command {
  /**
   * @description:命令的初始化阶段
   * @param {*}
   * @return {*}
   */
  init() {
    // 项目名称
    this.projectName = this._argv[0] || 'project';
    // 是否携带 force 参数
    this.force = !!this._argv[1].force;
    log.verbose('🚀🚀 ~ 是否强制安装', this.force);
    log.verbose('🚀🚀 ~ 项目名称', this.projectName);
  }
  /**
   * @description: 命令的执行阶段
   * @param {*}
   * @return {*}
   */
  async exec() {
    try {
      // 1.准备阶段
      const ret = await this.prepare();
      if (ret) {
        // 2.下载模板
        // 3.安装模板
      }
    } catch (error) {
      log.error(error.message);
    }
  }
  /**
   * @description:准备阶段
   * @param {*}
   * @return {*}
   */
  async prepare() {
    // 获取当前目录  path.resolve('.') 也可以拿到
    const localPath = process.cwd();
    // 1.判断当前目录是否为空
    const isEmpty = this.isDirEmpty(localPath);
    let isContinue = false;
    if (!isEmpty) {
      if (!this.force) {
        // 询问用户是继续
        const res = await inquirer.prompt([
          {
            type: 'confirm',
            name: 'isContinue',
            message: '当前文件夹内容不为空,是否在此继续创建项目?',
            default: false,
          },
        ]);
        isContinue = res.isContinue;
        if (!isContinue) {
          return false;
        }
      }
      // 2.是否启动强制安装
      // 清空文件夹进行创建
      if (isContinue || this.force) {
        const { isDelete } = await inquirer.prompt([
          {
            type: 'confirm',
            name: 'isDelete',
            message: '是否清空当前目录下的文件?',
            default: false,
          },
        ]);
        if (isDelete) {
          // 清空当前目录
          fse.emptyDirSync(localPath);
        }
      }
    }
    // 3.选择创建项目或者组件
    // 4.获取项目的基本信息 return Object
    return this.getInfo();
  }

  /**
   * @description: 选择创建项目或者组件 获取项目的基本信息 return Object
   * @param {*}
   * @return {*} 项目的基本信息
   */
  async getInfo() {
    const info = {};
    // 选择创建项目或者组件;
    const { type } = await inquirer.prompt({
      type: 'list',
      message: '请选择初始化类型',
      name: 'type',
      default: TYPE_PROJECT,
      choices: [
        {
          name: '项目',
          value: TYPE_PROJECT,
        },
        {
          name: '组件',
          value: TYPE_COMPONENT,
        },
      ],
    });
    log.verbose('type', type);
    // 获取项目的基本信息;
    if (type === TYPE_COMPONENT) {
    }
    if (type === TYPE_PROJECT) {
      const o = await inquirer.prompt([
        {
          type: 'input',
          message: '请输入项目名称',
          name: 'project',
          validate: (a) => {
            const reg =
              /^[a-zA-Z]+([-][a-zA-Z0-9]|[_][a-zA-Z0-9]|[a-zA-Z0-9])*$/;
            if (reg.test(a)) {
              return true;
            }
            return '要求英文字母开头,数字或字母结尾,字符只允许使用 - 以及 _ ';
          },
        },
        {
          type: 'input',
          message: '请输入项目版本号',
          name: 'version',
          default: '1.0.0',
          validate: (a) => {
            return !!semver.valid(a) || '请输入合法的版本号';
          },
          filter: (a) => {
            if (!!semver.valid(a)) {
              return semver.valid(a);
            }
            return a;
          },
        },
      ]);
      console.log('🚀🚀 ~ InitCommand ~ o', o);
    }
    return info;
  }

  /**
   * @description: 判断当前目录是否为空
   * @param {*}
   * @return { Boolean }  true 空的  false  不是空的
   */
  isDirEmpty(localPath) {
    // 读取当前目录下所有文件
    let fileList = fs.readdirSync(localPath);
    // 过滤掉不影响的文件目录(白名单)
    fileList = fileList.filter(
      (file) => !file.startsWith('.') && ['node_modules'].indexOf(file) < 0,
    );
    // 没有目录 或者 文件数量为 0 返回 true
    return !fileList || fileList.length === 0;
  }
}

/**
 * @description: 返回 InitCommand 的实例
 * @param {*} argv : projectName  项目名称 , 命令的 options  , commander实例
 * @return {*}
 */
function init(argv) {
  return new InitCommand(argv);
}

module.exports = init;
module.exports.InitCommand = InitCommand;