Webpack 核心概念
2025-06-14 · 18min前言
要深入理解 Webpack 就需要先理解其核心概念跟原理,主要记些核心概念的个人理解和原理,概念描述看 Webpack 文档 就好了。
前置概念
文档中虽有列举了核心概念的目录,但当中的描述存在部分没有描述的词汇,似乎应该是默认看文档的人就肯定已经提前懂的这个词汇的概念了,但翻译成中文我也还是难以理解这种命名...
module
这个不难翻译,就是模块的意思,但其实真正的意思就是指项目的源文件以及依赖!包括源代码文件、图片字体等资源,只要是任何能够被 import / require / loader 处理的东西,都叫做 module!!!
按理来说这个是最好理解的,但是跟后面的 chunk 在一些教程里也被翻译成了模块!真的很让人误解。
chunk
翻译过来也是块的意思,但指代码块,本质含义是指由 Webpack 内部处理依赖关系得到由 module 组成的中间产物。
简单来说,就是
bundle
直接翻译过来就是捆的意思,但其实应该想表达打包的意思。
本质含义其实是指 chunk 经过处理后的磁盘文件,既打包后的最终产物。
内容组成来说:
- JS bundle:包含编译后的代码+运行时逻辑。
- CSS bundle:
mini-css-extract-plugin会从 JS chunk 中抽离出 CSS 文件。 - Asset bundle:图片/字体等资源,由
file-loader等处理。
总结
简单来说:
- module 就是项目中你写的代码、第三方库、图片字体等资源。
- chunk 是 module 经过 loader 处理分组后,得到的中间产物。
- bundle 是 chunk 经过优化压缩等一系列最终处理后输出的最终打包产物。
核心概念&原理
入口(entry)
构建依赖图的起点,Webpack 会以此作为根节点递归解析所有依赖。
核心机制
- 单入口:生成单一的 chunk,所有模块合并成一个 bundle。
- 多入口:根据对象语法(例如
{app: "./app.js", admin: "./admin.js"})生成独立的 chunk - 依赖解析:读取到的代码文本通过词法分析、语法分析,输出 Tokens,组成 AST 分析
import/require语句,来逐个构建模块依赖图。依赖解析的过程其实就跟 Markdown 渲染的解析过程一样,只不过输入不是 Markdown 文本,而是我们的代码文本,要输出的格式也不一样。
输出(output)
控制 bundle 生成位置、命名以及资源加载路径。
核心配置
- filename:
[name]:基于入口 chunk 名称动态命名,如app.js。[hash]:文件内容哈希值,用于确保缓存更新。
- path:bundle 输出的绝对路径,如
dist/。 - publicPath:资源引用的前缀,如
assets/,也可以换成 CDN 地址,影响 HTML 中资源路径。 - chunkFilename:非入口 chunk(如动态导入模块)的命名规则
Loader
官方文档概念描述是将 JS 和 JSON 以外的 CSS/图片等资源转换为 Webpack 可以处理的 JS 模块。
但这个说法不严谨,比如 babel-loader 是用于将 ES6+ 语法的 JS 转换为 ES5 语法的 Loader。
所以准确来说,Loader 是一个文件处理器,本质上是一个**接受一个源文件内容,返回转换后内容的 ** 函数。
工作流程
- 文件匹配:通过 Webpack 配置文件中的
test规则匹配文件。 - Loader 排序:按照
use数组倒序排序。 - 内容读取:读取文件内容,一般为字符串或者 Buffer。
- Loader 链执行:将内容一次传递给排序好后的 Loader 处理。如
use:['style-loader', 'css-loader']会先执行css-loader。 - JS模块生成:最终输出成 Webpack 可识别的 JS 模块。如
export default 'image.png'。
配置
Webpack 在配置文件中,通过 module[rules] 设置不同类型的文件,用相应的 Loader 进行处理,例如:
/\.(js|mijs|jsx|ts|tsx)$/使用babel-loader处理。/\.css$/使用css-loader处理。/\.(scss|sass)$/使用sass-loader处理。/\.less$/使用less-loader处理。[/\.bmg$/, /\.gif$/, /\.jpe?g$/, /\.png$/]使用url-loader处理。
一般来说,rules里的每个对象包含 test、include、exclude、use 等字段,use 字段则是接受一个对象数组,里面包含了需要使用的 Loader 和各自的 options。
完整 Webpack 配置举例:
// webpack.config.js
const path = require("path");
const buildPath = path.resolve(__dirname, "dist");
const srcPath = path.resolve(__dirname, "src")
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
path: buildPath,
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
include: srcPath,
exclude: [/node_modules/],
use: [
{
loader: require.resolve("babel-loader"),
options: {
presets: [require.resolve("@babel/preset-react")]
}
}
]
}
]
}
}
// src/index.js
import React from "react";
import { createRoot } from "react-dom/client";
const App = () => {
return <h1>Hello this is React</h1>;
};
createRoot(document.getElementById("root")).render(<App />);
运行 webpack 命令后会生成 dist/bundle.js,将它放到一个 HTML 中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
<script src="bundle.js"></script>
</body>
</html>
运行效果:

这样就打包好了一个非常简单的 React 项目。
当然完整的 React 项目肯定不是这么简单就打包完成的,还需要将其他非 JS 模块以及优化需要处理,有的可以找到第三方 loader 处理,如果没有自己满意还可以自己开发自定义 loader。
插件(Plugin)
插件通过事件钩子干预构建流程,实现优化、资源生成等高级功能。
与 Loader 不同,Loader 是针对单个文件进行转换的,Plugin 则是涉及到了整个 Webpack 的编译流程,对流程进行干预。
在正常开发项目中,打包生成的文件名带有哈希值的,我们不可能每次都手动自己去修改的,但可以通过 Plugin,自动将生成的 bundle 引入到 HTML 文件。
以 create-react-app 的 Webpack Plugin 为例:
{
//...
plugins: [
new HtmlWebpackPlugin(
Object.assign(
{},
{
inject: true,
template: paths.appHtml,
},
isEnvProduction
? {
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true,
},
}
: undefined
)
),
]
}
通过 html-webpack-plugin 自动将生成 bundle 引入到 HTML 文件中,这个插件可以说是 Webpack 构建到前端项目中必备插件。
除此之外常用的插件还有:
webpack.DefinePlugin:常量注入插件,用于注入环境变量等。mini-css-extract-plugin:把 CSS 代码拆分到独立的文件中,需要搭配配套的 Loader使用,一般在打包时使用,实现 CSS 按需加载。
Webpack 为插件提供了有丰富功能的上下文对象(this),通过 Webpack 自研的 Tapable 事件流提供了9种钩子来让插件进行事件注册,但这个要讲也是非常多....
开发服务器(DevServer)
之前讲过构建工具主要功能之一就是提供开发服务器了,Webpack 通过 webpack-dev-server 来实现此功能。
配置非常多,也非常复杂, create-react-app 甚至专门新起了一个文件来导出 devServer 的配置,要把所有配置都记完不显示,主要记下比较核心常用的就好了:
static
该配置项允许配置从目录提供静态文件的选项,主要两个配置:
directory:目录地址,默认publicwatch:是否监听directory目录下的文件更改,默认开启。
compress
启用 GZIP Compression。
hot
热更新开启,相信也没人会去关。
但是 Webpack 的热更新效果会触发页面刷新,热更新的效果应该不触发页面刷新的...
在 create-react-app 中也是通过 react-refresh 和
@pmmmwh/react-refresh-webpack-plugin 来实现 React 的热更新的。
优化(Optimization)
就是优化配置了,配置多的也是离谱,完全记住全部根本不可能吧!我觉得正常来说只需要记住几个比较关键的配置项即可,真有不知道再去查文档好了,开发不是靠死记硬背的。
splitchunks
代码分割/拆包,目的是为了将公共模块或者符合指定条件的模块分到单独的 chunk 中,避免重复打包,提高打包速度,打包产物后可以利用浏览器缓存没改变的第三方库,优化首屏和后续页面的加载速度,是大型项目性能优化的核心配置。
配置项:
chunks:选择哪些 chunk 进行优化。有效值是all: 符合条件的全都拆分,也是最常用的,一般来说只会用这个。async:只拆分import()这种方式加载的模块成为一个单独 chunk,拆分效果就跟 React 和 Vue 的懒加载拆分时一样。initial:只拆分同步处理的模块。- 自定义 Function:如果不使用
all,可以传递一个函数对每一个 chunk 进行判断,需要返回一个布尔值。
miniSize:生成 chunk 的最小体积。小于该体积的模块不会被拆分出去,默认2000(20KB)。minchunks:模块至少被多少个 chunk 引用时才被提取为公共 chunk,默认1。maxAsyncRequest:按需加载时(异步 chunk)最大并行请求数,默认30,用于限制拆分过多导致大量请求。maxInitialRequests:入口点最大并行请求数,也是默认是30,同样用于限制入口加载时大量请求。automaticNameDelimiter:生成 chunk 名称时使用的分隔符,默认~。name: 默认为true,让 Webpack 基于cacheGroups和 chunks 自动生成名称,可以字节设置定义函数返回名称,一般来说也没人会设置false。cacheGroup:splitchunks核心,定义分组规则来提取模块:optimization: { splitchunks: { cacheGroups: { // 提取 node_modules 中的第三方库 vendors: { test: /[\\/]node_modules[\\/]/, // 匹配 node_modules 目录下的模块 name: 'vendors', // 提取出来的 chunk 名称 (可选,默认根据 chunks/module 自动生成) chunks: 'all', // 推荐 'all' priority: 10, // 优先级,防止被其他规则覆盖。数字越大优先级越高 // reuseExistingchunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则重用该模块 }, // 提取公共模块 (被多个入口/异步 chunk 引用的业务代码) common: { minchunks: 2, // 至少被 2 个入口/chunk 引用 name: 'common', chunks: 'all', priority: 5, // 优先级低于 vendors minSize: 0, // 有时公共模块可能较小,可以适当降低 minSize 或设为 0 }, // 按特定库拆分 (例如单独拆分 React, ReactDOM, Lodash) react: { test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/, name: 'react-vendor', chunks: 'all', priority: 20, // 比 vendors 优先级高,确保单独拆分出来 }, } } }
常用的拆分规则:- 将
node_modules提取到单独的 Vendors chunk 中,可以长效缓存。 - 提取公共业务代码到 Common chunk 。
- 将超大且不常变动的库单独拆分,例如
react、Echarts等。 - 结合
import()异步加载。
- 将
Minimize & Minimizer
minimize 开启代码压缩,默认开启。
minimizer: 代码压缩配置,通常是一个数组,配置具体的压缩插件实例,需要覆盖默认值,并自定义选项:
// 压缩 JS (Webpack 5+ 内置,但需显式引入配置)
const TerserPlugin = require('terser-webpack-plugin');
// 压缩 CSS
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
optimization: {
minimizer: [
// 压缩 JS (覆盖默认的 terser 配置)
new TerserPlugin({
parallel: true, // 使用多进程并行运行以提高构建速度(强烈推荐!)
terserOptions: {
compress: {
drop_console: true, // 生产环境移除 console.* (谨慎使用!调试时要去掉)
// ... 其他 terser 压缩选项
},
format: {
comments: false, // 移除注释
},
// ... 其他 terser 选项
},
extractComments: false, // 不将注释提取到单独的文件
}),
// 压缩 CSS (使用 cssnano 或其他引擎)
new CssMinimizerPlugin({
parallel: true, // 并行压缩
minimizerOptions: {
preset: [
'default',
{
discardComments: { removeAll: true }, // 移除所有注释
// ... 其他 cssnano 选项
},
],
},
}),
],
}
需要注意:
minimizer会覆盖 Webpack 默认压缩配置,所以必须显式添加terser-webpack-plugin和CssMinimizerPlugin才能压缩 JS 和 CSS。parallel: true极大提升压缩速度。drop_console等选项需谨慎,确保线上环境不需要日志。
runtimechunk
提取运行时代码,将 Webpack 的运行时代码(管理模块加载以及交互代码) 提取到一个单独且体积小的 chunk 中,提取后仅当运行时代码变化时,不会导致业务代码或第三方库的 Vendors / chunk 的哈希发生变化,从而利用浏览器可以长效缓存 Vendors / chunk,还能避免运行时代码重复注入多个入口 chunk。
optimization: {
runtimechunk: {
name: (entrypoint) => `runtime-${entrypoint.name}`, // 推荐:为每个入口生成单独的 runtime (更细粒度缓存)
// 或简写为:
// runtimechunk: 'single' // 所有入口共享一个 runtime chunk (简单应用)
}
}
moduleIds & chunkIds
控制 Webpack 如何给 module和 chunk 生成唯一的 ID,不同的 ID 生成策略会影响模块增删时,未变动模块的 ID 是否稳定,从而影响长效缓存。
optimization: {
// 生产环境推荐配置 (Webpack 4+)
moduleIds: 'deterministic', // 或 'hashed' (Webpack 4)。deterministic 是 Webpack 5 的默认值,提供稳定哈希。
chunkIds: 'deterministic', // 同上。
// 开发环境推荐
// moduleIds: 'named', // 使用易读的名称 (便于调试)
// chunkIds: 'named', // 同上
}
usedExports / sideEffects (Tree Shaking)
Tree Shaking 的配置,生效有前提条件:
- 只对 ES6 语法有效,Common JS 和 AMD 模块无法有效进行 Tree Shaking。
- 需要在项目中的
package.json中设置sideEffects: false或者指定有副作用的文件数组,例如[".css", "*.global.js"]。 - 确保 Babel 等编译器没有将 ES6 语法的模块转换成 Common JS,需要检查 Babel 配置。
- 需要使用同样支持 Tree Shaking 的第三方库,提供 ES Module 版本并且在其他
package.json正确配置好sideEffects。
配置项:
usedExports:设置为true时(Webpack 4+ 生产环境时默认开启),会比较哪些模块被导出使用了。sideEffects:设置为true时(Webpack 4+ 生产环境时默认开启),会根据package.json中的sideEffects属性移除整个为使用的模块或文件。
concatenateModules
开启后,Webpack 会分析模块间的依赖关系,尽可能将可以安全合并的模块内联到一个函数里,能够通过消除大量模块包装函数来减小代码提及,减少函数作用域创建和查找开销从而提升运行速度。
需要先设置 optimization.providedExports 和 optimization.usedExports 为启用状态。
Webpack 中在生产环境中默认开启,其他环境中默认禁用,通常由 Webpack 自动完成即可,效果取决于代码结构与模块语法,使用 ES Module 效果最好。
emitOnErrors
构建出错处理。当编译出错时,是否继续输出资源。
通常来说,生产环境下设置为 false,出错时则不输出资源;开发模式通常为 true,可方便调试。
总结
Webpack 核心概念大概就以上这些,但其实像 Loader、Plugin、优化,都还有很多可以深入的地方,但真的要一次性搞懂全部太困难了,还是先从整体的核心概念理解入手,在逐步深入好些。