3、构建阶段优化(下):压缩与混淆、Gzip/Brotli压缩、CDN加速与外部化
好,咱们接着聊构建阶段的优化。上一节我们讲了代码分割和Tree Shaking,这次要聊的这几个东西,说白了就是给项目「瘦身」和「提速」的最后一公里。
我个人习惯把这一节的内容分成三块:代码压缩与混淆、传输压缩、依赖外部化。这三板斧砍下去,你的打包体积至少能再缩个30%-50%。
一、压缩与混淆:让代码变得「面目全非」
先说说压缩。你写出来的代码,变量名又长又清晰,注释一堆,空格换行整整齐齐——这些对浏览器来说都是多余的。压缩的目的,就是把这些没用的东西全干掉。
1. TerserPlugin:JavaScript的终极瘦身师
Vue CLI和Vite默认都用TerserPlugin来做JS压缩。它干的事情很简单:去掉注释、缩短变量名、删除死代码。
我在项目中遇到过一个问题:默认配置下,Terser会把console.log也删掉。这对线上环境是好事,但开发阶段调试就麻烦了。所以我的习惯是区分环境配置:
// vue.config.js
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
configureWebpack: {
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: process.env.NODE_ENV === 'production', // 生产环境干掉console
drop_debugger: true,
},
output: {
comments: false, // 去掉注释
},
},
extractComments: false, // 不提取注释到单独文件
}),
],
},
},
};
嗯,这里要注意一点:drop_console 是暴力删除,如果你有些console是故意留的(比如埋点日志),那就别开这个选项。我曾经有个同事,开了这个选项后,线上埋点全挂了,排查了半天才发现是压缩给干掉了。
drop_console,结果把第三方SDK的日志也删了,导致数据上报异常。建议只删除 console.log,保留 console.warn 和 console.error。
2. CSS Minimizer:给样式表减减肥
CSS压缩相对简单,主要就是去掉空格、换行、注释,合并相同的选择器。我用的是 CssMinimizerPlugin,它和TerserPlugin配合起来效果很好。
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
module.exports = {
optimization: {
minimizer: [
new CssMinimizerPlugin({
minimizerOptions: {
preset: [
'default',
{
discardComments: { removeAll: true }, // 干掉所有注释
normalizeWhitespace: true, // 压缩空白
},
],
},
}),
],
},
};
你想想看,一个项目里CSS文件动不动几百KB,压缩后能省下30%的体积。虽然不如JS压缩效果那么明显,但蚊子腿也是肉啊。
二、Gzip/Brotli压缩:传输层的加速器
代码压缩完了,但文件还是要通过网络传输。这时候就需要传输层的压缩了。说白了,就是在服务器把文件再压一遍,浏览器收到后解压。
目前主流的有两种算法:Gzip 和 Brotli。Brotli是Google推出的新算法,压缩率比Gzip高20%-30%。我个人强烈建议用Brotli,前提是你的服务器和浏览器都支持。
1. 在构建时生成预压缩文件
我习惯在构建阶段直接生成 .gz 和 .br 文件,这样服务器就不用实时压缩了,能省下CPU资源。
// 使用 compression-webpack-plugin
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
plugins: [
new CompressionPlugin({
filename: '[path][base].gz', // 生成 .gz 文件
algorithm: 'gzip',
test: /\.(js|css|html|svg)$/,
threshold: 10240, // 只处理大于10KB的文件
minRatio: 0.8, // 压缩率低于0.8才压缩
}),
new CompressionPlugin({
filename: '[path][base].br', // 生成 .br 文件
algorithm: 'brotliCompress',
test: /\.(js|css|html|svg)$/,
threshold: 10240,
minRatio: 0.8,
}),
],
};
我在项目中遇到过一个问题:生成预压缩文件后,Nginx没配置好,导致浏览器请求的是 .br 文件,但服务器返回的是 .gz 文件,结果解压失败。嗯,这里要注意服务器配置要匹配。
2. Nginx配置示例
# 开启静态压缩文件支持
location ~* \.(js|css|html|svg)$ {
gzip_static on; # 优先使用预压缩的 .gz 文件
brotli_static on; # 优先使用预压缩的 .br 文件
# 如果浏览器支持 Brotli,返回 .br 文件
# 否则返回 .gz 文件
add_header Content-Encoding gzip;
}
三、CDN加速与外部化:把包袱甩给CDN
最后聊一个我特别喜欢的优化手段:Externals。说白了,就是把那些不常变的第三方库(比如Vue、React、Lodash)从打包文件里踢出去,改用CDN加载。
为什么要这么做?你想想看,用户访问你的网站,如果他从另一个网站已经加载过Vue了,那浏览器缓存里就有。你再用CDN加载,大概率命中缓存,连网络请求都省了。
1. 配置Externals
// vue.config.js
module.exports = {
configureWebpack: {
externals: {
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex',
axios: 'axios',
},
},
};
配置完这个,打包后的文件里就不会包含这些库了。体积能小一大截。我有个项目,把Vue全家桶外部化后,打包体积从800KB降到了200KB。
2. 在HTML中引入CDN
然后在 public/index.html 里手动引入CDN链接:
<!-- 使用 CDN 加载 Vue 全家桶 -->
<script src="https://cdn.jsdelivr.net/npm/vue@3.2.0/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-router@4.0.0/dist/vue-router.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuex@4.0.0/dist/vuex.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.21.0/dist/axios.min.js"></script>
3. 选择合适的CDN
CDN的选择也有讲究。我个人习惯用 jsDelivr 或 cdnjs,它们有全球节点,速度稳定。如果是国内项目,建议用 BootCDN 或 七牛云。
| CDN服务商 | 特点 | 适用场景 |
|---|---|---|
| jsDelivr | 全球节点,支持npm包 | 国际项目 |
| cdnjs | 老牌CDN,库全 | 通用场景 |
| BootCDN | 国内节点,速度快 | 国内项目 |
| 七牛云 | 企业级,可自定义 | 大型项目 |
四、总结一下
这一节的内容,说白了就是三件事:
- 压缩与混淆:用TerserPlugin和CssMinimizerPlugin把代码体积压到最小
- 传输压缩:用Gzip或Brotli在传输层再压一遍,优先用Brotli
- CDN外部化:把第三方库甩给CDN,利用浏览器缓存减少请求
这三步走完,你的项目加载速度至少能快一倍。我有个项目,优化前首屏加载要3秒,优化后直接降到1秒以内。嗯,效果就是这么明显。
下一节我们聊聊运行时优化,那才是真正考验功力的地方。