Webpack 学习总结
Webpack 学习笔记,按当前 Webpack 5.x 现状更新
一、Webpack 简介
1.1 webpack 是什么
webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。
在webpack 看来, 前端的所有资源文件(js/json/css/img/less/...)都会作为模块处理。
它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)。
1.2 webpack 五个核心概念
1.2.1 Entry
入口(Entry):指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图。
1.2.2 Output
输出(Output):指示 webpack 打包后的资源 bundles 输出到哪里去,以及如何命名。
1.2.3 Loader
Loader:让 webpack 能够去处理那些非 JS 的文件,比如样式文件、图片文件(webpack 自身只理解
JS)
Webpack 5 起图片、字体这类资源已经内置 Asset Modules,很多场景不再需要再装 url-loader / file-loader。后面配置里会直接换成推荐写法,但老写法目前还能跑。
1.2.4 Plugins
插件(Plugins):可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,
一直到重新定义环境中的变量等。
1.2.5 Mode
模式(Mode):指示 webpack 使用相应模式的配置。
| 选项 | 描述 | 特点 |
|---|---|---|
| development | 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin。 | 能让代码本地调试运行的环境 |
| production | 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置为 production。启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 TerserPlugin。 | 能让代码优化上线运行的环境 |
(现在 mode 直接帮你设好这些默认值,NamedModulesPlugin 这些老名字基本不用再手动提了。)
二、Webpack 初体验
2.1 初始化配置
- 初始化 package.json: npm init
- 下载安装webpack: (webpack4以上的版本需要全局/本地都安装webpack-cli)
- 全局安装:
npm i webpack webpack-cli -g - 本地安装:
npm i webpack webpack-cli -D
2.2 编译打包应用
创建 src 下的 js 等文件后,不需要配置 webpack.config.js 文件,在命令行就可以编译打包。
指令:
- 开发环境:webpack ./src/index.js -o ./build/built.js --mode=development
webpack会以 ./src/index.js 为入口文件开始打包,打包后输出到 ./build/built.js 整体打包环境,是开发环境 - 生产环境:webpack ./src/index.js -o ./build/built.js --mode=production
webpack会以 ./src/index.js 为入口文件开始打包,打包后输出到 ./build/built.js 整体打包环境,是生产环境
结论:
- webpack 本身能处理 js/json 资源,不能处理 css/img 等其他资源
- 生产环境和开发环境将 ES6 模块化编译成浏览器能识别的模块化,但是不能处理 ES6 的基本语法转化为 ES5(需要借助 loader)
- 生产环境比开发环境多一个压缩 js 代码
三、Webpack 开发环境的基本配置
webpack.config.js 是 webpack 的配置文件。
作用: 指示 webpack 干哪些活(当你运行 webpack 指令时,会加载里面的配置)
所有构建工具都是基于 nodejs 平台运行的,模块化默认采用 commonjs。
开发环境配置主要是为了能让代码运行。主要考虑以下几个方面:
- 打包样式资源
- 打包 html 资源
- 打包图片资源
- 打包其他资源
- devServer
下面是一个简单的开发环境 webpack.config.js 配置文件(ESM):
import { resolve, dirname } from 'path'
import { fileURLToPath } from 'url'
import HtmlWebpackPlugin from 'html-webpack-plugin'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
export default {
entry: './src/js/index.js',
output: {
filename: 'js/build.js',
path: resolve(__dirname, 'build'),
},
module: {
rules: [
{
test: /\.less$/,
use: [
// use数组中loader执行顺序:从右到左,从下到上,依次执行
// style-loader:创建style标签,将js中的样式资源插入进去,添加到head中生效
'style-loader',
// css-loader:将css文件变成commonjs模块加载到js中,里面内容是样式字符串
'css-loader',
// less-loader:将less文件编译成css文件
'less-loader',
],
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
// Webpack 5 推荐用内置 asset modules,url-loader / file-loader 已标记弃用
test: /\.(jpg|png|gif)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024, // 小于8kb转base64
},
},
generator: {
filename: 'imgs/[hash:10][ext]',
},
},
{
test: /\.html$/,
// 处理html中的img,从而能被上面的规则处理
loader: 'html-loader',
},
{
exclude: /\.(html|js|css|less|jpg|png|gif)/,
type: 'asset/resource',
generator: {
filename: 'media/[hash:10][ext]',
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
],
mode: 'development',
// 启动指令:npx webpack serve(旧写法 npx webpack-dev-server 仍可用)
devServer: {
// contentBase 早就换成 static 了
static: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
hot: true,
},
}其中,大部分配置都在注释中给出解释。
- 运行项目的两个指令:
webpack 会将打包结果输出出去(build文件夹)
npx webpack serve 只会在内存中编译打包,没有输出(旧写法npx webpack-dev-server仍可用) - loader 和 plugin 的不同:(plugin 一定要先引入才能使用)
- loader: 1. 下载 2. 使用(配置 loader)
- plugins: 1.下载 2. 引入 3. 使用
Webpack 5 对 ESM 配置支持已经很成熟。文件名用 webpack.config.mjs,或在 package.json 里声明 "type": "module"。严格 ESM 下没有 __dirname / __filename,需要用上面的 fileURLToPath + dirname 写法。
四、Webpack 生产环境的基本配置
而生产环境的配置需要考虑以下几个方面:
- 提取 css 成单独文件
- css 兼容性处理
- 压缩 css
- js 语法检查
- js 兼容性处理
- js 压缩
- html 压缩
下面是一个基本的生产环境下的 webpack.config.js 配置(已按当前推荐更新):
import { resolve, dirname } from 'path'
import { fileURLToPath } from 'url'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'
import ESLintPlugin from 'eslint-webpack-plugin'
import postcssPresetEnv from 'postcss-preset-env'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
// Webpack 5 里 mode: 'production' 已经会自动设置 process.env.NODE_ENV,下面这行多数情况可以省略
// process.env.NODE_ENV = 'production'
const commonCssLoader = [
// 取代 style-loader:提取 js 中的 css 成单独文件,再通过 link 加载
MiniCssExtractPlugin.loader,
'css-loader',
/*
postcss-loader:css 兼容性处理,需要 postcss-loader + postcss-preset-env
通过 package.json 的 browserslist 加载指定兼容性样式:
"browserslist": {
"development": ["last 1 chrome version", "last 1 firefox version", "last 1 safari version"],
"production": [">0.2%", "not dead", "not op_mini all"]
}
*/
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [postcssPresetEnv()],
},
},
},
]
export default {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build'),
},
module: {
rules: [
{
test: /\.css$/,
use: [...commonCssLoader],
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader'],
},
/*
正常来讲,一个文件只能被一个 loader 处理。
多个 loader 时要注意顺序:先 eslint 再 babel。
eslint-loader 已废弃,改用 eslint-webpack-plugin。
*/
{
// js 兼容性处理:babel-loader + @babel/core + @babel/preset-env
// 按需加载用 core-js
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '80',
firefox: '78',
safari: '14',
edge: '88',
},
},
],
],
cacheDirectory: true,
},
},
{
test: /\.(jpg|png|gif)/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024,
},
},
generator: {
filename: 'imgs/[hash:10][ext]',
},
},
{
test: /\.html$/,
loader: 'html-loader',
},
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
type: 'asset/resource',
generator: {
filename: 'media/[hash:10][ext]',
},
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/built.css',
}),
new HtmlWebpackPlugin({
template: './src/index.html',
// production 模式下默认就会压缩,下面其实可不写
minify: {
collapseWhitespace: true,
removeComments: true,
},
}),
new ESLintPlugin({
extensions: ['js'],
exclude: 'node_modules',
fix: true,
}),
],
optimization: {
minimizer: [
`...`, // 保留默认的 JS 压缩(Terser)
new CssMinimizerPlugin(),
],
},
mode: 'production',
}官方已经提供了 Native CSS(experiments.css)和 Native HTML(experiments.html)的迁移方向。5.107 起开始实验性原生处理 HTML(解析 <script src> / <link>、HMR 等),后续版本继续推进。旧的 css-loader / style-loader / mini-css-extract-plugin / HtmlWebpackPlugin + html-loader 生态目前仍可正常使用,老项目继续用完全没问题。具体默认开启时间和弃用时间表,以官方 changelog 为准。
试用原生 CSS 可加:
experiments: { css: true }
// 然后直接 import './style.css',无需 style-loader / css-loader五、Webpack 优化配置
5.1 开发环境性能优化
5.1.1 HMR(模块热替换)
HMR: hot module replacement 热模块替换 / 模块热替换
作用:一个模块发生变化,只会重新打包构建这一个模块(而不是打包所有模块),极大提升构建速度。
代码:只需要在 devServer 中设置 hot 为 true,就会自动开启 HMR 功能(只能在开发模式下使用)
devServer: {
static: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
// 开启HMR功能
// 当修改了webpack配置,新配置要想生效,必须重启webpack服务
hot: true,
}启动:npx webpack serve(旧写法 npx webpack-dev-server 仍可用)。
每种文件实现热模块替换的情况:
- 样式文件:可以使用 HMR 功能,因为开发环境下使用的 style-loader 内部默认实现了热模块替换功能
- js 文件:默认不能使用 HMR 功能(修改一个 js 模块所有 js 模块都会刷新)
--> 实现 HMR 需要修改 js 代码(添加支持 HMR 功能的代码)
if (module.hot) {
// 一旦 module.hot 为 true,说明开启了 HMR 功能
module.hot.accept('./print.js', function () {
// 监听 print.js 变化,只有这个模块会重新打包,其他模块不会
print()
})
}- 注意:HMR 对 js 的处理,只能处理非入口 js 文件的其他文件。
- html 文件:默认不能使用 HMR 功能(只有一个 html,一般不需要做 HMR)
使用 HMR 会导致 html 不能热更新。解决:把 html 也放进 entry:
entry: ['./src/js/index.js', './src/index.html']5.1.2 source-map
source-map:一种提供源代码到构建后代码的映射的技术(构建后代码出错时,可通过映射追踪到源代码错误)。
参数:[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
devtool: 'eval-source-map'可选方案:
- source-map:外部,错误代码准确信息 和 源代码的错误位置
- inline-source-map:内联,只生成一个内联 source-map,错误代码准确信息 和 源代码的错误位置
- hidden-source-map:外部,错误原因有,但没有错误位置(为了隐藏源代码)
- eval-source-map:内联,每个文件都生成对应的 source-map,都在 eval 中
- nosources-source-map:外部,有错误信息,但没有任何源代码信息
- cheap-source-map:外部,只能精确到整行,忽略列
- cheap-module-source-map:外部,module 会加入 loader 的 source-map
内联和外部的区别:
- 外部生成了文件,内联没有
- 内联对首次构建通常更快,但会让产物体积膨胀
开发环境:需要考虑速度快、调试更友好
- 速度快(eval > inline > cheap >...)
- eval-cheap-source-map
- eval-source-map
- 调试更友好 3. source-map 4. cheap-module-source-map 5. cheap-source-map
最终较好的两种 --> eval-source-map / eval-cheap-module-source-map
生产环境:考虑要不要隐藏源代码、调试友好度
- 内联会让体积变大,生产环境不用内联
- 隐藏源代码:nosources-source-map(全部隐藏)/ hidden-source-map(只隐藏源码位置)
最终较好的两种 --> source-map / cheap-module-source-map
5.2 生产环境性能优化
5.2.1 优化打包构建速度
5.2.1.1 oneOf
oneOf:匹配到 loader 后就不再向后匹配,优化打包构建速度。
module: {
rules: [
{
// 以下 loader 只会匹配一个
// 注意:不能有两个配置处理同一种类型文件(eslint 已提到插件)
oneOf: [
{
test: /\.css$/,
use: [...commonCssLoader],
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader'],
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '80',
firefox: '78',
safari: '14',
edge: '88',
},
},
],
],
cacheDirectory: true,
},
},
{
test: /\.(jpg|png|gif)/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024,
},
},
generator: {
filename: 'imgs/[hash:10][ext]',
},
},
{
test: /\.html$/,
loader: 'html-loader',
},
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
type: 'asset/resource',
generator: {
filename: 'media/[hash:10][ext]',
},
},
],
},
],
},5.2.1.2 babel 缓存 + filesystem 缓存
babel 缓存:类似 HMR,将 babel 处理后的资源缓存起来,第二次构建更快。
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '80',
firefox: '78',
safari: '14',
edge: '88',
},
},
],
],
cacheDirectory: true,
},
},文件资源缓存
文件名不变就不会重新请求,继续用之前的缓存。
-
hash:每次 webpack 打包生成一个唯一 hash。
问题:重新打包所有文件 hash 都变,导致所有缓存失效(可能只改了一个文件)。 -
chunkhash:根据 chunk 生成的 hash。同一 chunk 的 hash 一样。
问题:js 和 css 同属一个 chunk 时 hash 仍相同(css 被 css-loader 打进 js)。 -
contenthash:根据文件内容生成 hash。内容变了 hash 才变。
改 css 只影响 css 的 hash,js 不变,缓存可以分开判断。
Webpack 5 自带的持久化缓存比 babel 的 cacheDirectory 覆盖面更广,配置也更简单。大多数项目直接开这个就够了:
cache: {
type: 'filesystem',
buildDependencies: {
// 严格 ESM 下 __filename 需按 §三 的 fileURLToPath(import.meta.url) 方式重建
config: [__filename],
},
}缓存会落到 node_modules/.cache/webpack。二次构建速度提升非常明显,很多时候已经不需要再上 thread-loader。
5.2.1.3 多进程打包
⚠️ Legacy 提示:Webpack 5 的 cache: { type: 'filesystem' } 已经能覆盖大部分加速场景,thread-loader 的收益显著下降。只有确认 babel 等步骤仍然是明显瓶颈时才值得加。
多进程打包:某个任务耗时长会卡顿,多进程可以同时干多件事。
优点是提升打包速度,缺点是进程开启和通信都有开销(babel-loader 最耗时,所以针对它优化)。
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'thread-loader',
options: {
workers: 2,
},
},
{
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '80',
firefox: '78',
safari: '14',
edge: '88',
},
},
],
],
cacheDirectory: true,
},
},
],
},5.2.1.4 externals
externals:让某些库不打包,通过 cdn 引入。
externals: {
// 忽略的库名 -- npm 包名
jquery: 'jQuery',
}在 index.html 中通过 cdn 引入:
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>5.2.1.5 dll
⚠️ Legacy,新项目不建议。Webpack 5 官方迁移指南已经建议用 splitChunks + 持久化缓存替代 DllPlugin。下面配置仅作历史参考。
dll:让某些库单独打包,再直接引入到 build 中。
webpack.dll.js:
import { resolve, dirname } from 'path'
import { fileURLToPath } from 'url'
import webpack from 'webpack'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
export default {
entry: {
jquery: ['jquery'],
},
output: {
filename: '[name].js',
path: resolve(__dirname, 'dll'),
library: '[name]_[hash]',
},
plugins: [
new webpack.DllPlugin({
name: '[name]_[hash]',
path: resolve(__dirname, 'dll/manifest.json'),
}),
],
mode: 'production',
}主配置中:
import webpack from 'webpack'
import AddAssetHtmlWebpackPlugin from 'add-asset-html-webpack-plugin'
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json'),
}),
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js'),
}),
],5.2.2 优化代码运行的性能
5.2.2.1 缓存
(见上面 contenthash 与 filesystem 缓存)
5.2.2.2 tree shaking(树摇)
tree shaking:去除无用代码。
前提:1. 必须使用 ES6 模块化 2. 开启 production 环境。
作用:减少代码体积。
原理:
- ES6 Module 静态分析,编译时能判断加载了哪些模块
- 静态分析程序流,删除未被使用或引用的模块和变量
在 package.json 中:
"sideEffects": false表示所有代码都没有副作用(都可以 tree shaking)。
问题:可能会把 css / @babel/polyfill 干掉。可改成:
"sideEffects": ["*.css", "*.less"]5.2.2.3 code split(代码分割)
将一个大的 bundle.js 拆成多个小文件,并行加载更快。
- 多入口拆分
entry: {
index: './src/js/index.js',
test: './src/js/test.js',
},
output: {
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build'),
},- optimization.splitChunks
optimization: {
splitChunks: {
chunks: 'all',
},
},- 将 node_modules 中代码单独打包(超过约 30kb)
- 自动分析多入口 chunk 的公共文件,有则打成单独 chunk
- import 动态导入
import(/* webpackChunkName: 'test' */ './test')
.then(({ mul, count }) => {
console.log(mul(2, 5))
})
.catch(() => {
console.log('文件加载失败~')
})5.2.2.4 lazy loading(懒加载/预加载)
- 懒加载:用到时才加载(需要代码分割)。资源大时可能有延迟。
- 正常加载:并行加载,没有先后顺序,可能先加载不需要的资源。
- 预加载 prefetch(兼容性较差):空闲时提前加载,用时已经准备好。
document.getElementById('btn').onclick = function () {
import(/* webpackChunkName: 'test', webpackPrefetch: true */ './test').then(
({ mul }) => {
console.log(mul(4, 5))
}
)
}5.2.2.5 pwa(离线可访问技术)
pwa:渐进式网络应用,使用 service worker 和 workbox。优点是离线也能访问,缺点是兼容性差。
import WorkboxWebpackPlugin from 'workbox-webpack-plugin'
// plugins 中:
new WorkboxWebpackPlugin.GenerateSW({
// 1. 帮助 service worker 快速启动
// 2. 删除旧的 service worker
clientsClaim: true,
skipWaiting: true,
})index.js 中激活:
/*
1. eslint 不认识 window、navigator,需在 package.json 的 eslintConfig 里加:
"env": { "browser": true }
2. sw 必须跑在服务器上(nodejs 或 serve -s build)
*/
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/service-worker.js')
.then(() => console.log('sw注册成功了~'))
.catch(() => console.log('sw注册失败了~'))
})
}5.2.2.6 Module Federation(补充)
Webpack 5 的旗舰特性之一。允许运行时动态加载其他独立构建的模块,典型场景是微前端。
核心概念:
exposes:当前应用对外暴露的模块remotes:声明要消费的远程模块shared:共享依赖(react、react-dom 等),避免重复加载
简单示例(host 端):
import webpack from 'webpack'
const { ModuleFederationPlugin } = webpack.container
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
}),
]实际落地时还要处理版本协商、样式隔离、类型共享等问题,这里只点到为止。
5.2.2.7 分析工具
排查体积和依赖时常用 webpack-bundle-analyzer:
npm i -D webpack-bundle-analyzerimport { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'
plugins: [new BundleAnalyzerPlugin()]六、Webpack 配置详情
6.1 entry
entry: 入口起点
- string -->
'./src/index.js',单入口
形成一个 chunk,输出一个 bundle,名称默认 main - array -->
['./src/index.js', './src/add.js'],多入口
最终只形成一个 chunk、一个 bundle(一般用于 HMR 让 html 热更新生效) - object,多入口
有几个入口就形成几个 chunk、几个 bundle,名称是 key
特殊用法:
entry: {
index: ['./src/index.js', './src/count.js'], // 最终一个 chunk
add: './src/add.js', // 一个 chunk
}6.2 output
output: {
filename: 'js/[name].js',
path: resolve(__dirname, 'build'),
publicPath: '/',
chunkFilename: 'js/[name]_chunk.js',
// Webpack 5 推荐 library 对象写法,旧的 libraryTarget 已移除
library: {
name: '[name]',
type: 'umd', // 'var' | 'module' | 'commonjs' | 'umd' 等
},
// 直接输出 ESM(5.111 起 experiments.outputModule 已移除,用 output.module 即可):
// module: true,
},6.3 module
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
// 语法检查已改为插件,不再写在 rules 里
{
oneOf: [],
},
],
},
plugins: [
new ESLintPlugin({
extensions: ['js'],
exclude: 'node_modules',
fix: true,
}),
]6.4 resolve
resolve: {
alias: {
$css: resolve(__dirname, 'src/css'),
},
extensions: ['.js', '.json', '.jsx', '.css'],
modules: [resolve(__dirname, '../../node_modules'), 'node_modules'],
}引入时可写:import '$css/index'
6.5 devServer
devServer: {
static: resolve(__dirname, 'build'), // 原 contentBase
// watch 默认开启,watchContentBase 已移除
compress: true,
port: 5000,
host: 'localhost',
open: true,
hot: true,
client: {
logging: 'none',
overlay: false,
},
proxy: {
'/api': {
target: 'http://localhost:3000',
pathRewrite: {
'^/api': '',
},
},
},
}跨域:浏览器和服务器之间有同源限制,服务器之间没有。开发时通过代理服务器转发请求,即可解决开发环境跨域。
6.6 optimization
contenthash 会导致:改 a.js 时,index.js 里记录的 a 的 hash 也变,进而 index.js 自己的 hash 也变,缓存失效。
解决:runtimeChunk,把记录其他模块 hash 的部分单独打成 runtime 文件。
output: {
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build'),
chunkFilename: 'js/[name].[contenthash:10]_chunk.js',
},
optimization: {
splitChunks: {
chunks: 'all',
/* 以下为默认值,可不写;具体数字以官方 SplitChunksPlugin 文档为准
(Webpack 5 的 maxAsyncRequests / maxInitialRequests 等与 v4 不同)
minSize: 30 * 1024, // 分割的chunk最小为30kb(大于30kb的才分割)
maxSize: 0, // 最大没有限制
minChunks: 1, // 要提取的chunk最少被引用1次
maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量为5
maxInitialRequests: 3, // 入口js文件最大并行请求数量
automaticNameDelimiter: '~', // 名称连接符
name: true, // 可以使用命名规则
cacheGroups: { // 分割chunk的组
vendors: {
// node_modules中的文件会被打包到vendors组的chunk中,--> vendors~xxx.js
// 满足上面的公共规则,大小超过30kb、至少被引用一次
test: /[\\/]node_modules[\\/]/,
// 优先级
priority: -10
},
default: {
// 要提取的chunk最少被引用2次
minChunks: 2,
priority: -20,
// 如果当前要打包的模块和之前已经被提取的模块是同一个,就会复用,而不是重新打包
reuseExistingChunk: true
}
} */
},
runtimeChunk: {
name: (entrypoint) => `runtime-${entrypoint.name}`,
},
minimizer: [
// Terser 现在默认带缓存和多进程,基本不用再手动配
],
}七、Webpack5 介绍和使用
此版本重点关注:
- 通过持久缓存提高构建性能
- 使用更好的算法和默认值改善长期缓存
- 通过更好的树摇和代码生成改善包体积
- 清理内部结构
- 为后续大版本做准备
下载
npm i webpack webpack-cli -D(直接装最新 5.x 即可)
自动删除 Node.js Polyfills
webpack ≤ 4 会自动给 node 核心模块打 polyfill,导致包体积变大。Webpack 5 默认不再填充,专注前端兼容模块。
迁移:尽量用前端兼容模块;确需 polyfill 时按报错提示手动添加。
Chunk 和模块 ID
生产模式默认启用确定性算法:
chunkIds: 'deterministic'
moduleIds: 'deterministic'Chunk ID
开发环境可以不写 webpackChunkName,生产环境仍建议命名。内部不再单纯用 0、1、2 命名。
Tree Shaking
- 支持嵌套模块的 tree shaking
- 能分析多模块之间的关系
- 能对 CommonJS 做 tree shaking
Output
webpack 4 默认只能输出 ES5。Webpack 5 支持更多输出形态;output.module: true 已稳定(5.111 起 experiments.outputModule 移除,直接用 output.module)。
SplitChunks 的 minSize 也可按类型配置:
minSize: {
javascript: 30000,
style: 50000,
}Caching
cache: {
type: 'filesystem',
buildDependencies: {
// 严格 ESM 下 __filename 需按 §三 的 fileURLToPath(import.meta.url) 方式重建
config: [__filename],
},
}缓存目录:node_modules/.cache/webpack
监视输出文件
首次构建会对比输出文件是否变化,再决定是否全部写出;watch 重建时只更新变化部分。
默认值
entry: "./src/index.js"output.path: path.resolve(__dirname, "dist")output.filename: "[name].js"
更多内容
到 2026 年,Webpack 5 仍在小版本迭代(universal target、内置 CSS/HTML/TS 方向、性能检查、ESM 输出等)。完整 changelog 见官方仓库。
同时要看到生态变化:Rspack(兼容 Webpack 配置的高性能实现)、Vite、Turbopack 等已经改变了很多人的默认选择。Webpack 在大型存量项目和 Module Federation 场景仍然稳,但新项目选型时不必默认上 Webpack。
changelog-v5:https://github.com/webpack/changelog-v5