05-脚手架框架搭建(三)(前端架构师入门笔记)

#关键词#

  • Command
  • 事件循环
  • import-local

#Lerna Command执行过程#

1、上章代码示例看到yargs注册命令如下:

// index.js
cli()
    .command(listCmd)

// listCommand.js
"use strict";

const filterable = require("@lerna/filter-options");
const listable = require("@lerna/listable");

exports.command = "list";

exports.aliases = ["ls", "la", "ll"];

exports.describe = "List local packages";

exports.builder = yargs => {
  listable.options(yargs);

  return filterable(yargs);
};

exports.handler = function handler(argv) {
  return require(".")(argv);
};


2、其中command是命令全称,aliases是别名,describe是描述,builder是命令前置的相关options配置,最关键的是handler是命令的执行过程,深入解析可以看到:

module.exports = factory;

function factory(argv) {
  return new ListCommand(argv);
}

class ListCommand extends Command {
  get requiresGit() {
    return false;
  }

  initialize() {
    let chain = Promise.resolve();

    chain = chain.then(() => getFilteredPackages(this.packageGraph, this.execOpts, this.options));
    chain = chain.then(filteredPackages => {
      this.result = listable.format(filteredPackages, this.options);
    });

    return chain;
  }

  execute() {
    // piping to `wc -l` should not yield 1 when no packages matched
    if (this.result.text.length) {
      output(this.result.text);
    }

    this.logger.success(
      "found",
      "%d %s",
      this.result.count,
      this.result.count === 1 ? "package" : "packages"
    );
  }
}

handler文件里exports出来的是一个factory工厂,返回的是ListCommand的实例化对象,ListCommand类里没有constructor,因为继承了Command类,所以会执行到Command类的constructor里解读如下显示:

class Command {
  constructor(_argv) {
    log.pause();
    log.heading = "lerna";

    const argv = cloneDeep(_argv);
    log.silly("argv", argv);

    // "FooCommand" => "foo"
    this.name = this.constructor.name.replace(/Command$/, "").toLowerCase();
    // ......省略部分代码......
    // launch the command
    let runner = new Promise((resolve, reject) => {
      // run everything inside a Promise chain
      let chain = Promise.resolve();

      chain = chain.then(() => {
        this.project = new Project(argv.cwd);
      });
      chain = chain.then(() => this.configureEnvironment());
      chain = chain.then(() => this.configureOptions());
      chain = chain.then(() => this.configureProperties());
      chain = chain.then(() => this.configureLogging());
      chain = chain.then(() => this.runValidations());
      chain = chain.then(() => this.runPreparations());
      chain = chain.then(() => this.runCommand());

      chain.then(
        result => {
          warnIfHanging();

          resolve(result);
        },
        err => {
          if (err.pkg) {
            // Cleanly log specific package error details
            logPackageError(err, this.options.stream);
          } else if (err.name !== "ValidationError") {
            // npmlog does some funny stuff to the stack by default,
            // so pass it directly to avoid duplication.
            log.error("", cleanStack(err, this.constructor.name));
          }

          // ValidationError does not trigger a log dump, nor do external package errors
          if (err.name !== "ValidationError" && !err.pkg) {
            writeLogFile(this.project.rootPath);
          }

          warnIfHanging();

          // error code is handled by cli.fail()
          reject(err);
        }
      );
    });

    // passed via yargs context in tests, never actual CLI
    /* istanbul ignore else */
    if (argv.onResolved || argv.onRejected) {
      runner = runner.then(argv.onResolved, argv.onRejected);

      // when nested, never resolve inner with outer callbacks
      delete argv.onResolved; // eslint-disable-line no-param-reassign
      delete argv.onRejected; // eslint-disable-line no-param-reassign
    }
    // ......省略部分代码(常规逻辑)......
  }
  // ......省略部分代码......
  runCommand() {
    return Promise.resolve()
      .then(() => this.initialize())
      .then(proceed => {
        if (proceed !== false) {
          return this.execute();
        }
        // early exits set their own exitCode (if non-zero)
      });
  }

  initialize() {
    throw new ValidationError(this.name, "initialize() needs to be implemented.");
  }

  execute() {
    throw new ValidationError(this.name, "execute() needs to be implemented.");
  }
}

module.exports = Command;

其中runner跟我们命令真正执行过程息息相关,它定义了一个Promise对象,这个方法会立即执行,执行完以后进入内部定义了一个chain,也是一个Promise对象,这个Promise对象then里面的内容不会被马上执行,它会被依次加入到我们的微任务执行栈当中,由于在同一个Promise对象后面不停的调用then,微任务队列会形成一个排队,依次执行,这是为了在整个常规逻辑执行完之后,再开始执行这些代码逻辑,并且可以通过异步的方式来进行代码执行。

前面微任务里的操作主要做了些脚手架执行过程中的初始化环节,初始化环境变量,配置options、属性,验证工作,验证准备等,以及最核心的runCommand方法,runCommand里面呢又分为2部:initialize初始化和execute,如果子类ListCommand未实现这两个方法会报错,强制我们去实现这两个方法,最终命令的业务逻辑在这两个方法内进行实现。

#javascript事件循环#

  • 事件循环又称为event loop
  • task主要分为宏任务和微任务
  • javascript脚本执行过程中(如下代码示例):整个脚本指令首先会被加入到宏任务队列中进行依次执行,先打印出start,执行到setTimeout时会往我们宏任务队列里加入一个function,不会立即执行,会等到微任务队列执行完后进行执行,然后执行到new Promise对象,这Promise对象里面的function会进行立即执行,它会定义一个chain,chain里面会定义一个Promise对象,Promise对象调then()会立即执行,并且会往微任务队列里依次插入function,此时微任务队列里有console.log('chain1');console.log('chain2');console.log('chain3')三个方法,最后执行end,所以整个输出是start、end、然后执行微任务里的chain1、chain2、chain3,最后清空宏任务队列里的setTimeout,直到任务执行完毕。
handler:() => {
    console.log('start');
    setTimeout(() => {
        console.log('settimeout');
    })
    new Promise(() => {
        let chain = Promise.resolve();
        chain().then(() => console.log('chain1'));
        chain().then(() => console.log('chain2'));
        chain().then(() => console.log('chain3'));
    })
    console.log('end')
}

#import-local 执行流程深度解析#

#!/usr/bin/env node

"use strict";

/* eslint-disable import/no-dynamic-require, global-require */
const importLocal = require("import-local");

if (importLocal(__filename)) {
  require("npmlog").info("cli", "using local version of lerna");
} else {
  require(".")(process.argv.slice(2));
}

import-local库:当我们项目当中本地存在一个脚手架命令,同时全局在node当中也存在一个脚手架命令的时候,它优先选用我们node_modules当中的版本。

__filename:which lerna对应的代码文件链接到的地址,把filename传入到import库当中,如果返回的是true,它会优先使用我们本地的版本,会帮我们把本地版本加载进来,并打印一条log。

node执行过程中会在我们上下文注入一些变量包括module、require、exports、__filename、__dirname等

// import-local.js
'use strict';
const path = require('path');
const resolveCwd = require('resolve-cwd');
const pkgDir = require('pkg-dir');

module.exports = filename => {
	const globalDir = pkgDir.sync(path.dirname(filename));
	const relativePath = path.relative(globalDir, filename);
	const pkg = require(path.join(globalDir, 'package.json'));
	const localFile = resolveCwd.silent(path.join(pkg.name, relativePath));

	// Use `path.relative()` to detect local package installation,
	// because __filename's case is inconsistent on Windows
	// Can use `===` when targeting Node.js 8
	// See https://github.com/nodejs/node/issues/6624
	return localFile && path.relative(localFile, filename) !== '' ? require(localFile) : null;
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端-张冠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值