前端打包优化实战总结几个有效降低bundle体积的方法
先搞个最基础的打包优化配置
最近接手了个老项目,打包出来的文件能有20MB,用户加载得要死要活的。打开Chrome DevTools一看,主包chunk-vendors.js就有8MB,这还得了?直接动手优化。
首先在vue.config.js里加几个最基本的配置:
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
configureWebpack: {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
name: 'chunk-vendors',
test: /[\/]node_modules[\/]/,
priority: 10,
chunks: 'initial'
},
elementUI: {
name: 'chunk-elementUI',
priority: 20,
test: /[\/]node_modules[\/]_?element-ui(.*)/
}
}
}
}
}
})
这样分包后,至少把第三方库和业务代码分开打包了。elementUI单独拆出来是因为它特别大,经常改动的话用户每次都要重新下载整个vendor包。
代码压缩这块的坑踩够了
压缩这一块我之前踩过不少坑,特别是source map的问题。开发环境一般会开productionSourceMap: true,方便调试错误定位。但上线的时候千万别忘了关掉,不然打包出来的map文件比源码还大。
module.exports = defineConfig({
productionSourceMap: false, // 记住要关掉
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
config.optimization.minimizer = [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true, // 去掉console
drop_debugger: true // 去掉debugger
}
}
})
]
}
}
})
这里有个重点,drop_console设置为true后会把所有console都删掉,包括console.error,所以记得提前把重要的错误处理用try-catch包起来,别到时候出了错也看不到日志。
懒加载路由才是王道
路由懒加载这个我就不多说了,基本操作。但有个细节要注意,import()里不能写死路径,要用变量会导致webpack无法静态分析依赖。
// 正确写法
{
path: '/home',
component: () => import('@/views/Home.vue')
}
// 错误写法
const viewName = 'Home'
{
path: '/home',
component: () => import(@/views/${viewName}.vue)
}
动态导入写不对的话webpack分析不出来模块关系,就不会生成独立的chunk,那就白费了。
CDN外部化是个好办法
对于特别大的库比如vue、vuex、element-ui这些,可以考虑放到CDN上。但配置起来有点麻烦,而且版本管理是个问题。
module.exports = defineConfig({
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
config.externals = {
'vue': 'Vue',
'vue-router': 'VueRouter',
'vuex': 'Vuex',
'axios': 'axios',
'element-ui': 'ELEMENT'
}
}
}
})
然后在public/index.html里手动引入CDN:
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-router@3.5.1/dist/vue-router.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/element-ui@2.15.6/lib/index.js"></script>
这里有坑,CDN资源加载失败的情况下页面就跑不起来了。建议加个onerror兜底:
<script
src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"
onerror="window.location.href='/error.html'">
</script>
图片压缩和Base64转换配置
图片处理这块也很重要,小图标转base64可以减少HTTP请求,大图片就得压缩了。
module.exports = defineConfig({
chainWebpack: config => {
// 小于4KB的图片转base64
config.module
.rule('images')
.use('url-loader')
.loader('url-loader')
.tap(options => Object.assign(options, {
limit: 4096,
fallback: {
loader: 'file-loader',
options: {
name: 'img/[name].[hash:8].[ext]'
}
}
}))
}
})
不过这里有个坑,如果项目里图片特别多,全部转base64会让JS包变得很大,反而影响首屏加载速度。一般建议只对小于4KB的小图标这样做。
Tree Shaking该来的还是要来
Tree Shaking主要针对ES6模块语法,确保使用的都是按需导入。像Element UI这样全量引入的库就享受不到tree shaking的好处了。
// 推荐写法
import { Button, Table } from 'element-ui'
// 全量引入就享受不到tree shaking
import ElementUI from 'element-ui'
配合babel-plugin-import插件可以实现按需加载:
// babel.config.js
module.exports = {
plugins: [
[
"import",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
Webpack Bundle Analyzer帮你找问题
分析打包结果必备工具,安装webpack-bundle-analyzer后可以直观看到各个模块的大小分布。
npm install --save-dev webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = defineConfig({
configureWebpack: config => {
if (process.env.ANALYZE) {
config.plugins.push(new BundleAnalyzerPlugin())
}
}
})
然后执行npm run build –report,就会自动打开可视化界面显示打包结果。这个工具帮我发现了不少隐藏很深的大体积依赖。
缓存策略配置细节
合理配置文件名哈希可以有效利用浏览器缓存。每次构建只有真正改动的文件才会改变哈希值,没变的就能走缓存了。
module.exports = defineConfig({
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
config.output.filename = 'js/[name].[contenthash:8].js'
config.output.chunkFilename = 'js/[name].[contenthash:8].js'
}
}
})
contenthash比chunkhash更精细,只有文件内容变了hash才会变。不过注意如果用了externals,外链资源的版本更新可能不会触发hash变化,需要自己控制CDN版本。
以上是我踩坑后的总结,希望对你有帮助。打包优化的细节还有很多,像预加载preload、资源提示prefetch这些,后续会继续分享这类博客。

暂无评论