把一个工程里的 JavaScript、CSS、图片、字体混在一起打包成浏览器能直接运行的静态资源,就是 webpack 在做的事。它本质是一颗静态模块打包器:从入口文件出发,递归解析所有依赖,构建出一张完整的依赖图,再把这张图按规则拆合压缩后输出到磁盘。前端工程化里的代码转换、按需加载、长期缓存、Tree Shaking 等能力,几乎都靠它落地。
一、webpack 解决的三个核心问题
早期前端页面是几个 jQuery 文件手动拼接,后来业务复杂到需要模块化、按需引入、自动化构建,浏览器却只认识 HTML、CSS、JavaScript 三种文件。webpack 正好填补这道缝:
- 统一资源口径:把 JS、CSS、图片、字体都视为”模块”,让所有资源走同一条打包流水线;
- 处理浏览器兼容:把 ES6+、TypeScript、SCSS 通过 Loader 转译成浏览器能跑的旧语法;
- 产物优化:按需分割代码、压缩、Tree Shaking、长期缓存,把首屏体积压到最小。
如果只用 script 标签引文件,无法做依赖分析、无法做代码分割、无法做生产压缩;用了 webpack,这些能力开箱即用。
二、五个核心概念
| 概念 | 作用 | 典型配置 |
|---|---|---|
| Entry | 依赖图的起点 | entry: './src/index.js' |
| Output | 产物输出位置与命名规则 | output.filename: 'js/[name].[contenthash:8].js' |
| Loader | 把非 JS 资源转成模块 | babel-loader、sass-loader |
| Plugin | 扩展构建生命周期 | HtmlWebpackPlugin、MiniCssExtractPlugin |
| Mode | 决定内置优化策略 | development / production |
这五项是 webpack 配置文件的骨架。理解它们各自管什么,遇到构建问题时就知道该改哪一段。
三、Loader 与 Plugin 的区别
这是面试高频题,也是理解 webpack 内部机制的关键。
| 维度 | Loader | Plugin |
|---|---|---|
| 作用 | 转换单个文件,让 webpack”认识”非 JS 资源 | 在整个构建流程的钩子上做扩展 |
| 运行时机 | 模块解析阶段,对源码做转译 | 贯穿编译到产物输出的全流程 |
| 写法 | 函数,接收源码返回转译结果 | 类,通过 apply 方法挂载到 compiler |
| 典型代表 | babel-loader、css-loader、sass-loader | HtmlWebpackPlugin、TerserPlugin、DefinePlugin |
Loader 像流水线上的工人,每个人只负责一道工序:把 SCSS 编成 CSS,再把 CSS 编成 JS 能 import 的模块。Plugin 像流水线上的质检和包装工,能在任意环节插一脚:HtmlWebpackPlugin 在产物输出前自动生成 HTML 引用,TerserPlugin 在产物生成后做压缩。
四、构建流程的生命周期
从执行 webpack 命令到产物落盘,内部走完了一整条流水线:
- 读取配置文件与命令行参数,初始化 Compiler;
- 从 entry 出发,递归解析 import / require,建立依赖图;
- 对每个模块按 module.rules 配置调用 Loader 链(从右到左执行);
- 把所有模块合并成 Chunk(按 SplitChunks 规则切分);
- 在 Compilation 生命周期钩子上触发 Plugin;
- 生成 Assets(最终产物),写出到 output.path。
理解生命周期的好处是:能定位”为什么我的自定义插件没生效”或”为什么某个 Loader 没被调用”——是钩子没对上,还是 rule 匹配模式没写对。
五、webpack 5 的关键能力
webpack 5 是当前主流版本,相比 4 有几项显著改进:
- 内置 Asset Modules:用
type: 'asset'替代旧的 file-loader / url-loader,按文件大小自动决定输出为独立文件还是 base64 内联; - 持久化缓存:默认开启
cache: { type: 'filesystem' },二次构建速度大幅提升; - 强化 Tree Shaking:能追踪嵌套模块的副作用,配合
sideEffects: false删除未引用的导出; - 模块联邦:原生支持跨应用共享模块,是微前端的官方方案;
- 移除 Node.js polyfill:减少前端产物体积,倒逼业务按需引入 polyfill。
六、典型配置文件示例
下面是一份最小可运行的 webpack 5 配置,覆盖 JS 转译、SCSS 处理、图片资源、开发服务器四个常见场景:
// webpack.config.js
const path = require('node:path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].[contenthash:8].js',
clean: true,
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader',
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
{
test: /\.(png|jpg|gif|svg)$/,
type: 'asset',
parser: { dataUrlCondition: { maxSize: 8 * 1024 } },
},
],
},
plugins: [
new HtmlWebpackPlugin({ template: './public/index.html' }),
],
devServer: {
static: path.resolve(__dirname, 'dist'),
port: 3000,
hot: true,
},
}
实际项目通常把配置拆成 common / dev / prod 三份,再用 webpack-merge 合并。Loader 链顺序按”从右到左”读:SCSS 文件先经 sass-loader 转 CSS,再经 css-loader 解析为 JS 模块,最后由 style-loader 注入到页面的 style 标签里。
七、Loader 链的执行顺序
webpack 匹配到文件后,Loader 按数组顺序从右往左、从下往上逐个执行,前一个 Loader 的输出是后一个的输入。这条规则常被新手忽略,导致 SCSS 里的 @import 解析不到。
处理 .scss 文件的典型链:
sass-loader (把 SCSS 编译成 CSS)
↓
postcss-loader (自动加浏览器前缀)
↓
css-loader (解析 CSS 中的 @import 和 url())
↓
style-loader (把 CSS 注入到页面的 <style> 标签)
要临时调整顺序,可以用 enforce: 'pre' 或 enforce: 'post',比如把 eslint-loader 放到 pre 位置提前做源码校验。
八、生产环境必做的四件事
开发完代码后,生产构建还要做几件事把产物压到最小、缓存最稳:
- 把 mode 切到
production,自动开启 Terser 压缩; - 用
contenthash命名文件,配合runtimeChunk: 'single'拿到稳定的长期缓存; - 用
SplitChunksPlugin把第三方依赖、业务代码、运行时拆成独立 chunk; - 用
MiniCssExtractPlugin把 CSS 抽成独立文件,避开 style-loader 的运行时注入开销。
webpack 不是唯一选择,Vite、Rollup、esbuild 在不同场景下表现更好。但在中大型 React / Vue 项目里,webpack 的生态成熟度、插件覆盖度仍然是首选,熟悉它的配置和原理能省下大量排错时间。
常见问题(FAQ)
Q1:webpack 和 Vite 选哪个?
新项目、追求开发体验优先选 Vite;中大型存量项目、对插件生态依赖重的继续用 webpack。
Q2:Loader 和 Plugin 能互相替代吗?
不能。Loader 处理”单个文件怎么转”,Plugin 处理”整个构建流程做什么”,分工不同。
Q3:build 后 dist 文件很大怎么办?
先开 production 模式,再加 SplitChunks 拆包、用 contenthash 开缓存、确认 Tree Shaking 是否被 sideEffects 配置关闭。