首页
学习
活动
专区
圈层
工具
发布
50 篇文章
1
【架构师(第一篇)】整体需求分析和架构设计
2
【架构师(第二篇)】脚手架架构设计和框架搭建
3
【架构师(第三篇)】脚手架开发之掌握Lerna操作流程
4
【架构师(第四篇)】脚手架开发之Lerna源码分析
5
【架构师(第五篇)】脚手架之import-local执行流程及简历设计
6
【架构师(第六篇)】脚手架之需求分析和架构设计
7
【架构师(第七篇)】脚手架之准备阶段编写
8
【架构师(第八篇)】脚手架之 commander 框架使用方法
9
【架构师(第九篇)】如何让 Node 环境支持 ES Module
10
【架构师(第十篇)】脚手架之注册命令及架构优化
11
【架构师(第十一篇)】脚手架之命令注册和执行过程开发
12
【架构师(第十二篇)】脚手架之命令行交互工具 inquirer.js 使用方法
13
【架构师(第十三篇)】脚手架之创建项目准备阶段开发
14
【架构师(第十四篇)】脚手架之 egg.js 和 mongodb 的使用
15
【架构师(第十五篇)】脚手架之创建项目模板开发
16
【架构师(第十六篇)】脚手架之创建项目模板的下载与更新
17
【架构师(第十七篇)】脚手架之 ejs 和 glob 的使用
18
【架构师(第十八篇)】脚手架之项目模板的安装
19
【架构师(第十九篇)】脚手架之组件库模板开发
20
【架构师(第二十篇)】脚手架之自定义模板及第一阶段总结
21
【架构师(第二十一篇)】编辑器开发之需求分析和架构设计
22
【架构师(第二十二篇)】编辑器开发之项目整体搭建
23
【架构师(第二十三篇)】编辑器开发之画布区域组件的渲染
24
【架构师(第二十四篇)】编辑器开发之添加模版到画布
25
【架构师(第二十五篇)】编辑器开发之属性编辑区域表单渲染
26
【架构师(第二十六篇)】编辑器开发之属性编辑同步渲染
27
【架构师(第二十七篇)】前端单元测试框架 Jest 基础知识入门
28
【架构师(第二十八篇)】 测试工具 Vue-Test-Utils 基础语法
29
【架构师(第二十九篇)】Vue-Test-Utils 触发事件和异步请求
30
【架构师(第三十篇)】Vue-Test-Utils 全局组件和第三方库 vuex | vue-router
31
【架构师(第三十一篇)】前端测试之 TDD 的开发方式
32
【架构师(第三十二篇)】 通用上传组件开发及测试用例
33
【架构师(第三十三篇)】 Vue 中的实例及本地图片预览
34
【架构师(第三十四篇)】 业务组件库开发之 vue3 的插件系统
35
【架构师(第三十五篇)】 业务组件库开发之使用 Rollup 进行打包
36
【架构师(第三十六篇)】 业务组件库开发之发布到 NPM
37
【架构师(第三十七篇)】 服务端开发之后端框架与数据库技术选型
38
【架构师(第三十八篇)】 服务端开发之本地安装最新版 MySQL 数据库
39
【架构师(第三十九篇)】 服务端开发之连接 MySQL 数据库
40
【架构师(第四十篇)】 服务端开发之连接 Mongodb 数据库
41
【架构师(第四十一篇)】 服务端开发之安装并连接 Redis数据库
42
【架构师(第四十二篇)】 服务端开发之常用的登录鉴权方式
43
【架构师(第四十三篇)】 服务端开发之单元测试和接口测试
44
【架构师(第四十四篇)】 服务端开发之 pm2 和 nginx 介绍
45
【架构师(第四十五篇)】 服务端开发之认识 Github actions
46
【架构师(第四十六篇)】 服务端开发之安装 Docker
47
【架构师(第四十七篇)】 服务端开发之认识 Docker
48
【架构师(第四十八篇)】 服务端开发之 Dockerfile
49
【架构师(第四十九篇)】 服务端开发之认识 Docker-compose
50
【架构师(第五十篇)】 服务端开发之自动发布到测试机
清单首页架构文章详情

【架构师(第十一篇)】脚手架之命令注册和执行过程开发


脚手架命令动态加载功能架构设计图

是否执行本地代码

设置全局的 targetPath

代码语言:javascript
复制
// 设置全局的 targetPath
program.on('option:targetPath', () => {
    process.env.CLI_TARGET_PATH = params.targetPath;
});

新建一个 exec 包,动态加载命令

代码语言:javascript
复制
lerna create @hzw-cli-dev/exec

新建一个 package 包,放在 models 目录下面

代码语言:javascript
复制
lerna create @hzw-cli-dev/package

新建一个 command 包,放在 models 目录下面

代码语言:javascript
复制
lerna create @hzw-cli-dev/command

npminstall 用法

代码语言:javascript
复制
const npmInstall = require('npminstall');
const path = require('path');
const userHome = require('user-home');

// 直接调用安装方法
npmInstall({
  root: path.resolve(userHome, '.hzw-cli-dev'), // 模块路径
  storeDir: path.resolve(userHome, '', 'node_modules'), // 模块安装位置
  register: 'https://registry.npmjs.org', // 设置 npm 源
  pkgs: [ // 要安装的包信息
    {
      name: 'warbler-js',
      version: '',
    },
  ],
});

Package 类的实现

代码语言:javascript
复制
'use strict';

const { isObject } = require('@hzw-cli-dev/utils');
const { getRegister, getLatestVersion } = require('@hzw-cli-dev/get-npm-info');
const formatPath = require('@hzw-cli-dev/format-path');
const npmInstall = require('npminstall');
const fse = require('fs-extra');
const pathExists = require('path-exists').sync;
const pkgDir = require('pkg-dir').sync;
const path = require('path');
// Package 类 管理模块
class Package {
  /**
   * @description: 构造函数
   * @param {*} options 用户传入的配置信息
   * @return {*}
   */
  constructor(options) {
    if (!options) {
      throw new Error('Package 类的参数不能为空!');
    }
    if (!isObject(options)) {
      throw new Error('Package 类的参数必须是对象类型!');
    }
    // 获取 targetPath ,如果没有 则说明不是一个本地的package
    this.targetPath = options.targetPath;
    // 模块安装位置 缓存路径
    this.storeDir = options.storeDir;
    // package 的 name
    this.packageName = options.packageName;
    // package 的 Version
    this.packageVersion = options.packageVersion;
    // 缓存路径的前缀
    this.cacheFilePathPrefix = this.packageName.replace('/', '_');
  }

  /**
   * @description: 准备工作
   * @param {*}
   * @return {*}
   */
  async prepare() {}

  /**
   * @description: 获取当前模块缓存路径
   * @param {*}
   * @return {*}
   */
  get cacheFilePath() {}

  /**
   * @description: 获取最新版本模块缓存路径
   * @param {*}
   * @return {*}
   */
  getSpecificFilePath(packageVersion) {}

  /**
   * @description: 判断当前 package 是否存在
   * @param {*}
   * @return {*}
   */
  async exists() {}

  /**
   * @description: 安装 package
   * @param {*}
   * @return {*}
   */
  async install() {}

  /**
   * @description: 更新 package
   * @param {*}
   * @return {*}
   */
  async update() {}

  /**
   * @description:获取入口文件的路径
   * 1.获取package.json所在的目录 pkg-dir
   * 2.读取package.json
   * 3.找到main或者lib属性 形成路径
   * 4.路径的兼容(macOs/windows)
   * @param {*}
   * @return {*}
   */
  getRootFilePath() {}
module.exports = Package;

准备工作

代码语言:javascript
复制
  /**
   * @description: 准备工作
   * @param {*}
   * @return {*}
   */
  async prepare() {
    // 当缓存目录不存在的时候
    if (this.storeDir && !pathExists(this.storeDir)) {
      // 创建缓存目录
      fse.mkdirpSync(this.storeDir);
    }
    // 获取最新版本
    const latestVersion = await getLatestVersion(this.packageName);
    // 如果设定的版本号是最新的话,就赋值
    if (this.packageVersion === 'latest') {
      this.packageVersion = latestVersion;
    }
  }

获取当前模块缓存路径

代码语言:javascript
复制
  /**
   * @description: 获取当前模块缓存路径
   * @param {*}
   * @return {*}
   */
  get cacheFilePath() {
    return path.resolve(
      this.storeDir,
      `_${this.cacheFilePathPrefix}@${this.packageVersion}@${this.packageName}`,
    );
  }

获取最新版本模块缓存路径

代码语言:javascript
复制
  /**
   * @description: 获取最新版本模块缓存路径
   * @param {*}
   * @return {*}
   */
  getSpecificFilePath(packageVersion) {
    return path.resolve(
      this.storeDir,
      `_${this.cacheFilePathPrefix}@${packageVersion}@${this.packageName}`,
    );
  }

判断当前 package 是否存在

代码语言:javascript
复制
  /**
   * @description: 判断当前 package 是否存在
   * @param {*}
   * @return {*}
   */
  async exists() {
    // 如果 this.storeDir 存在 ,就是需要下载安装,否则就是本地安装
    if (this.storeDir) {
      // 获取具体版本号
      await this.prepare();
      return pathExists(this.cacheFilePath);
    } else {
      // 查看本地路径是否存在
      return pathExists(this.targetPath);
    }
  }

安装 package

代码语言:javascript
复制
  /**
   * @description: 安装 package
   * @param {*}
   * @return {*}
   */
  async install() {
    await this.prepare();
    return npmInstall({
      root: this.targetPath, // 模块路径
      storeDir: this.storeDir, // 模块安装位置
      register: getRegister('npm'), // 设置 npm 源
      pkgs: [
        // 要安装的包信息
        {
          name: this.packageName,
          version: this.packageVersion,
        },
      ],
    });
  }

更新 package

代码语言:javascript
复制
  /**
   * @description: 更新 package
   * @param {*}
   * @return {*}
   */
  async update() {
    await this.prepare();
    // 获取最新版本号
    const latestVersion = await getLatestVersion(this.packageName);
    // 查询本地是否已经是最新版本
    const localPath = this.getSpecificFilePath(latestVersion);
    const isLocalLatestVersion = pathExists(localPath);
    console.log('🚀🚀 ~ Package ~ latestVersion', latestVersion);
    console.log('🚀🚀 ~ Package ~ localPath', localPath);
    console.log('🚀🚀 ~ Package ~ isLocalLatestVersion', isLocalLatestVersion);
    // 如果不是最新版本 安装最新版本
    if (!isLocalLatestVersion) {
      await npmInstall({
        root: this.targetPath, // 模块路径
        storeDir: this.storeDir, // 模块安装位置
        register: getRegister('npm'), // 设置 npm 源
        pkgs: [
          // 要安装的包信息
          {
            name: this.packageName,
            version: latestVersion,
          },
        ],
      });
      this.packageVersion = latestVersion;
    }
  }

获取入口文件的路径

代码语言:javascript
复制
  /**
   * @description:获取入口文件的路径
   * 1.获取package.json所在的目录 pkg-dir
   * 2.读取package.json
   * 3.找到main或者lib属性 形成路径
   * 4.路径的兼容(macOs/windows)
   * @param {*}
   * @return {*}
   */
  getRootFilePath() {
    function _getRootFIle(_path) {
      const dir = pkgDir(_path);
      if (dir) {
        const pkgFile = require(path.resolve(dir, 'package.json'));
        if (pkgFile && (pkgFile.main || pkgFile.lib)) {
          const rootPath =
            formatPath(path.resolve(dir, pkgFile.main)) ||
            formatPath(path.resolve(dir, pkgFile.lib));
          return rootPath;
        }
      }
      return null;
    }
    // 如果 this.storeDir 存在 ,就是需要下载安装,否则就是本地安装
    if (this.storeDir) {
      return _getRootFIle(this.cacheFilePath);
    }
    return _getRootFIle(this.targetPath);
  }

脚手架优化后执行过程

Command 类的实现

代码语言:javascript
复制
'use strict';

// 引入版本比对第三方库 semver
const semver = require('semver');
const LOWEST_NODE_VERSION = '12.0.0';
// 引入颜色库 colors
const colors = require('colors/safe');
const log = require('@hzw-cli-dev/log');
const { isArray } = require('@hzw-cli-dev/utils');
class Command {
  /**
   * @description: 命令的准备和执行阶段
   * @param {*} argv : projectName  项目名称 , 命令的 options  , commander实例
   * @return {*}
   */
  constructor(argv) {
    if (!argv) {
      throw new Error('参数不可以为空!');
    }
    if (!isArray(argv)) {
      throw new Error('参数必须是数组类型!');
    }
    if (argv.length < 1) {
      throw new Error('参数列表不可以为空!');
    }
    this._argv = argv;
    let runner = new Promise((resolve, reject) => {
      let chain = Promise.resolve();
      chain = chain.then(() => this.checkNodeVersion());
      chain = chain.then(() => this.initArgs());
      chain = chain.then(() => this.init());
      chain = chain.then(() => this.exec());
      chain.catch((error) => log.error(error.message));
    });
  }

  /**
   * @description: 检查当前的 node 版本,防止 node 版本过低调用最新 api 出错
   * @param {*}
   * @return {*}
   */
  checkNodeVersion() {
    // 获取当前 node 版本号
    const currentVersion = process.version;
    log.test('环境检查 当前的node版本是:', process.version);
    // 获取最低 node 版本号
    const lowestVersion = LOWEST_NODE_VERSION;
    // 对比最低 node 版本号
    if (!semver.gte(currentVersion, lowestVersion)) {
      throw new Error(colors.red('错误:node版本过低'));
    }
  }

  /**
   * @description:初始化参数
   * @param {*}
   * @return {*}
   */
  initArgs() {
    this._cmd = this._argv[this._argv.length - 1];
    this._argv = this._argv.slice(0, this._argv.length - 1);
  }

  init() {
    throw new Error('command 必须拥有一个 init 方法');
  }

  exec() {
    throw new Error('command 必须拥有一个 exec 方法');
  }
}

module.exports = Command;

注:第四周 第五章全部以及第七章全部 没看懂,后面需要再复习一下,但是不影响主课程的进行

exec 的实现

代码语言:javascript
复制
'use strict';

const Package = require('@hzw-cli-dev/package');
const log = require('@hzw-cli-dev/log');
const path = require('path');

const cp = require('child_process');

// package 的映射表
const SETTINGS = {
  // init: '@hzw-cli-dev/init',

  init: '@imooc-cli/init',
};

// 缓存目录
const CACHE_DIR = 'dependencies';

/**
 * @description: 动态加载命令
 * 1.获取 targetPath
 * 2.获取 modulePath
 * 3.生成一个 package
 * 4.提供一个 getRootFIle 方法获取入口文件
 * 5.提供 update 更新方法以及 install 安装方法
 * @return {*}
 */
async function exec(...argv) {
  // 获取 targetPath
  let targetPath = process.env.CLI_TARGET_PATH;
  // 获取 homePath
  const homePath = process.env.CLI_HOME_PATH;
  log.verbose('targetPath', targetPath);
  log.verbose('homePath', homePath);
  // 获取 commander 实例
  const cmdObj = argv[argv.length - 1];
  // 获取命令名称 exp:init
  const cmdName = cmdObj.name();
  // 获取 package 的 name  exp:根据 init 命令 映射到 @hzw-cli-dev/init 包
  const packageName = SETTINGS[cmdName];
  // 获取 package 的 version
  const packageVersion = 'latest';
  // 模块安装路径
  let storeDir = '';
  // package 类
  let pkg = '';

  // 如果  targetPath 不存在
  if (!targetPath) {
    targetPath = path.resolve(homePath, CACHE_DIR); // 生成缓存路径
    storeDir = path.resolve(targetPath, 'node_modules');
    // 创建 Package 对象
    pkg = new Package({
      storeDir,
      targetPath,
      packageName,
      packageVersion,
    });
    // 如果当前 package 存在
    if (await pkg.exists()) {
      // 更新 package
      pkg.update();
    } else {
      // 安装 package
      await pkg.install();
    }
  } else {
    // 如果 targetPath 存在
    pkg = new Package({
      targetPath,
      packageName,
      packageVersion,
    });
  }

  // 获取模块入口
  const rootFile = pkg.getRootFilePath();
  if (!rootFile) {
    throw new Error('模块不存在入口文件!');
  }
  // 执行模块
  try {
    // 在当前进程中调用
    // rootFile &amp;&amp; require(rootFile)(argv);
    // 在 node 子进程中调用
    const cmd = argv[argv.length - 1];
    const newCmd = Object.create(null);
    // 给参数瘦身
    Object.keys(cmd).forEach((key) => {
      if (cmd.hasOwnProperty(key) &amp;&amp; !key.startsWith('_') &amp;&amp; key !== 'parent') {
        newCmd[key] = cmd[key];
      }
    });
    argv[argv.length - 1] = newCmd;
    // 组合code
    const code = `require('${rootFile}')(${JSON.stringify(argv)})`;
    const childProcess = spawn('node', ['-e', code], {
      cwd: process.cwd(),
      stdio: 'inherit',
    });
    childProcess.on('error', (error) => {
      log.error(error.message);
      process.exit(1);
    });
    childProcess.on('exit', (e) => {
      log.verbose('命令执行成功');
      process.exit(e);
    });
  } catch (error) {
    console.log(error.message);
  }
}

/**
 * @description: 封装一个 spawn 方法,兼容 mac 和 windows
 * windows : cp.spawn('cmd',['/c','node','-e',code],{})
 * mac : cp.spawn('node', ['-e', code],{})
 * @param {*} command 'cmd'
 * @param {*} args ['/c','node','-e',code]
 * @param {*} options {}
 * @return {*} cp.spawn(cmd, cmdArgs, options)
 */

/** 所有可能的值
const process = require('process');
var platform = process.platform;
switch (platform) {
  case 'aix':
    console.log('IBM AIX platform');
    break;
  case 'darwin':
    console.log('Darwin platform(MacOS, IOS etc)');
    break;
  case 'freebsd':
    console.log('FreeBSD Platform');
    break;
  case 'linux':
    console.log('Linux Platform');
    break;
  case 'openbsd':
    console.log('OpenBSD platform');
    break;
  case 'sunos':
    console.log('SunOS platform');
    break;
  case 'win32':
    console.log('windows platform');
    break;
  default:
    console.log('unknown platform');
}
 */

function spawn(command, args, options = {}) {
  const win32 = process.platform === 'win32';
  const cmd = win32 ? 'cmd' : command;
  const cmdArgs = win32 ? ['/c'].concat(command, args) : args;
  return cp.spawn(cmd, cmdArgs, options);
}

module.exports = exec;
下一篇
举报
领券