跳到主要内容

资源压缩技术详解

概述

资源压缩是前端性能优化的基础技术,通过减少文件大小来降低网络传输时间,提升页面加载速度。本文详细介绍各种资源压缩技术的原理、实现方法和最佳实践。

压缩原理

压缩算法对比

压缩算法性能对比图
┌─────────────────┬──────────┬──────────┬──────────┬──────────┐
│ 压缩算法 │ 压缩率 │ 压缩速度 │ 解压速度 │ 兼容性 │
├─────────────────┼──────────┼──────────┼──────────┼──────────┤
│ Gzip │ 70-80% │ 快 │ 快 │ 优秀 │
│ Brotli │ 80-90% │ 中等 │ 快 │ 良好 │
│ Deflate │ 65-75% │ 快 │ 快 │ 优秀 │
│ LZ4 │ 50-60% │ 极快 │ 极快 │ 中等 │
│ Zstandard │ 75-85% │ 快 │ 快 │ 中等 │
└─────────────────┴──────────┴──────────┴──────────┴──────────┘

压缩原理图示

原始文件 → 压缩算法 → 压缩后文件
100KB Gzip 30KB
Brotli 25KB
Deflate 35KB

压缩过程:
1. 查找重复模式
2. 建立字典表
3. 替换重复内容
4. 优化编码

HTML压缩

手动压缩示例

<!-- 压缩前 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>页面标题</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<h1>主标题</h1>
<nav>
<ul>
<li><a href="#home">首页</a></li>
<li><a href="#about">关于</a></li>
</ul>
</nav>
</header>
<main>
<p>这是主要内容</p>
</main>
</body>
</html>

<!-- 压缩后 -->
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>页面标题</title><link rel="stylesheet" href="styles.css"></head><body><header><h1>主标题</h1><nav><ul><li><a href="#home">首页</a></li><li><a href="#about">关于</a></li></ul></nav></header><main><p>这是主要内容</p></main></body></html>

自动化压缩工具

Gulp + html-minifier

// gulpfile.js
const gulp = require('gulp');
const htmlmin = require('gulp-htmlmin');

gulp.task('minify-html', () => {
return gulp.src('src/*.html')
.pipe(htmlmin({
collapseWhitespace: true, // 折叠空白
removeComments: true, // 移除注释
removeRedundantAttributes: true, // 移除冗余属性
removeScriptTypeAttributes: true, // 移除script的type属性
removeStyleLinkTypeAttributes: true, // 移除style的type属性
useShortDoctype: true, // 使用短doctype
minifyCSS: true, // 压缩内联CSS
minifyJS: true // 压缩内联JavaScript
}))
.pipe(gulp.dest('dist'));
});

// 运行任务
gulp.task('build', gulp.series('minify-html'));

Webpack + html-webpack-plugin

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: __dirname + '/dist'
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true
}
})
],
optimization: {
minimize: true,
minimizer: [new TerserPlugin()]
}
};

CSS压缩

手动压缩示例

/* 压缩前 */
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.button {
display: inline-block;
padding: 12px 24px;
background-color: #007bff;
color: #ffffff;
text-decoration: none;
border-radius: 4px;
transition: background-color 0.3s ease;
}

.button:hover {
background-color: #0056b3;
}

/* 压缩后 */
.container{width:100%;max-width:1200px;margin:0 auto;padding:20px;background-color:#fff;border:1px solid #e0e0e0;border-radius:8px;box-shadow:0 2px 4px rgba(0,0,0,.1)}.button{display:inline-block;padding:12px 24px;background-color:#007bff;color:#fff;text-decoration:none;border-radius:4px;transition:background-color .3s ease}.button:hover{background-color:#0056b3}

自动化压缩工具

PostCSS + cssnano

// postcss.config.js
module.exports = {
plugins: [
require('cssnano')({
preset: ['default', {
discardComments: {
removeAll: true,
},
normalizeWhitespace: true,
colormin: true,
minifyFontValues: true,
minifySelectors: true
}]
})
]
};

// package.json scripts
{
"scripts": {
"build:css": "postcss src/css/*.css --dir dist/css"
}
}

Gulp + clean-css

// gulpfile.js
const gulp = require('gulp');
const cleanCSS = require('gulp-clean-css');
const autoprefixer = require('gulp-autoprefixer');

gulp.task('minify-css', () => {
return gulp.src('src/css/*.css')
.pipe(autoprefixer({
cascade: false
}))
.pipe(cleanCSS({
compatibility: 'ie8',
level: {
1: {
all: true,
normalizeUrls: false
},
2: {
all: false,
removeDuplicateRules: true,
removeEmpty: true,
mergeNonAdjacentRules: true
}
}
}))
.pipe(gulp.dest('dist/css'));
});

JavaScript压缩

手动压缩示例

// 压缩前
function calculateTotal(items) {
let total = 0;
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.price && item.quantity) {
total += item.price * item.quantity;
}
}
return total;
}

function formatCurrency(amount) {
return new Intl.NumberFormat('zh-CN', {
style: 'currency',
currency: 'CNY'
}).format(amount);
}

// 压缩后
function calculateTotal(e){let t=0;for(let n=0;n<e.length;n++){const o=e[n];o.price&&o.quantity&&(t+=o.price*o.quantity)}return t}function formatCurrency(e){return new Intl.NumberFormat("zh-CN",{style:"currency",currency:"CNY"}).format(e)}

自动化压缩工具

Terser (推荐)

// terser.config.js
module.exports = {
compress: {
drop_console: true, // 移除console.log
drop_debugger: true, // 移除debugger
pure_funcs: ['console.log'], // 移除特定函数调用
passes: 2 // 压缩遍数
},
mangle: {
toplevel: true, // 混淆顶级作用域变量
reserved: ['$', 'jQuery'] // 保留特定变量名
},
output: {
comments: false, // 移除注释
beautify: false // 不美化输出
}
};

// 命令行使用
// npx terser input.js -o output.js -c -m

Webpack + TerserPlugin

// webpack.config.js
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
mode: 'production',
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log']
},
mangle: {
toplevel: true
},
output: {
comments: false
}
},
extractComments: false
})
]
}
};

图像压缩

图像格式对比

图像格式性能对比
┌─────────┬──────────┬──────────┬──────────┬──────────┬──────────┐
│ 格式 │ 压缩率 │ 质量 │ 浏览器支持 │ 透明度 │ 动画 │
├─────────┼──────────┼──────────┼──────────┼──────────┼──────────┤
│ JPEG │ 高 │ 中等 │ 优秀 │ 不支持 │ 不支持 │
│ PNG │ 中等 │ 高 │ 优秀 │ 支持 │ 不支持 │
│ WebP │ 很高 │ 高 │ 良好 │ 支持 │ 支持 │
│ AVIF │ 极高 │ 高 │ 中等 │ 支持 │ 支持 │
│ SVG │ 可变 │ 无损 │ 优秀 │ 支持 │ 支持 │
└─────────┴──────────┴──────────┴──────────┴──────────┴──────────┘

图像压缩工具

Sharp (Node.js)

// image-compress.js
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');

async function compressImages(inputDir, outputDir, quality = 80) {
const files = fs.readdirSync(inputDir);

for (const file of files) {
if (file.match(/\.(jpg|jpeg|png)$/i)) {
const inputPath = path.join(inputDir, file);
const outputPath = path.join(outputDir, file.replace(/\.(jpg|jpeg|png)$/i, '.webp'));

try {
await sharp(inputPath)
.webp({ quality })
.toFile(outputPath);

console.log(`压缩完成: ${file}${path.basename(outputPath)}`);
} catch (error) {
console.error(`压缩失败: ${file}`, error);
}
}
}
}

// 使用示例
compressImages('./src/images', './dist/images', 85);

响应式图像生成

// responsive-images.js
const sharp = require('sharp');

async function generateResponsiveImages(inputPath, outputDir, sizes) {
const filename = path.basename(inputPath, path.extname(inputPath));

for (const size of sizes) {
const { width, height, suffix } = size;
const outputPath = path.join(outputDir, `${filename}-${suffix}.webp`);

await sharp(inputPath)
.resize(width, height, {
fit: 'cover',
position: 'center'
})
.webp({ quality: 85 })
.toFile(outputPath);
}
}

// 使用示例
const sizes = [
{ width: 320, height: 240, suffix: 'small' },
{ width: 640, height: 480, suffix: 'medium' },
{ width: 1024, height: 768, suffix: 'large' }
];

generateResponsiveImages('./src/hero.jpg', './dist/images', sizes);

字体压缩

字体子集化

// font-subset.js
const fontkit = require('fontkit');
const fs = require('fs');

async function createFontSubset(fontPath, text, outputPath) {
const font = await fontkit.open(fontPath);

// 获取文本中使用的字符
const usedChars = new Set();
for (const char of text) {
usedChars.add(char);
}

// 创建子集字体
const subset = font.createSubset(Array.from(usedChars));
const buffer = await subset.encode();

fs.writeFileSync(outputPath, buffer);
console.log(`字体子集创建完成: ${outputPath}`);
}

// 使用示例
const text = '你好世界Hello World 1234567890';
createFontSubset('./fonts/NotoSansSC-Regular.otf', text, './dist/fonts/subset.otf');

字体优化CSS

/* 字体加载优化 */
@font-face {
font-family: 'CustomFont';
src: url('font-subset.woff2') format('woff2'),
url('font-subset.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap; /* 优先显示系统字体,避免闪烁 */
unicode-range: U+4E00-9FFF, U+0020-007F; /* 指定字符范围 */
}

/* 预加载关键字体 */
<link rel="preload" href="font-subset.woff2" as="font" type="font/woff2" crossorigin>

服务器端压缩配置

Nginx配置

# nginx.conf
http {
# 启用gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/xml+rss
application/json
image/svg+xml;

# 启用Brotli压缩(需要安装ngx_brotli模块)
brotli on;
brotli_comp_level 6;
brotli_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/xml+rss
application/json
image/svg+xml;

# 静态资源缓存
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary Accept-Encoding;
}
}

Express.js配置

// app.js
const express = require('express');
const compression = require('compression');
const app = express();

// 启用gzip压缩
app.use(compression({
level: 6, // 压缩级别
threshold: 1024, // 最小压缩大小
filter: (req, res) => {
// 只对特定类型的内容进行压缩
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
}
}));

// 静态文件服务
app.use(express.static('public', {
maxAge: '1y',
etag: true,
lastModified: true,
setHeaders: (res, path) => {
if (path.endsWith('.css') || path.endsWith('.js')) {
res.setHeader('Cache-Control', 'public, max-age=31536000');
}
}
}));

压缩效果测试

压缩前后对比

// compression-test.js
const fs = require('fs');
const path = require('path');

function analyzeCompression(inputDir, outputDir) {
const files = fs.readdirSync(inputDir);
let totalOriginal = 0;
let totalCompressed = 0;

console.log('压缩效果分析:');
console.log('文件名\t\t原始大小\t压缩后大小\t压缩率');
console.log('─'.repeat(60));

for (const file of files) {
const inputPath = path.join(inputDir, file);
const outputPath = path.join(outputDir, file);

if (fs.existsSync(outputPath)) {
const originalSize = fs.statSync(inputPath).size;
const compressedSize = fs.statSync(outputPath).size;
const compressionRatio = ((originalSize - compressedSize) / originalSize * 100).toFixed(1);

totalOriginal += originalSize;
totalCompressed += compressedSize;

console.log(`${file.padEnd(20)}\t${(originalSize/1024).toFixed(1)}KB\t\t${(compressedSize/1024).toFixed(1)}KB\t\t${compressionRatio}%`);
}
}

const totalRatio = ((totalOriginal - totalCompressed) / totalOriginal * 100).toFixed(1);
console.log('─'.repeat(60));
console.log(`总计\t\t${(totalOriginal/1024).toFixed(1)}KB\t\t${(totalCompressed/1024).toFixed(1)}KB\t\t${totalRatio}%`);
}

// 使用示例
analyzeCompression('./src', './dist');

最佳实践总结

压缩策略选择

  1. HTML: 移除注释、空白、冗余属性,压缩内联CSS/JS
  2. CSS: 移除注释、空白、合并选择器,优化值
  3. JavaScript: 混淆变量名、移除注释、死代码消除
  4. 图像: 选择合适的格式、压缩质量、响应式尺寸
  5. 字体: 子集化、WOFF2格式、预加载策略

压缩工具选择

  • 构建工具: Webpack、Rollup、Vite
  • 任务工具: Gulp、Grunt
  • 图像工具: Sharp、ImageOptim、TinyPNG
  • 字体工具: fonttools、fontkit
  • 服务器: Nginx、Apache、Express.js

性能监控

  • 使用Lighthouse测试压缩效果
  • 监控网络传输时间
  • 跟踪用户加载体验
  • 定期优化压缩策略

通过合理的资源压缩策略,可以显著减少文件大小,提升页面加载速度,改善用户体验。