Skip to content
webpack 命令行与 Node API 用法
webpack 的构建可以通过命令行(CLI)和 Node API 两种方式启动。大多数场景下 CLI 即可满足需求;当需要程序化地控制构建流程(例如动态生成配置、集成到其他工具链、在构建前后执行自定义脚本)时,则可以使用 Node API。以下逐一介绍这两种方式,并梳理相关的配置选项和注意事项。
基本构建命令
项目安装了 webpack 和 webpack-cli 之后,最简单的执行方式是:
bash
npx webpack该命令会从当前目录查找 webpack.config.js,获取配置后执行一次全量构建。如果没有配置文件,webpack 5 会采用默认值(入口 src/index.js,输出 dist/main.js)。实际项目里通常需要手动指定参数。
常用命令选项
--config
当配置文件名称不是 webpack.config.js,或者不在项目根目录时,可以通过 --config 指明路径:
bash
npx webpack --config ./config/webpack.prod.js配置文件同样支持导出函数或数组,这一点在 Node API 部分表现得更直接。
--mode
--mode 用于设置构建模式,可选值为 development、production 或 none。它会决定默认启用的优化插件。例如 production 模式会开启代码压缩、作用域提升等;development 模式则更侧重建构速度和代码可读性。
bash
npx webpack --mode production如果配置文件里也设置了 mode,命令行参数会覆盖配置文件中的值——这个优先级规则对大部分 CLI 参数都适用。
--progress
默认的输出信息比较精简,添加 --progress 可以打印构建进度:
bash
npx webpack --progress进度输出会显示每个模块的构建百分比,在大型项目里能够看出卡在哪个 loader 或入口。在持续集成环境中,这些输出可能干扰日志,可以去掉该选项或显式使用 --no-progress。
--entry、--output-path
还有一些覆盖入口/出口的选项,不过通常不会在命令行中大量配置,而是在配置文件里管理,命令行只做少量切换。
构建输出信息解读
执行 npx webpack 后,终端会打印构建结果摘要。一份典型的输出如下:
asset bundle.js 143 KiB [emitted] [minimized] (name: main)
asset index.html 353 bytes [emitted]
runtime modules 1.02 KiB 5 modules
modules by path ./src/ 17.3 KiB
modules by path ./src/utils/*.js 4.82 KiB
./src/utils/helper.js 1.62 KiB [built] [code generated]
./src/utils/format.js 3.2 KiB [built] [code generated]
./src/index.js 1.95 KiB [built] [code generated]
./src/App.js 10.5 KiB [built] [code generated]
webpack 5.74.0 compiled successfully in 902 ms关键信息:
asset行显示生成的资源文件名、大小和标记。[emitted]表示文件已写入磁盘,[minimized]表示经过压缩。(name: main)对应入口的 chunk 名称,通常是 entry 对象的键。runtime modules是 webpack 运行时代码的大小与模块数量。modules部分按路径展示包含的模块,[built]表示参与了构建,[code generated]表示生成了最终代码。- 最后一行给出 webpack 版本和编译耗时。当构建失败或有警告时,这里会变为错误信息或黄色警告。
如需查看更详细的模块依赖关系或 chunk 拆分情况,可以在命令后添加 --stats verbose,不过日常开发中较少使用。更细粒度的信息也可以通过 Stats 对象程序化地获取(见后文)。
监听模式——watch
开发过程中每次修改代码都手动执行一次构建并不现实。webpack 提供了 watch 模式,在检测到文件变动后自动重新编译。
启用方式有两种:
- 在配置文件里设置
watch: true,然后执行npx webpack:
js
// webpack.config.js
module.exports = {
// ...
watch: true,
watchOptions: {
ignored: /node_modules/,
aggregateTimeout: 300,
poll: 1000 // 部分文件系统需要轮询
}
};- 通过命令行参数
--watch:
bash
npx webpack --watch两种方式效果相同:编译完成后进程不会退出,进入监听状态。修改源文件并保存后,webpack 会重新编译并在终端输出新的构建信息。
注意点:watch 模式只负责重新编译,不会刷新浏览器。实际开发中它更多地配合其他工具(例如编辑器插件、LiveReload)使用,或者作为自定义脚本的一部分。如果希望“保存后浏览器自动更新”,需要结合 devServer 和 HMR。
另外,如果文件位于网络挂载盘、Docker 数据卷等无法触发文件事件的文件系统,watch 可能失效。此时需要将 watchOptions.poll 设置为合适的毫秒数以开启轮询。轮询会占用较多 CPU 资源,在支持 inotify 的环境中应优先使用原生通知机制。
开发服务器与模块热替换
webpack-dev-server 是官方提供的开发服务器,它将 webpack 的构建结果保存在内存中(不写磁盘),并通过 HTTP 提供给浏览器。同时它可以搭配 HMR(Hot Module Replacement)实现模块级替换,而无需刷新整个页面。
启动服务器:
bash
npx webpack serve这条命令背后执行的是 webpack serve --config webpack.config.js,启动后会显示服务地址(默认 http://localhost:8080)。
devServer 的关键配置
js
// webpack.config.js
module.exports = {
// ...
devServer: {
static: './public', // 静态文件目录
hot: true, // 开启 HMR
port: 3000,
open: true
}
};static 项(旧版本名为 contentBase)指向存放静态资源的目录,这些资源不参与 webpack 构建,但开发服务器会直接将其提供给浏览器。
启用 HMR
设置了 devServer.hot: true 之后,webpack 5 会自动挂载 HotModuleReplacementPlugin。但仅此还不够:应用代码必须自愿接收更新。如果代码没有显式接收模块替换,HMR 将退化为整个页面重新加载(即 live reload)。
一个典型的 HMR 接收示例:
js
// 入口文件或某个模块中
if (module.hot) {
module.hot.accept('./someModule.js', () => {
// 当 someModule.js 发生变化时重新执行某些逻辑
console.log('Module updated');
});
// 也可以通过 dispose 清理旧的副作用
module.hot.dispose(() => {
// 清理定时器、解绑事件等
});
}React、Vue 等框架通过对应的 loader 或插件(如 react-refresh-webpack-plugin、vue-loader 自带的 HMR 支持)可以在组件层面实现局部更新,而不需要手动编写 module.hot.accept。
如果修改代码后整个页面重新加载且控制台没有错误信息,通常意味着 HMR 配置不完整,某个模块替换失败触发了整页刷新。检查控制台的 HMR 日志能够定位到出错的模块。
使用 Node API 调用 webpack
当需要在构建前后执行自定义逻辑,或者需要动态生成配置数组、与其他工具集成时,命令行方式的局限性就会显现。此时可以在脚本中将 webpack 作为库来使用。
compiler 实例与 run/watch 方法
webpack 函数接收一个配置对象(或配置数组),返回一个 compiler 实例。
js
const webpack = require('webpack');
const config = require('./webpack.config.js');
const compiler = webpack(config);compiler 是 webpack 的核心引擎,后续操作都通过它完成。
单次构建:compiler.run()
js
compiler.run((err, stats) => {
if (err) {
console.error(err);
return;
}
console.log(stats.toString({
colors: true,
chunks: false
}));
compiler.close(); // webpack 5 中建议在不需要时关闭编译资源
});run 接受一个回调,在一次构建完成后调用。回调提供两个参数:err 是编译过程中的严重错误(例如配置错误导致无法启动),stats 是包含构建信息的对象。即使没有致命错误,stats.hasErrors() 也可能返回 true(比如模块解析失败)。因此处理回调时要区分这两种情况:
js
compiler.run((err, stats) => {
if (err) {
// 配置级错误,webpack 无法启动
console.error('Fatal Error:', err);
return;
}
if (stats.hasErrors()) {
// 编译过程中的错误,如模块未找到
console.error(stats.toString('errors-only'));
return;
}
console.log(stats.toString({ colors: true }));
compiler.close();
});使用 Promise 风格
run 是回调式的,如果希望与 async/await 配合,可以使用 util.promisify:
js
const util = require('util');
const webpack = require('webpack');
const config = require('./webpack.config.js');
const compiler = webpack(config);
const run = util.promisify(compiler.run.bind(compiler));
(async () => {
try {
const stats = await run();
console.log(stats.toString({ colors: true }));
} catch (err) {
console.error('Build failed:', err);
} finally {
compiler.close();
}
})();注意:promisify 把 run 转为 Promise 时,如果回调中传递了错误(err 参数),Promise 会进入 rejected 状态。而 stats.hasErrors() 并不会触发 err,所以 catch 块只能捕获致命错误,编译错误仍需要在 stats 中处理。
持续监听:compiler.watch()
watch 方法返回一个 watching 实例,用于停止监听。
js
const watching = compiler.watch({
// watchOptions
aggregateTimeout: 300,
poll: undefined
}, (err, stats) => {
if (err) {
console.error(err);
return;
}
console.log(stats.toString({ colors: true }));
});停止监听时调用 watching.close(() => { console.log('Watching ended.'); });。长期运行的服务应当在进程关闭时(如 SIGINT 信号)主动调用 close 来释放资源,避免文件句柄泄漏。
在 Node API 中启动开发服务器与 HMR
通过 Node API 启动开发服务器通常有两种做法:直接使用 webpack-dev-server 的编程式 API,或者通过中间件将 webpack 嵌入到已有的 Node 服务中。
方式一:webpack-dev-server 编程式 API
webpack-dev-server v4 提供了 WebpackDevServer 类:
js
const Webpack = require('webpack');
const WebpackDevServer = require('webpack-dev-server');
const config = require('./webpack.config.js');
async function startDevServer() {
const compiler = Webpack(config);
const devServerOptions = {
hot: true,
static: './public',
port: 3000
};
const server = new WebpackDevServer(devServerOptions, compiler);
await server.start();
console.log('Dev server is running on port 3000');
}
startDevServer();这种方式与命令行 webpack serve 对配置的处理一致,但转为程序化控制。
方式二:webpack-dev-middleware + webpack-hot-middleware
该方法更灵活,适合将 webpack 构建嵌入已有的 Express / Koa 应用中。
js
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const config = require('./webpack.config.js');
const app = express();
// 为 HMR 注入两个入口,使客户端代码可以连接到热更新服务
config.entry.app = ['webpack-hot-middleware/client', config.entry.app];
const compiler = webpack(config);
app.use(webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath
}));
app.use(webpackHotMiddleware(compiler));
app.listen(3000, () => {
console.log('Server listening on port 3000');
});开发时修改代码,HMR 会触发模块替换。注意:该方案下需要自行引入 HotModuleReplacementPlugin(webpack 5 下 devMiddleware 不会自动添加),并确保客户端代码包含正确的 module.hot.accept 逻辑。
解读构建结果:Stats 对象
每次构建完成后,获得的 stats 对象包含了当前构建的大量信息——模块、chunk、资源、耗时、错误、警告等。前面示例中已经多次使用 stats.toString() 输出格式化文本。
stats.toString(options) 接受一个配置对象,控制输出哪些信息。常用选项:
colors: true– 带颜色输出errors: true– 显示错误warnings: true– 显示警告modules: true– 列出所有模块chunks: true– 显示 chunk 信息assets: true– 显示资源信息excludeModules: /node_modules/– 排除指定的模块
除文本输出外,还可以通过 stats.compilation 拿到更底层的编译对象。例如 stats.compilation.assets 是所有生成资源的 Map,stats.compilation.modules 是所有模块的集合。这些数据可用于生成自定义的构建报告。
一个仅输出错误和警告的示例:
js
compiler.run((err, stats) => {
if (err) { console.error(err); return; }
if (stats.hasErrors()) {
console.error(stats.toString({ errors: true, colors: true }));
}
if (stats.hasWarnings()) {
console.warn(stats.toString({ warnings: true, colors: true }));
}
compiler.close();
});在脚本中还可以根据 stats.hasErrors() 的返回值来决定进程退出码,以便持续集成系统识别构建失败。
CLI 与 Node API 的选择
对于标准的单次生产构建或简单的开发服务器,使用 npx webpack 与 npx webpack serve 已经足够。CLI 的启动开销可以忽略,配置集中在文件里,维护成本较低。
以下场景里 Node API 的优势会更加明显:
- 构建过程前后需要执行自定义准备或清理工作(如生成版本号、拷贝静态文件)
- 需要传入多个配置,并控制它们之间的执行顺序或并行策略
- 将 webpack 嵌入到其他工具链中(例如 Gulp、Grunt、自定义的部署脚本)
- 需要精细控制编译资源的释放(如服务端长期监听并定期构建)
Node API 在带来灵活性的同时,也要求开发者自行处理错误、关闭 compiler、管理 watch 实例的生命周期。忘记调用 compiler.close() 或 watching.close() 可能导致文件句柄未被释放,进而引发文件锁定或资源泄漏,在特定部署环境下尤为隐蔽。
