脚手架功能模块图

需要注意的是不支持 windows 系统
因为内部是使用#!/usr/bin/env node 来执行命令的
项目脚手架搭建  cli js_js

脚手架流程图

项目脚手架搭建  cli js_js_02

安装lerna

lerna是一个可以在管理多个npm包,所以对于多个npm包管理很方便。

npm install lerna -g

建立项目文件夹

mkdir xi-cli
cd xi-cli
mkdir command
mkdir core
mkdir utils

lerna初始化

lerna init

npm包主要是分功能放在command、core、utils三个文件下面,所以需要删除packages,修改lerna.json

 "packages": ["command/*", "core/*", "utils/*"]

项目脚手架搭建  cli js_js_03

实现个模块功能

添加core模块

lerna create @xi-cli/core

项目脚手架搭建  cli js_js_04
给core/index.js 添加#!usr/bin/env node,让系统执行xi-cl的时候,去环境变量里面去找node来执行

// core/index.js
#!usr/bin/env node
//core/package.json
"bin": {
    "xi-cli": "lib/index.js"
  },

执行npm link

npm link

测试命令

xi-cli

添加日志模块

//添加日志
lerna create @xi-cli/log
//安装npmlog
lerna add npmlog --scope=@xi-cli/log

在log/index.js里面,初始化npmlog的基本配置

"use strict";
const npmlog = require("npmlog");
//定义npmlog的level
npmlog.level = process.env.LOG_LEVEL ? process.env.LOG_LEVEL : "info";
npmlog.heading = "xi-cli";
module.exports = npmlog;

core模块功能开发

core模块主要实现init项目命令

//给core模块添加 log模块
lerna add @xi-cli/log --scope=@xi-cli/core

安装commander

lerna add commander --scope=@xi-cli/core

注册init命令

program
    .name("xi-cli")
    .command("init [name]")
    .option("-f,--force", "是否强制初始化项目")
    .action((name) => {
     console.log("name")
    });
 //重点 不能忘记
  program.parse(process.argv);

测试命令

xi-cli init projectname

项目脚手架搭建  cli js_js_05

实现init项目command的逻辑

这里主要实现的功能就是下载模板,render模板,安装依赖,启动项目这几项功能

"use strict";

const fs = require("fs");
const inquier = require("inquirer");
const fse = require("fs-extra");
const {execSync} = require("child_process");
const path = require("path");
const glob = require("glob");
const ejs = require("ejs");
const log = require("@xi-cli/log");
const {resolve} = require("path");
//__dirname 执行文件的目录  这里是init/lib/index.js
//需要的程序执行的当前目录
const localPath = process.cwd();
class Init {
  constructor(name) {
    this.propjectName = name;
    this.force = true;
    this.configInfo = {
      projectName: name,
    };
    this.exec();
  }
  async exec() {
    try {
      // 准备阶段
      await this.prepare();
      //下载模块
      this.downloadTemplate();
      //匹配文件
      let files = await this.getGlobFile();
      //ejsrender模板
      let res = await this.ejsRender(files);
      let tempalteDir = await this.moveFile();
      //删除模板文件夹
      fse.removeSync(path.join(localPath, tempalteDir));
      //安装依赖 并启动
      this.installDepend();
      this.initStartProject();
    } catch (e) {
      log.error(e);
    }
  }
  async prepare() {
    //判断当前目录是否为空
    if (this.isCewEmpty()) {
      //询问是否创建
      const answer = await inquier.prompt([
        {
          type: "confrim",
          name: "isContinue",
          message: "当前文件不为空,是否继续?",
          default: "xi-project",
        },
      ]);
      if (answer.isContinue) {
        //是否启动强制更新
        fse.emptyDirSync(localPath);
      }
    }
  }
  isCewEmpty() {
    let fileList = fs.readdirSync(localPath);
    //文件过滤
    fileList = fileList.filter((file) => {
      return !file.startsWith(".") && ["node_modules"].indexOf(file) < 0;
    });
    return fileList.length > 0 && fileList ? true : false;
  }
  downloadTemplate() {
    log.info("开始下载模板");
    //git模块的地址
    execSync("git clone https://gitee.com/rainbowChenhong/xi-template.git", {
      stdio: [0, 1, 2], // we need this so node will print the command output
      cwd: localPath, // path to where you want to save the file
    });
  }
  async getGlobFile() {
    const dir = process.cwd();
    return new Promise((resolve, reject) => {
      glob(
        "**",
        {
          cwd: dir,
          //忽略文件
          ignore: ["**/**.html"],
          //匹配文件夹
          nodir: true,
        },
        (err, files) => {
          if (err) reject(err);
          resolve(files);
        }
      );
    });
  }
  ejsRender(files) {
    let filesPromise = [];
    files.map((file) => {
      const filePath = path.join(localPath, file);
      filesPromise.push(
        new Promise((resolve, reject) => {
          ejs.renderFile(filePath, this.configInfo, (err, result) => {
            if (err) {
              reject(err);
            }
            if (result) {
              //写入
              fse.writeFileSync(filePath, result);
            }
            resolve(result);
          });
        })
      );
    });
    return Promise.all(filesPromise);
  }
  moveFile() {
    return new Promise((resolve, reject) => {
      glob(
        "**",
        {
          cwd: localPath,
          //忽略文件
          ignore: [],
          //匹配文件夹
          nodir: true,
        },
        (err, files) => {
          let curentDir = "";
          files.map((file) => {
            if (err) reject(err);
            const filePath = path.join(localPath, file);
            let fileAr = file.split("/");
            curentDir = fileAr.shift();
            let newPath = path.join(localPath, fileAr.join("/"));
            //移动文件
            fse.moveSync(filePath, newPath);
          });
          resolve(curentDir);
        }
      );
    });
  }
  installDepend() {
    execSync("npm install ", {
      stdio: [0, 1, 2],
      cwd: localPath,
    });
  }
  initStartProject() {
    execSync("npm run start ", {
      stdio: [0, 1, 2],
      cwd: localPath,
    });
  }
}
module.exports = Init;

修改core/lib/index.js

//安装init模块
lerna add @xi-cli/init --scope=@xi-cli/core
//core/lib/index.js 修改命令
const Init = require("@xi-cli/init");
 program
    .command("init [name]")
    .option("-f,--force", "是否强制初始化项目")
    .action((name) => {
      new Init(name);
    });

测试执行

xi-cli init projectName

效果:
项目脚手架搭建  cli js_js_06

代码地址