前置知识点
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
,此变量存储了把所有命令转换成 webpack
配置参数的值。把经过处理的 options
,通过调用 webpack(options)
生成 compiler
。如果命令行参数传入的是 watch
,则调用 compiler.watch
,否则调用 compiler.run
。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。