Webpack 入口文件
入口文件
无论是通过 npm scripts 运行 webpack:
- 开发环境:
npm run dev - 生产环境:
npm run build
还是通过 webpack 直接运行:
webpack entry.js bundle.js
在命令行运行了上述命令后,npm 会在命令行中进入 node_modules/bin 目录下去查找是否存在 webpack.sh 或者 webpack.cmd 文件,如果存在,就执行,不存在,就抛出错误。
我们在 node_modules 中我们可以找到 webpack 项目的 package.json 文件,可以发现有如下配置:
json
{
"name": "webpack",
"version": "4.41.5",
// ...
"main": "lib/webpack.js",
"web": "lib/webpack.web.js",
"bin": "./bin/webpack.js",
// ...
}
npm的bin字段作用:设置了之后 可执行文件 会被链接到当前项目的./node_modules/.bin中,在本项目中,就可以很方便地利用npm执行脚本,更多可参考 npm的package.json字段含义。
在函数中引入 webpack,那么入口将会是lib/webpack.js。而如果在 shell 中执行,那么将会走到 ./bin/webpack.js。
其实 webpack 实际的路口文件是 node_modules/webpack/bin/webpack.js。
入口文件 webpack.js 分析
我们分析一下 /bin/webpack.js,文件很简单,主要分为六步,如下图:



- 这一行代码正常执行返回
ini
process.exitCode = 0;
exitCode 代表错误码,0 的意思就是没有错误,当运行过程中有错误就会去修改相应的错误码。
- 运行某个命令
ini
const runCommand = (command, args) =>{...};
通过 Node.js 核心模块 child_process 去运行某个命令,类似执行 npm run webpack-cli 这样。
- 判断某个包是否安装
ini
const isInstalled = packageName =>{...};
这个方法是判断某个包是否安装
- 指定
webpack可用的CLI:
csharp
const CLIs = [
{
name: "webpack-cli",
package: "webpack-cli",
binName: "webpack-cli",
alias: "cli",
installed: isInstalled("webpack-cli"),
recommended: true,
url: "https://github.com/webpack/webpack-cli",
description: "The original webpack full-featured CLI."
},
{
name: "webpack-command",
package: "webpack-command",
binName: "webpack-command",
alias: "command",
installed: isInstalled("webpack-command"),
recommended: false,
url: "https://github.com/webpack-contrib/webpack-command",
description: "A lightweight, opinionated webpack CLI."
}
];
webpack-cli:webpack4.0之后将webpack-cli分离了出来,具有webpack所有的特性和功能。webpack-command:轻量级的webpack-cli
- 返回安装的
CLI的个数
ini
const installedClis = CLIs.filter(cli => cli.installed);
判断上面的两个 CLI 是否安装了,并返回安装的 CLI 的个数。
- 根据安装数量进行处理
ini
if (installedClis.length === 0){...}
else if(installedClis.length === 1){...}
else{...}.
- 当安装个数是 0 的时候,会提示我们需要至少安装一个
CLI,他会在命令行中给予一些选择,一步一步引导我们去安装CLI:
ini
// ...
// 定义错误提示信息
let notify =
"One CLI for webpack must be installed. These are recommended choices, delivered as separate packages:";
for (const item of CLIs) {
if (item.recommended) {
notify += `\n - ${item.name} (${item.url})\n ${item.description}`;
}
}
// ...
// 选择安装包工具
const isYarn = fs.existsSync(path.resolve(process.cwd(), "yarn.lock"));
const packageManager = isYarn ? "yarn" : "npm";
const installOptions = [isYarn ? "add" : "install", "-D"];
// ...
const question = `Do you want to install 'webpack-cli' (yes/no): `;
const questionInterface = readLine.createInterface({
input: process.stdin,
output: process.stderr
});
// 询问我们是否安装 webpack-cli
questionInterface.question(question, answer => {
// ...
// 运行 安装命令
runCommand(packageManager, installOptions.concat(packageName))
.then(() => {
require(packageName); //eslint-disable-line
})
.catch(error => {
console.error(error);
process.exitCode = 1;
});
});
- 当安装
CLI个数是 1 个的时候,webpack就会自动去执行这个CLI:
javascript
const path = require("path");
// 找到 package.json 文件夹
const pkgPath = require.resolve(`${installedClis[0].package}/package.json`);
// 引入 CLI
const pkg = require(pkgPath);
// 执行 bin 中的文件
// 以 webpack-cli 举例子 会执行 其文件中 ./bin/cli.js 文件
require(path.resolve(
path.dirname(pkgPath),
pkg.bin[installedClis[0].binName]
));
下面我们会具体讲一下 webpack-cli 具体做了一些什么事情。
- 当安装
CLI个数是 2 个的时候,会提示我们报错,说只能使用一个CLI,需要移除一个:

入口文件小结
在执行 ./bin/webpack.js 文件之后,webpack 最终会找到 webpack-cli(webpack-command)这个 npm 包,并且执行 CLI。
webpack-cli 做的事情
上面我们分析了当我们运行 webpack 的时候,其实会去运行 webpack-cli 这个 CLI,其实运行 webpack-cli,最终运行的就是其目录下的 ./bin/cli.js 那么 webpack-cli 是做什么的呢?
作用
webpack-cli 对配置文件(webpack.config.js)和命令行参数进行转换最终生成 webpack 构建所需要的配置选项参数,最终会根据配置参数实例化 webpack 对象,然后执行构建流程。
流程分析
首先在 webpack-cli 中引入了 yargs 工具,用于对命令行进行定制。
分析输入之后的命令行参数,对各个参数进行转换,组成编译配置项,最后通过引入 webpack 结合处理好的配置,进行编译和构建。
源码分析
NON_COMPILATION_CMD
有一些 webpack 命令不需要去实例化一个 webpack 去执行编译,在 cli.js 中定义了一个 NON_COMPILATION_CMD 字段,去处理这些不需要编译的命令:
ini
const { NON_COMPILATION_ARGS } = require("./utils/constants");
// ...
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);
}
我们可以从 ./utils/constants 文件中找到 webpack-cli 提供的不需要编译的命令:
arduino
const NON_COMPILATION_ARGS = [
"init", // 创建一份 webpack 配置文件
"migrate", // 进行 webpack 版本迁移
"serve", // 运行 webpack-serve
"generate-loader", // 生成 webpack loader 代码
"generate-plugin", // 生成 webpack plugin 代码
"info" // 返回与本地环境相关的一些信息
];
如果他属于不需要编译的命令,那么那就回去执行 ./utils/prompt-command 这个模块,处理这些命令,其实这个模块与我们之前讲道德 webpack.js 比较类似,他会定义一个 runCommand 方法,接着去判断这个命令是否存在,存在就通过 runWhenInstalled 方法去执行,不存在的话就提示我们去安装,如下图:

我们在命令行中输入 npx webpack init,他会提示我们安装 @webpack-cli/init:

工具包 yargs 介绍
动态生成帮助信息
yargs 提供了命令和分组参数,动态生成 help 帮助信息,如下所示:

这些命令行参数都写在了 config/config-yargs.js 中:
php
const { GROUPS } = require("../utils/constants");
const {
CONFIG_GROUP,
BASIC_GROUP,
MODULE_GROUP,
OUTPUT_GROUP,
ADVANCED_GROUP,
RESOLVE_GROUP,
OPTIMIZE_GROUP,
DISPLAY_GROUP
} = GROUPS;
// ...
module.exports = function(yargs) {
yargs
.help("help")
.alias("help", "h")
.version()
.alias("version", "v")
.options({
config: {
type: "string",
describe: "Path to the config file",
group: CONFIG_GROUP,
defaultDescription: "webpack.config.js or webpackfile.js",
requiresArg: true
},
// ...
})
}
在这里定义了所有的命令参数,并且将它们区分成了 8个组,加上 webpack 的通用参数,其实总共有九个组,我们可以查看 utils/constants.js
文件:
ini
// ...
const CONFIG_GROUP = "Config options:";
const BASIC_GROUP = "Basic options:";
const MODULE_GROUP = "Module options:";
const OUTPUT_GROUP = "Output options:";
const ADVANCED_GROUP = "Advanced options:";
const RESOLVE_GROUP = "Resolving options:";
const OPTIMIZE_GROUP = "Optimizing options:";
const DISPLAY_GROUP = "Stats options:";
// ...
const WEBPACK_OPTIONS_FLAG = "WEBPACK_OPTIONS";
// ...
具体参数的意思如下:
- Config options:配置相关参数(文件名称、运行环境等)
- Basic options:基础参数(
entry设置、debug模式设置、watch监听设置、devtool设置) - Module options:模块参数,给
loader设置扩展 - Output options:输出参数(输出路径、输出文件名称)
- Advanced options:高级用法(记录设置、缓存设置、监听频率、
bail等) - Resolving options:解析参数(
alias和 解析的文件后缀设置) - Optimizing options:优化参数
- Stats options:统计参数
- options:通用参数(帮助命令、版本信息等)
处理命令行参数
对于 yargs 的使用用法,笔者举几个简单例子,新建一个 yargs-demo,
bash
cd yargs-demo
npm init
npm install -D yargs
新建一个 index.js 文件:
ini
var argv = require('yargs').argv;
console.log('hello ', argv.name);
接着我们在命令行中输入:
lua
$ node index.js --name=tom
--> hello tom
$ node index.js --name tom
--> hello tom
之前我们可以通过 Node.js 的 process.argv 获取相应的命令行参数:

yargs 可以上面的结果改为一个对象,每个参数项就是一个键值对:
css
{ _: [], name: 'tom', '$0': 'index.js' }
如果将 argv.name 改成 argv.n,就可以使用一个字母的短参数形式了。
ruby
$ node index.js -n tom
输出:
css
{ _: [], n: 'tom', '$0': 'index.js' }
可以使用 alias 方法,指定 name 是 n 的别名,修改 index.js
javascript
var argv = require('yargs')
.alias('n', 'name')
.argv;
console.log('hello ', argv.n);
输出:
shell
$ node index.js -n tom
--> hello tom
$ node index.js --name tom
--> hello tom
argv 对象有一个下划线(_)属性,可以获取非连词线开头的参数:
css
$ node index.js A -n tom B C
输出:
css
{ _: [ 'A', 'B', 'C' ], n: 'tom', name: 'tom', '$0': 'index.js' }
笔者只是列举了几个比较简单的例子,更多用法大家可以参考:Node.js 命令行程序开发教程 和 yargs 的 api。
生成配置项参数
webpack-cli 通过 yargs 来处理生层 webpack 所需的配置参数:
scss
yargs.parse(process.argv.slice(2), (err, argv, output) => {
// arguments validation failed
if (err && output) {
console.error(output);
process.exitCode = 1;
return;
}
// help or version info
if (output) {
console.log(output);
return;
}
// ...
let options;
try {
options = require("./utils/convert-argv")(argv);
} catch (err) {
// ...
}
// 判断对象中是否存在相应对键值对
function ifArg(name, fn, init) {
// ...
}
// 最核心的方法
function processOptions(options) {
// ...
}
processOptions(options);
})
可以看到当 webpack 遇到错误或者输入 -help 和 version 的时候就直接返回不去处理配置参数了。
options 这个值就是代表要传给 webpack 的配置参数,它包含了两部分,一个是配置文件里面的配置,还有一个是命令行里面的配置参数,它会通过 ./utils/convert-argv 这个模块返回最终处理好的配置参数。
最后将配置文件传入到 processOptions 这个函数中,启动 webpack,同时在函数中还会去定义一个 outputOptions 这个是用于在 webpack 编译后输出出来的配置项,具体可以看在 compilerCallback 中的使用。
scss
function processOptions(options) {
// ...
const webpack = require("webpack");
let compiler;
try {
// 实例化 webpack 对象
compiler = webpack(options);
} catch (err) {
// ...
}
// 回调函数
function compilerCallback(err, stats) {
// ...
}
// 如果是 watch 启动 webpack,使用 compiler.watch
if (firstOptions.watch || options.watch) {
// ...
compiler.watch(watchOptions, compilerCallback);
} else {
// 正常启动,使用 compiler.run
compiler.run((err, stats) => {
if (compiler.close) {
compiler.close(err2 => {
compilerCallback(err || err2, stats);
});
} else {
compilerCallback(err, stats);
}
});
}
}
小结
笔者只是大致讲了一下流程,对于具体 webpack-cli 是怎么讲配置文件和命令行参数转化为具体的 webpack 所需的配置,这个大家有兴趣可以自己去研究一下,其实说到底还是使用 yargs 这个神器来解决的。
相关链接
示例代码
示例代码可以看这里,具体是在 node_modules 中 webpack 文件:
文档地址
文档地址:webpack-doc 学习文档
文档地址
文档地址:webpack-doc 学习文档