1

前置知识点

process.exitCode

Node API 文档中明确写到,process.exit() 方法以退出状态 code 指示 Node.js 同步地终止进程。 如果省略 code,则使用成功代码 0process.exitCode 的值(如果已设置)退出。

require.resolve

用于从模块名取到绝对路径。

readline

readline 模块提供了一个接口,用于一次一行地读取可读流(例如 process.stdin)中的数据。

const readline = require('readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

rl.question('你如何看待 Node.js 中文网?', (answer) => {
  // TODO:将答案记录在数据库中。
  console.log(`感谢您的宝贵意见:${answer}`);

  rl.close();
});

import-local

打开  import-local index.js 文件,代码量十分的少。

// 当前文件所在项目的跟目录,根据 package.json 来判断项目的根目录所在位置
const globalDir = pkgDir.sync(path.dirname(filename));
// 返回 filename 相对于 globalDir 的路径
const relativePath = path.relative(globalDir, filename);
const pkg = require(path.join(globalDir, 'package.json'));
// 找到本地模块路径
const localFile = resolveCwd.silent(path.join(pkg.name, relativePath));
// 如果本地模块路径与 filename 不一致,则使用本地模块
return localFile && path.relative(localFile, filename) !== '' ? require(localFile) : null;

process.argv

process.argv 属性返回一个数组,其中包含当启动 Node.js 进程时传入的命令行参数。第一个参数为 process.execPath 启动 Node.js 进行的可执行文件的绝对路径名,第二个参数为正在执行的 JavaScript 文件的路径,剩余元素是任何其他命令行参数。

process.cwd

process.cwd() 方法返回 Node.js 进程的当前工作目录。

yargs

yargs 解决如何处理命令行参数,其提供 argv 对象,用来读取命令行参数。

#!/usr/bin/env node
var argv = require('yargs').argv;

console.log('hello ', argv.name);

使用时,下面两种用法都可以。

$ hello --name=tom
hello tom

$ hello --name tom
hello tom

涉及 API

  • usage
    设置命令的提示的使用方法。
  • help
    设置帮助信息,添加 --help,但是没有 -h,需要手动添加。
  • alias
    设置别名,比如指定 namen 的别名。

    #!/usr/bin/env node
    var argv = require('yargs')
      .alias('h', 'help')
      .argv;
  • version
    添加版本显示参数 --version,不过不添加缩写参数。
  • options
    批量设置参数。

    require('yargs')
      .options({
        'run': {
          alias: 'r', // 别名
          describe: 'run your program', // 描述
          demandOption: true // 是否必填
        },
        'path': {
          alias: 'p',
          describe: 'provide a path to file',
          demandOption: true
        }
      })
      .help()
      .argv

    设置完成后,当你打印帮助信息时。

    DraggedImage.png

path.relative(from, to)

path.relative() 方法根据当前工作目录返回 fromto 的相对路径。 如果 fromto 各自解析到相同的路径(分别调用 path.resolve() 之后),则返回零长度的字符串。

path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb');
// 返回: '../../impl/bbb'

入口文件

大家可能并不陌生,当我们在执行 webpack XX,会去本地项目文件的 node_modeles/bin 文件下查找 webpack 命令。所以,首先打开 node_modeles/bin 下的 webpack 文件。
入口文件由 4 个部分组成,分别为 process.exitCoderunCommandCLI 数组判断某 CLI 是否安装

process.exitCode

process.exitCode 为 0,代表程序正常运行;非 0,则代表程序报错。

runCommand

此函数会启动一个子进程,并执行输入命令。

CLI 数组

定义了一个 CLI 数组,数组共有 2 项,分别为 webpack-cliwebpack-command

  • webpack-cli

webpack-cli 包含了所有的 webpack 特性,一般推荐使用这个包

  • webpack-command

webpack-command 只是一个轻量的 webpack 包。

判断某 CLI 是否安装

  • 如果两个 CLI 均未安装,则会提示必须安装一个 CLI。当你输入 yes 并回车后,会通过上文定义的 runCommand 函数进行安装。当然,webpack 会判断当前应该使用 npm,还是使用 yarn。安装完成后,并 reuqire 进来。

    DraggedImage-1.png

  • 如果只安装一个,程序正常执行。这里以 webpack-cli 举例。

    首先通过 require.resolve 取到 webpack-cli 存放的绝对路径,比如取到的路径为 /Users/XXX/Works/Demo/node_modules/webpack-cli/package.json,并把包 require 进来。然后根据之前取到的 webpack-cli 路径执行到 webpack-cli/bin/cli.js 文件,文件内具体逻辑,此处不作讲解。

    const path = require("path");
    const pkgPath = require.resolve(`${installedClis[0].package}/package.json`);
    const pkg = require(pkgPath);
    require(path.resolve(
        path.dirname(pkgPath),
        pkg.bin[installedClis[0].binName]
    ));
  • 如果两个都安装,webpack 会提示需要删除其中一个,才可以正常运行。

总结

DraggedImage-2.png

Webpack-cli

打开 node_modules/webpack-cli/bin/cli.js 文件,我们首先看到下面这个代码片段。webpack 并不会编译执行所有的命令,它会针对不需要编译的 cmd,执行 ./utils/prompt-command 文件方法。

const NON_COMPILATION_CMD = process.argv.find(arg => {
     if (arg === 'serve') {
        global.process.argv = global.process.argv.filter(a => a !== 'serve');
        process.argv = global.process.argv;
    }
    return NON_COMPILATION_ARGS.find(a => a === arg);
});
if (NON_COMPILATION_CMD) {
    return require('./utils/prompt-command')(NON_COMPILATION_CMD, ...process.argv);
}

不需要编译的 CMD 列表位于文件 ./utils/constants` 下 `NON_COMPILATION_ARGS 数组中。

DraggedImage-3.png

./utils/prompt-command 文件往外暴露一个方法 promptForInstallation。方法内部首先会去查询当前项目是否安装 @webpack-cli/ + 外界传入的 NON_COMPILATION_CMD,如果未安装,便提示安装,安装后执行 runWhenInstalled;已安装,则执行 runWhenInstalled

回到 cli.js 文件中,我们可以继续往下看。代码中引入 yargs,并定义了帮助信息,并把大部分帮助信息放在 ./config/config-yargs 文件中。进入 ./config/config-yargs 文件,不知大家是否留意到,每一项都设置了 group 分组,group 的值都来自 ../utils/constants 文件。webpack 共有 8 个分组的帮助信息,加上默认的分组,总共有 9 个分组。

DraggedImage-4.png

继续往下看,我们调用yargs.parse 方法,处理命令行参数。第一个参数传入已去除 process.argv 数组前两项,也就是传入所有命令行参数,第二个参数为一个回调函数。

yargs.parse(process.argv.slice(2), (err, argv, output) => {})

定义变量 options,此变量存储了把所有命令转换成 webpack 配置参数的值。把经过处理的 options,通过调用 webpack(options) 生成 compiler。如果命令行参数传入的是 watch,则调用 compiler.watch,否则调用 compiler.run

总结

DraggedImage-5.png

参考链接


时间小鱼
127 声望5 粉丝

成长的路上充满坎坷,要么跨过去,要么等死!