静态站点资源体积审计与图片自动压缩流水线
背景
校园景点介绍页面以大图、轮播图为主,经常遇到几个问题:
1. 设计师/运营直接上传原图,单张图片几MB,页面加载慢;
2. 压缩标准不统一:有的用WebP、有的JPG,分辨率参差不齐;
3. 无法提前感知本次打包后资源总大小,等到上线才发现首屏体积超标;
4. 压缩动作靠手动,容易漏处理新增图片,增量发布时无法自动识别哪些图片需要重压缩。
前面我们已经实现了资源路径校验、环境隔离、manifest增量比对,现在把资源体积审计 + 图片自动压缩前置到构建阶段,作为CI卡点。
核心思路
整体分为两个阶段:
1. 审计阶段:扫描资源目录,统计各类型文件大小,输出体积报表,超过阈值给出告警;
2. 压缩阶段:对图片进行无损/有损压缩,自动生成WebP兼容格式,保留原图用于归档;
约束:只处理图片资源,js/css不做混淆压缩(简单站点场景);审计结果写入报告,可作为CI流水线产物。
依赖包: sharp (高性能图片处理)、 filesize (友好大小格式化)
bash
npm install sharp filesize --save-dev
实现脚本
1. 资源审计脚本 audit-assets.js
javascript
const fs = require('fs');
const path = require('path');
const filesize = require('filesize');
const distDir = path.resolve(__dirname, './dist');
const LIMITS = {
total: 8 * 1024 * 1024, // 全站静态资源上限 8MB
singleImage: 500 * 1024 // 单张图片上限 500KB
};
const imageExt = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
let totalSize = 0;
const oversizedList = [];
const report = [];
function scan(dir) {
const entries = fs.readdirSync(dir);
for (const entry of entries) {
const full = path.join(dir, entry);
const stat = fs.statSync(full);
if (stat.isDirectory()) {
scan(full);
continue;
}
const ext = path.extname(full).toLowerCase();
totalSize += stat.size;
report.push({ path: full, size: stat.size });
if (imageExt.includes(ext) && stat.size > LIMITS.singleImage) {
oversizedList.push({ path: full, size: stat.size });
}
}
}
scan(distDir);
const totalOk = totalSize < LIMITS.total;
const singleOk = oversizedList.length === 0;
console.log(`📊 资源总大小:${filesize(totalSize)}`);
if (!totalOk) console.warn('❌ 警告:全站资源超出总阈值!');
if (oversizedList.length > 0) {
console.warn(`❌ 发现 ${oversizedList.length} 张超大图片:`);
oversizedList.forEach(item => console.warn(` ${item.path} ${filesize(item.size)}`));
}
// 输出审计报告
fs.writeFileSync('./dist/asset-audit.json', JSON.stringify({
totalSize,
totalLimit: LIMITS.total,
pass: totalOk && singleOk,
oversizedList,
report
}, null, 2));
// CI 阻断:不通过直接退出非0码
if (!totalOk || !singleOk) {
process.exit(1);
}
2. 图片自动压缩脚本 compress-images.js
javascript
const fs = require('fs');
const path = require('path');
const sharp = require('sharp');
const srcDir = path.resolve(__dirname, './src/images');
const outDir = path.resolve(__dirname, './dist/images');
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
const imageExt = ['.jpg', '.jpeg', '.png'];
const quality = 75;
async function compressImage(file) {
const ext = path.extname(file).toLowerCase();
const baseName = path.basename(file, ext);
const inputPath = path.join(srcDir, file);
const outputJpg = path.join(outDir, `${baseName}.jpg`);
const outputWebp = path.join(outDir, `${baseName}.webp`);
await sharp(inputPath)
.resize({ width: 1600, withoutEnlargement: true })
.jpeg({ quality })
.toFile(outputJpg);
await sharp(inputPath)
.resize({ width: 1600, withoutEnlargement: true })
.webp({ quality })
.toFile(outputWebp);
console.log(`✅ 已处理:${file}`);
}
async function run() {
const files = fs.readdirSync(srcDir);
for (const f of files) {
const ext = path.extname(f).toLowerCase();
if (imageExt.includes(ext)) {
await compressImage(f);
}
}
console.log('🎉 图片压缩全部完成');
}
run().catch(e => {
console.error('压缩失败:', e);
process.exit(1);
});
流水线编排顺序(CI Job)
plaintext
1. 资源路径白名单校验脚本
2. 图片自动压缩脚本
3. HTML/CSS/JS打包复制
4. 资源审计脚本(卡点,失败直接阻断发布)
5. 生成manifest清单
6. manifest差异比对,生成增量上传列表
7. 增量上传静态资源
页面中使用 picture 标签做WebP降级,兼容老旧浏览器:
html
<picture>
<source srcset="images/scene.webp" type="image/webp">
<img src="images/scene.jpg" alt="校园景点">
</picture>
工程权衡
✅ 收益
- 新增图片自动压缩,避免运营上传原图拖慢页面;
- 审计卡点提前拦截超大体量资源,上线前发现问题;
- 同时输出WebP,大幅降低图片流量,对手机端访问友好;
- 审计报告和manifest清单可以一起归档,每次发布可追溯资源体积变化。
⚠️ 风险与取舍
- sharp 依赖原生模块,部分CI环境需要预装依赖;
- 有损压缩质量75是折中值,重要实景照片可以单独调高质量;
- 已经手动优化好的图片,可增加 .skip-compress 标记跳过处理;
- 审计阈值可区分测试环境、公网环境,测试环境阈值放宽。
和之前模块联动
- 和manifest增量脚本:压缩后图片hash会变更,自动纳入增量上传列表;
- 和资源白名单校验:压缩输出目录同样经过路径规则校验;
- 和环境变量隔离:不同环境可配置不同的图片压缩质量、体积阈值。
后续演进方向
1. 接入图片内容简单检测:识别超大空白图、重复图片;
2. 增加LCP指标模拟预估,不只是看文件大小;
3. 支持图片懒加载属性自动注入,不用手动改HTML;
4. 把审计报告输出成Markdown,直接作为CI评论推送到仓库。
- 点赞
- 收藏
- 关注作者
评论(0)