Webpack打包瘦身40%:Tree Shaking与Code Splitting实战!
大家好,我是King。欢迎来到今天的实战分享。
很多朋友刚接触Webpack时,总觉得打包出来的文件又大又慢,页面加载像老牛拉车。其实问题往往出在两个地方:一是打包进了很多没用到的代码,二是所有代码都塞进了一个大文件里。
今天咱们就用大白话,把Tree Shaking和Code Splitting这两个“减体积神器”讲明白。跟着配下来,打包体积减少40%不是梦。
一、Tree Shaking:摇掉没用的“死代码”
说白了,Tree Shaking就是一棵树,你摇一摇,枯枝败叶(没被引用的代码)就掉下来了。比如你写了个工具函数库,只用了其中一个,Webpack就会把其他没用的函数从最终包里“摇掉”。
前提条件: https://www.71wa.com/quanwei/77.html
使用ES Modules(import / export) www.187game.com.cn
生产模式(mode: "production")下自动开启
// utils.js
export function add(a, b) {
return a + b;
}
export function mul(a, b) {
return a * b;
}
// index.js
import { add } from './utils.js';
console.log(add(2, 3));
// mul 这个函数不会出现在最终打包文件里
二、Code Splitting:把大包拆成小块
不是所有代码都要一次性加载。Code Splitting就是把代码拆成多个小包,按需加载。 https://www.687game.com.cn/bbcz/124.html
比如用户打开首页,只需要首页的代码,那么“个人中心”页面的代码就可以等用户点击了再加载。 https://www.687game.com.cn/hjhy/125.html
// webpack.config.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
};
三、完整Webpack配置示例
下面给出一个可直接上手用的配置。注意我们把公共依赖和第三方库单独提取出来。
const path = require('path');
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
optimization: {
usedExports: true,
splitChunks: {
chunks: 'all',
minSize: 20000,
minChunks: 1,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
name: 'vendors',
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
runtimeChunk: 'single',
},
};
四、标记可被Tree Shaking的代码 https://www.99sf.com.cn/tougao/78.html https://www.99sf.com.cn/cqgl/77.html https://www.99sf.com.cn/game/76.html
在package.json中增加sideEffects字段,告诉Webpack哪些文件有副作用(比如修改了全局变量),不要乱摇。
{
"name": "my-project",
"sideEffects": ["*.css", "*.global.js"]
}
如果没有副作用,直接设为false,这样Webpack会更放心地删除未引用的导出。
{
"sideEffects": false
}
五、使用动态导入实现路由级代码拆分
这是目前最实用的拆分方式之一。比如我们有一个按钮,点击后才加载某个功能模块。
// 以前静态导入
// import heavyModule from './heavyModule';
// 现在动态导入
button.addEventListener('click', async () => {
const heavyModule = await import('./heavyModule');
heavyModule.doSomething();
});
Webpack会自动把heavyModule及其依赖单独打成一个chunk文件,只有点击按钮时才会下载。
六、预防打包重复代码
多个入口或组件可能引用同一个库(例如lodash)。不处理的话,每个包里都会重复打包。用splitChunks可以把公共部分抽出来。
// webpack.config.js 中更细致的公共分组
optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: 'commons',
chunks: 'initial',
minChunks: 2,
priority: 10,
},
},
},
},
七、查看打包前后体积对比 www.99sf.com.cn
用Webpack插件来可视化分析,看看哪些模块体积大。
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin(),
],
};
跑完打包后浏览器会自动打开一个关系图,哪里体积大、哪里重复了,一眼就看出来。
八、完整的生产环境配置示例(整合版)
module.exports = {
mode: 'production',
devtool: 'source-map',
optimization: {
usedExports: true,
minimize: true,
concatenateModules: true,
splitChunks: {
chunks: 'async',
minSize: 30000,
maxAsyncRequests: 5,
maxInitialRequests: 3,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
priority: 10,
},
},
},
},
};
新手常见问题和解答
问1:我按教程配了Tree Shaking,但打包体积没怎么变,怎么回事?
答:最常见的原因有三个。第一,你是不是用了require()?Tree Shaking只对ES Modules有效,CommonJS语法(require/module.exports)没法被静态分析,摇不掉。第二,检查package.json里的sideEffects字段,如果没声明或者错误设为true,Webpack会保守处理。第三,你是不是在import后但代码里没直接使用,却通过Object.assign之类的方式间接引用了?那样也会导致没法去除。建议先用webpack-bundle-analyzer看看实际包含的模块。
问2:动态导入的内容太多,导致网络请求变多了,反而加载变慢怎么办?
答:这个问题很实际。拆分过细确实会增加HTTP请求。可以这样调整:在splitChunks配置里调高minSize(比如从20000调高到50000),这样小于50KB的模块就不会单独拆出去了。另外开启maxAsyncRequests和maxInitialRequests限制并行请求数量。更简单的办法是只用路由级别的代码拆分,不要对很小的工具函数做动态导入。通常“1个大包+3-5个按需包”是比较合理的平衡点。
希望今天这篇大白话能帮你真正把打包体积降下来。配置不在多,关键是把usedExports和splitChunks用好,配合动态导入,你的项目加载速度一定会明显提升。咱们下期见!
更多经验分享:
把Codex技能变MCP服务,分享你的专属工具! - 九世師 - 博客园

浙公网安备 33010602011771号