如何使用 splitChunks 将第三方库(node_modules)与业务代码分离打包?
在 Webpack 中,使用 optimization.splitChunks 将第三方库(node_modules)与业务代码分离打包是非常常见的优化手段。这样可以利用浏览器的缓存机制:当业务代码频繁更新时,第三方库的代码文件名保持不变,用户无需重新下载大体积的第三方库。
以下是实现这一功能的详细配置步骤和最佳实践:
1. 基础配置(推荐)
在 webpack.config.js 中添加 optimization.splitChunks 配置:
javascript
const path = require('path');
module.exports = {
// ... 其他配置 (entry, output 等)
output: {
path: path.resolve(__dirname, 'dist'),
// 使用 [contenthash] 保证只有内容变更时文件名才会改变,利于长缓存
filename: 'js/[name].[contenthash:8].js',
chunkFilename: 'js/[name].[contenthash:8].chunk.js',
clean: true, // Webpack 5 清理 dist
},
optimization: {
// 提取 webpack 运行时的引导代码 (manifest),防止业务代码修改导致 vendor 元素的 hash 改变
runtimeChunk: 'single',
splitChunks: {
// initial: 只处理同步代码;async: 只处理异步加载代码(import());all: 处理所有代码(推荐)
chunks: 'all',
cacheGroups: {
// 自定义抽离 node_modules 的缓存组
vendor: {
test: /[\\/]node_modules[\\/]/, // 匹配 node_modules 目录下的模块
name: 'vendors', // 打包后的 chunk 名称 (如 vendors.js)
priority: 10, // 优先级,数字越大优先级越高
enforce: true, // 强制执行,忽略 minSize, minChunks 等限制
},
// 抽离公共的业务代码(被多次引用的业务模块)
common: {
name: 'common',
minChunks: 2, // 至少被引用 2 次才抽离
priority: 0,
reuseExistingChunk: true, // 如果该模块已经被打包过,则复用
},
},
},
},
};
2. 关键参数详解
chunks: 'all':默认是'async'(只对动态导入import()的代码生效)。设为'all'可以确保无论是同步import React from 'react'还是异步导入的node_modules都能被抽离。test: /[\\/]node_modules[\\/]/:正则表达式,用于匹配模块路径。使用[\\/]是为了兼容 Windows (\) 和 macOS/Linux (/) 的路径分隔符。priority(优先级):一个模块可能同时符合多个缓存组规则,Webpack 会根据priority的大小决定将其放入哪个组。vendor的优先级通常要高于common。runtimeChunk: 'single':极为重要! 它将 Webpack 管理模块依赖的运行时代码单独抽取为一个runtime.js。如果不加这个,业务代码的修改会导致注入的 runtime 改变,从而破坏vendors.js的contenthash缓存策略。
3. 进阶优化:拆分超大第三方库(多 Vendor 策略)
如果你的 node_modules 非常大(例如包含了 React、Vue、Element-UI、ECharts 等),把它们全部打包进一个 vendors.js 会导致首屏下载单文件过大。
你可以进一步将大的第三方库独立拆分:
javascript
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
cacheGroups: {
// 1. 拆分框架核心库 (以 React 为例)
reactFramework: {
test: /[\\/]node_modules[\\/](react|react-dom|react-router-dom)[\\/]/,
name: 'framework-react',
priority: 20, // 优先级高于通用 vendor
},
// 2. 拆分 UI 组件库 (以 Element Plus 为例)
elementPlus: {
test: /[\\/]node_modules[\\/]element-plus[\\/]/,
name: 'ui-element-plus',
priority: 15,
},
// 3. 剩下的 node_modules 打包进 vendors
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10,
reuseExistingChunk: true,
},
},
},
}
4. 效果对比
配置前打包结果:
main.js(2.5 MB) —— 包含所有业务逻辑 + React + Lodash + 工具库
配置后打包结果:
runtime.a1b2c3.js(2 KB) —— Webpack 运行时vendors.d4e5f6.js(1.8 MB) —— 包含所有第三方库(基本不怎么变,利于缓存)main.7g8h9i.js(700 KB) —— 纯业务代码(更新频繁,文件小,下载快)
5. 注意事项
- Webpack 5 的
name属性:在生产环境中,Webpack 5 推荐不要将name设为固定字符串(或者设为false),让 Webpack 根据 Chunk ID 自动生成名称,这样可以获得更好的长期缓存(Long-term Caching)效果。 - HTML 引入顺序:如果使用的是
html-webpack-plugin,它会自动按正确的依赖顺序注入<script>标签(通常是runtime->vendors->main)。无需手动干预。