新年新气象,2020 开篇之作
前置知识点
process.exitCode
Node API 文档中明确写到,process.exit() 方法以退出状态 code 指示 Node.js 同步地终止进程。 如果省略 code,则使用成功代码 0 或 process.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设置别名,比如指定name是n的别名。#!/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设置完成后,当你打印帮助信息时。

path.relative(from, to)
path.relative() 方法根据当前工作目录返回 from 到 to 的相对路径。 如果 from 和 to 各自解析到相同的路径(分别调用 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.exitCode、runCommand、CLI 数组 和 判断某 CLI 是否安装。
process.exitCode
process.exitCode 为 0,代表程序正常运行;非 0,则代表程序报错。
runCommand
此函数会启动一个子进程,并执行输入命令。
CLI 数组
定义了一个 CLI 数组,数组共有 2 项,分别为 webpack-cli 和 webpack-command。
webpack-cli
webpack-cli 包含了所有的 webpack 特性,一般推荐使用这个包
webpack-command
webpack-command 只是一个轻量的 webpack 包。
判断某 CLI 是否安装
-
如果两个
CLI均未安装,则会提示必须安装一个CLI。当你输入yes并回车后,会通过上文定义的runCommand函数进行安装。当然,webpack会判断当前应该使用npm,还是使用yarn。安装完成后,并reuqire进来。
-
如果只安装一个,程序正常执行。这里以
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会提示需要删除其中一个,才可以正常运行。
总结

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 数组中。

./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 个分组。

继续往下看,我们调用yargs.parse 方法,处理命令行参数。第一个参数传入已去除 process.argv 数组前两项,也就是传入所有命令行参数,第二个参数为一个回调函数。
yargs.parse(process.argv.slice(2), (err, argv, output) => {})
定义变量 options,值为通过 requireConfig 方法取出配置文件内容,再经过 processOptions 方法把命令行参数一一赋值给 options,所以说此变量存储了所有 webpack 配置参数。
把 options 通过调用 webpack(options) 生成 compiler。如果命令行参数传入的是 watch,则调用 compiler.watch,否则调用 compiler.run。
总结

参考链接
最后
笔者建议按照顺序食用,效果更佳哦。如果本文对您有所帮助,还请不要吝啬您的点赞,每一个点赞都是对笔者最大的鼓励。