图片优化与 SVG 工具工程化手册
一张图片为什么会拖垮发布链
活动页在设计机上完全正常,进入测试环境后却同时出现三种故障:手机首屏下载了一张 6000 像素宽的 PNG,品牌图从 Display P3 转成无配置 JPEG 后明显偏色,两个内联 SVG 的渐变都引用 id="paint",最终后出现的图覆盖了前一张图的定义。开发者把图片“压小”了,但系统仍然不知道输入是什么、转换改变了什么、输出是否还能安全地放进页面。
图片优化的工程目标不是追求最小字节数,而是把原始资产变成一个可验证制品。输入尺寸、方向、色彩配置、帧数和透明通道先被识别;转换规则再决定缩放、色彩归一与编码;输出随后经过视觉差异、安全扫描、体积预算和浏览器验证;最终以内容哈希命名进入构建和 CDN。任何一步失败,都应保留原始输入和证据,并阻止坏资产覆盖上一个可用版本。
先分清像素、画布与显示尺寸
位图把颜色记录在像素网格里。把 400×200 的截图拉到 1600×800,只会让插值算法猜出更多像素,不会找回文字边缘;SVG 保存的是路径、形状、渐变和文本等绘制指令,在不同显示尺寸上重新栅格化,适合图标、标志和结构图,却不适合直接承载照片级纹理。
浏览器中的 CSS 尺寸也不是文件像素。一个宽度为 600 CSS px 的图片,在设备像素比 DPR=2 的屏幕上,通常需要约 1200 个源像素才不会发虚;继续交付 4800 像素并不会让同一显示区域清晰四倍,只会增加解码内存与传输成本。解码后的 RGBA 位图可近似按 宽 × 高 × 4 字节估算,6000×4000 的图片即使压缩文件只有几 MB,展开后也接近百 MB。容量评估因此要同时看文件字节、像素总数、帧数和页面并发解码量。
网页资产常见选择可以先这样判断:
| 内容特征 | 首选 | 保守回退 | 容易犯的错 |
|---|---|---|---|
| 照片、连续色调 | AVIF 或 WebP | JPEG | 用 PNG 保存照片,体积巨大 |
| 截图、代码、细线文字 | PNG 或无损 WebP | PNG | 有损压缩让文字边缘出现振铃 |
| 图标、Logo、结构图 | SVG | PNG | 把不可信 SVG 当普通图片内联 |
| 需要透明的复杂位图 | WebP、AVIF 或 PNG | PNG | 转 JPEG 后透明区被错误填色 |
| 动画 | 按播放能力选择 WebP、AVIF、APNG | GIF 或静态封面 | 转换时只保留第一帧却未告警 |
MDN 的 Web 图片格式指南 给出了格式特性和浏览器回退方式。格式支持会随客户端基线变化,团队应从真实访问矩阵决定是否提供 <picture> 回退,而不是把“新格式更小”直接等同于“所有用户可用”。
安装 ImageMagick 与 SVGO
ImageMagick 7 的统一入口是 magick。Windows 可从官方 Windows 安装包或 winget 入口安装;官方列出的 winget 包包括 Q8、Q16 与 Q16-HDRI。Q16 能保留更高通道精度,但像素缓存成本高于 Q8;普通 Web 资产流水线通常不应仅因为“数字更大”就选择 Q16-HDRI,先用高位深或 HDR 输入做回归再决定。macOS 官方建议使用 Homebrew:
brew install imagemagick
magick -versionLinux 发行版仓库便于补丁管理,但版本和编译选项由发行版决定;官方提供 AppImage,也支持源码构建。无论从哪种入口安装,都要检查实际编译能力,而不是只检查命令存在:
magick -version
magick identify -list format | grep -E 'PNG|JPEG|WEBP|AVIF'
magick identify -list policyWindows PowerShell 可将筛选命令写成:
magick identify -list format | Select-String 'PNG|JPEG|WEBP|AVIF'后续示例中的行尾反斜杠是 POSIX shell 续行符。PowerShell 应改用反引号,或者把参数写在同一行;Windows 路径统一用双引号包裹。不要把 Bash 的单引号、通配符和退出码组合原样塞进 PowerShell 脚本,正式批处理应以参数数组调用进程,避免空格路径和用户输入被 shell 二次解释。
格式列表中的 R、W 分别表示可读和可写。ImageMagick 格式表明确说明部分编码依赖 delegate 库,所以开发机能输出 AVIF,不代表精简 CI 镜像也能输出。把 magick -version、目标格式行和 policy 摘要保存为 CI 诊断产物,能快速区分“参数写错”和“运行镜像缺编码器”。
SVGO 是 Node.js CLI 与库。个人全局安装便于试用,项目中应锁进开发依赖,让配置和版本跟随仓库:
npm install --save-dev svgo
npx svgo --version
npx svgo --helpSVGO 官方仓库同时给出了单文件、目录递归和 JavaScript API 用法。升级主版本前要阅读迁移说明和插件变更;优化插件会重写文档结构,不能把浮动的 npx svgo@latest 放进生产流水线。
第一次转换先保留输入证据
不要一上来使用 mogrify 覆盖源文件。先创建彼此隔离的目录:
assets/
source/ # 原始输入,只读保留
generated/ # 可重新生成的候选输出
reports/ # 元数据、差异和预算报告用 identify 读取一张图片:
magick identify -verbose assets/source/hero.jpg
magick identify -format "%f|%m|%wx%h|%[colorspace]|%[channels]|%n\n" \
assets/source/hero.jpg输出至少要解释文件魔数、宽高、色彩空间、通道和帧数。扩展名只是命名,%m 才来自识别到的格式;%n 大于 1 时要按动画或多页序列处理。identify -verbose 还能暴露 ICC、EXIF、方向和像素签名,官方 Identify 手册说明签名基于像素内容而不是元数据,适合判断两个不同编码文件是否解码为同一像素结果。
一条保守的照片转换可以这样开始:
magick assets/source/hero.jpg \
-auto-orient \
-resize "1600x1600>" \
-colorspace sRGB \
-strip \
-quality 82 \
assets/generated/hero.webp参数顺序有因果关系。-auto-orient 根据方向元数据旋转像素;随后缩放,避免对本来应竖放的画面按错误方向计算;1600x1600> 只缩小不放大,并保持比例;色彩归一后再 -strip,移除可能含设备、位置和拍摄信息的配置与 EXIF;-quality 最后影响编码器。质量值不是跨格式的统一评分,JPEG 的 82 与 WebP 的 82 不能直接横向比较。ImageMagick 选项手册对 JPEG、PNG、WebP 的 quality 语义分别有说明,WebP 编码参数还会影响 alpha、编码速度和近无损行为。
如果品牌资产依赖特定 ICC,不能先 -strip 再期待颜色保持。应先把源配置转换到团队约定的 sRGB 配置;输出契约若要求色彩管理,就在清理其他元数据后重新嵌入目标 ICC:
magick assets/source/brand-cmyk.tif \
-profile config/profiles/source-cmyk.icc \
-profile config/profiles/sRGB.icc \
-resize "1600x1600>" \
-strip -profile config/profiles/sRGB.icc assets/generated/brand.png只有源文件缺少配置且来源契约明确时,才能指定源空间;“声明为 RGB”和“转换为 RGB”并不是同一动作。ImageMagick 色彩管理说明区分了元数据声明与实际像素转换。若发布契约允许无 ICC 的 sRGB,则可以省略最后一个 -profile,但必须把这个约定写入解码与浏览器验证;不能把 -strip 后的无配置文件当作自动完成色彩管理。处理宽色域、印刷 CMYK、HDR 或灰度科学图像时,应保留受控测试图和目标设备验证,不要用一个 -colorspace sRGB 覆盖所有输入。
正向实验:把质量、尺寸和元数据变成证据
准备一张有拍摄方向和 EXIF 的测试照片,复制到 assets/source/hero.jpg。先生成候选,再读取输出事实:
magick assets/source/hero.jpg \
-auto-orient -resize "1600x1600>" -colorspace sRGB -strip \
-quality 82 assets/generated/hero.webp
magick identify -format \
"format=%m width=%w height=%h colorspace=%[colorspace] channels=%[channels] bytes=%b\n" \
assets/generated/hero.webp
magick identify -verbose assets/generated/hero.webp > assets/reports/hero-identify.txt正向结果不是笼统的“图片能打开”,而应同时满足:格式是当前环境实际支持的 WebP;长边不超过约定上限;方向已经落入像素,输出不再依赖 EXIF orientation;色彩空间与 ICC 策略符合发布约定;敏感 EXIF 已清除,但需要保留的 ICC 或版权字段仍在;透明通道是否保留与输入契约一致。最后在支持矩阵中的浏览器查看 <picture> 候选与回退,并用截图或视觉回归确认构图、文字边缘、肤色、渐变和透明边缘。
有损输出还要与一个同尺寸、同色彩空间的无损参考比较:
magick assets/source/hero.jpg \
-auto-orient -resize "1600x1600>" -colorspace sRGB -strip \
assets/generated/hero-reference.png
magick compare -metric SSIM \
assets/generated/hero-reference.png assets/generated/hero.webp null:compare 的指标通常写到标准错误流,CI 要同时采集退出码与该输出。SSIM、PSNR 或感知差异只能发现变化,不能替代业务判定:头像皮肤、产品文字、二维码和 UI 细线的容忍度不同。团队应从已通过人工复核的基线样本建立每类资产阈值,并观察趋势;不要复制一个脱离编码器版本、分辨率和内容类型的“万能分数”。
实验结束可删除 generated 和 reports,原始素材是否保留则按授权与保留期执行:
rm -rf assets/generated assets/reportsPowerShell 对应使用 Remove-Item -Recurse -Force,执行前先用 Resolve-Path 确认目标位于实验目录。清理命令不应被自动化拼接到未经校验的用户路径。
反向实验:更小的文件也可能是不合格制品
把质量压到极低,故意制造文字边缘和渐变断层:
magick assets/generated/hero-reference.png \
-quality 20 assets/generated/hero-bad.jpg
magick compare -metric SSIM \
assets/generated/hero-reference.png assets/generated/hero-bad.jpg \
assets/reports/hero-bad-diff.png预期 hero-bad.jpg 可能明显更小,但 SSIM 下降,差异图在高对比边缘、细字和渐变区域出现密集变化;放大查看还会看到块效应、振铃或色度抽样造成的彩边。门禁应拒绝这个候选,并把原始大小、候选大小、指标、工具版本和差异图一并保存。若命令报 no encode delegate,第一证据应转向 magick identify -list format,这说明 CI 镜像没有相应写入能力,不是图片质量不合格。
透明与动画要另外做破坏实验。把透明 PNG 输出为 JPEG 时,先显式选择背景,否则不同实现或默认值会产生黑边、白边或不可预测底色:
magick assets/source/logo-transparent.png \
-background white -alpha remove -alpha off \
assets/generated/logo-on-white.jpg如果设计要求透明,就不应走 JPEG 分支。对于动画,先检查帧数、每帧延时和循环信息;流水线若只支持静态图,应在帧数大于 1 时失败,而不是静默取第一帧。动画解码成本近似由各帧像素与持续时间共同决定,文件小并不代表 CPU 和内存成本低。
用 SVGO 优化,但不要破坏语义
在项目根目录创建 svgo.config.mjs:
export default {
multipass: true,
plugins: [
{
name: 'preset-default',
params: {
overrides: {
cleanupIds: false,
},
},
},
'removeScripts',
'prefixIds',
],
};preset-default 会组合多种清理和路径优化插件;multipass 让优化结果再次进入优化,可能换来更小文件,也会增加构建时间。当前 preset 并不默认启用 removeViewBox,所以配置里不再写一个会产生警告的无效 override;需要移除 viewBox 时必须单独启用对应插件并做响应式回归。prefixIds 不使用固定全局前缀,而是让 SVGO 根据输入文件路径生成可复现前缀;如果通过 JS API 调用 optimize,必须传入 path。ID 策略必须根据使用方式决定:作为 <img src> 的独立文档彼此隔离,内联到同一 HTML 的 SVG 却共享文档 ID 空间。两个文件都把渐变压成 id="a" 时,url(#a) 可能引用错误定义。SVGO 的 cleanupIds 文档明确提示多个内联 SVG 的冲突风险,prefixIds用于给 ID 与 class 加前缀,并建议使用文件名等可复现种子。
运行单文件和目录批处理:
npx svgo assets/source/icon.svg -o assets/generated/icon.svg
npx svgo --recursive --folder assets/source/icons \
--output assets/generated/icons不要让输入和输出指向同一个受版本控制目录,除非团队明确接受源码被重写。优化后至少检查 XML 可解析、viewBox 存在、外部引用和脚本策略、安全扫描通过,并在真实背景色、缩放比例和内联方式下渲染。
SVG 安全不是“删除 script 标签”这么简单
SVG 可以包含 <script>、onload/onclick 等事件属性、javascript: URI、<foreignObject>、外部图片、字体、样式和 <use href>。通过 <img> 加载、作为 CSS background、内联 DOM 或直接访问 SVG URL,浏览器安全上下文也不同。服务端上传不能仅凭 MIME 或后缀把 SVG 当无害图片。
SVGO 的 removeScripts 插件明确负责移除 <script>、SVG 事件属性并折叠 <a>;它不是完整的不可信内容沙箱,也不能替代对 javascript: URI、外部 href、<foreignObject>、外部图片/字体/样式和危险协议的独立白名单检查。面向用户上传时更稳妥的路线是:拒绝 SVG,或在隔离服务中按严格白名单解析并重新序列化;若业务只需要预览,可栅格化为受支持位图,再从独立资源域以正确 Content-Type、X-Content-Type-Options: nosniff 和受限 CSP 提供。转换进程不应拥有应用凭证、仓库写权限或内网访问能力。
一个最小安全反例可以验证配置是否真的移除活动内容:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 40"
onclick="alert(1)">
<rect id="paint" width="100" height="40" fill="#08a"/>
<script>alert('unsafe')</script>
</svg>执行 SVGO 后扫描:
npx svgo --config svgo.config.mjs unsafe.svg -o safe.svg
if grep -Eina '<script|\son[a-z]+=|javascript:|<foreignObject|<image[^>]+(href|xlink:href)=["'"']https?://' safe.svg; then
echo 'active content or external reference detected' >&2
exit 1
fi
grep -En 'viewBox=' safe.svg预期安全扫描无命中且 viewBox 仍存在。若 script、事件属性、危险协议或外部引用命中,立即拒绝制品;若图形消失,则比较优化前后的浏览器渲染和引用关系,检查插件是否删除了通过 CSS、ARIA、动画或外部代码使用的 ID。Windows 可用 Select-String 完成同类扫描,但正式门禁应采用 XML 解析器和明确的 URI/元素/属性允许集,正则只适合暴露已知反例。
批处理必须确定输入、输出与失败语义
项目可以把版本与配置锁进 package.json:
{
"devDependencies": {
"svgo": "4.0.2"
},
"scripts": {
"assets:svg": "svgo -rf assets/source/icons -o assets/generated/icons",
"assets:verify": "node scripts/verify-assets.mjs",
"assets:build": "npm run assets:svg && npm run assets:verify"
}
}提交 package-lock.json 并用 npm ci 安装,不要把 npx svgo@latest 放入构建脚本。位图批处理不要依赖 shell 的通配符与路径规则跨平台一致。用 Node.js 枚举允许扩展名,按规范化路径生成输出,使用参数数组调用 magick,并拒绝输出逃逸到目标目录之外。每个输入记录 SHA-256、源尺寸、帧数和 profile;每个输出记录工具版本、规则版本、格式、尺寸、字节数、像素签名和质量指标。只有全部任务成功后,才以目录重命名或制品发布动作一次性切换候选版本。
并行度不能直接等于 CPU 核数。ImageMagick 的像素缓存可能从内存降级到内存映射和磁盘,一个大图任务同时占用源像素、目标像素与中间缓冲;过度并发会让 CI 磁盘抖动甚至耗尽。先用代表性最大图片测量单任务峰值 RSS、临时磁盘与耗时,再据执行器预算设置 worker 数。批次失败时停止接收新任务,等待已启动进程退出,删除候选目录,不删除已发布目录。
构建、路径、缓存与 CDN 必须一起设计
源码中引用稳定逻辑名,构建产物使用内容哈希,例如 hero.6f3a2c.webp。HTML 或 manifest 指向新哈希后,CDN 才能对哈希资源使用长缓存;同一个 URL 覆盖内容会让浏览器、CDN 节点和服务端同时持有不同版本。回滚时恢复旧 HTML/manifest 即可重新引用仍在保留期内的旧哈希文件,不需要等待全网 purge。
响应式图片应交付真实候选,而不是把同一个大图重复写进 srcset:
<picture>
<source type="image/avif"
srcset="/assets/hero-800.ab12.avif 800w, /assets/hero-1600.cd34.avif 1600w">
<source type="image/webp"
srcset="/assets/hero-800.ef56.webp 800w, /assets/hero-1600.789a.webp 1600w">
<img src="/assets/hero-1600.4567.jpg"
srcset="/assets/hero-800.1234.jpg 800w, /assets/hero-1600.4567.jpg 1600w"
sizes="(max-width: 800px) 100vw, 800px"
width="1600" height="900" alt="产品工作台">
</picture>width 与 height 提供固有比例,减少布局偏移;sizes 要与真实布局匹配,否则浏览器仍可能下载过大的候选。发布验证应从页面 Network 面板记录实际选中的 URL、响应 MIME、缓存头、传输字节和解码尺寸。路径 404 时先看 manifest 与 base path,再看 CDN 回源和大小写;本地 Windows 文件系统不敏感的大小写错误,可能只在 Linux 构建或对象存储上暴露。
原始资产、候选产物和线上制品需要不同保留策略。原始授权素材通常不能公开;generated 可以由源码和工具链重建;线上哈希制品至少保留到所有可回滚发布退出窗口。CDN purge 不是回滚方案,只是缓存操作,而且会制造回源峰值和额外成本。
因此,优化流水线的决策顺序不是“先压缩,再看看能不能用”,而是先证明输入允许被解析,再按位图与 SVG 的不同攻击面处理,最后才比较质量与体积。任一门禁失败都只丢弃候选,不触碰当前线上哈希;发布后的异常则通过引用回退恢复旧制品。
图中的“重新序列化”要求真正使用 XML 解析器和允许集,而不是用正则替换几段文本;“栅格化”也必须运行在无应用凭证、无内网访问、资源受限的进程中。元数据门禁按资产契约判断保留与清除项,质量门禁则同时看可解码性、视觉不变量和页面预算。这样,安全失败不会被压缩率掩盖,质量失败也不会迫使团队覆盖已经验证的线上文件。
质量门禁看不变量,不迷信单一压缩率
一套可执行门禁至少应产生这些判定:
输入扩展名、魔数和允许格式一致,像素总量、宽高与帧数未超过该资产类型预算。输出长宽、比例、方向、色彩空间、alpha 和动画策略符合清单;输出能被完整解码,不是截断文件。元数据策略生效:需要保留的 ICC 或版权字段仍在,需要清除的 EXIF、GPS、缩略图和评论已消失。
有损候选相对同尺寸无损参考通过分类阈值,并保留差异图;关键图还通过人工或视觉回归抽检。SVG 可解析,保留响应式与无障碍所需语义,不含禁止元素、属性、协议和外链,内联 ID 不冲突。单文件、页面总量和变化趋势处于预算内;新增一个巨大候选不能靠删除无关图片抵消。
预算数字应来自页面性能目标、真实网络分布和基线样本。可先以“候选不得比同用途已批准基线显著变大”“移动首屏图片总字节不持续上升”“最大解码像素和帧数受控”建立趋势门禁,再逐步按 hero、缩略图、图标、截图分类。仅限制 Git 文件大小会漏掉页面总量和解码峰值;仅看 Lighthouse 分数又难以定位到具体资产。
故障证据如何反推到处理阶段
| 现象 | 第一证据 | 常见根因 | 修复与复验 |
|---|---|---|---|
CI 提示 no encode delegate | identify -list format、magick -version | 构建镜像没有目标编码器 | 固定镜像与 delegate,重新转换并解码输出 |
| 图片方向错误 | 输入 orientation、输出像素宽高 | -strip 早于 -auto-orient | 先旋转像素再删元数据 |
| 品牌色偏或灰图发暗 | 输入/输出 ICC、colorspace、测试图 | 把“声明色彩空间”误当转换,或提前删除 profile | 用受控 ICC 转换,跨浏览器与目标屏复验 |
| 透明边缘出现黑边 | alpha 通道、背景合成结果 | 有损格式或错误预乘 alpha | 保留 alpha,或在目标背景上显式合成 |
| 动画变静态 | 输入与输出帧数、delay、iterations | 管道只读取第一帧 | 明确拒绝或使用支持动画的编码分支 |
| 两个内联图渐变串色 | DOM 中的 ID 与 url(#...) | 分别优化后生成相同短 ID | 启用确定性前缀并做同页渲染测试 |
| SVG 优化后图形缺失 | 优化前后 XML、浏览器截图、引用 ID | 插件删除外部 CSS/脚本依赖的 ID 或元素 | 保留 ID、调整插件并新增回归样本 |
| 大图任务拖死执行器 | 像素数、帧数、RSS、临时盘、policy | 压缩文件很小但解码后巨大 | 读取前限额,限制资源和并行度 |
| CDN 仍显示旧图 | 请求 URL、ETag/Age、manifest | 稳定 URL 被覆盖或节点未刷新 | 内容哈希换址,回滚 manifest 而非覆盖文件 |
排障顺序是输入事实、工具能力、转换中间态、输出事实、发布引用、客户端选择。跳过前几层直接“清缓存”,往往只会暂时掩盖不确定流水线。
不可信图片要在受限进程中处理
图片解析器面对的是复杂二进制输入。上传服务应先限制请求体,再读取尺寸与帧数,并在隔离进程中转换;不要让 Web 进程以高权限直接执行任意格式和任意路径。不同发行版和构建包的 ImageMagick policy 可能不同,不能假设默认值已经适合不可信输入。官方 Security Policy建议按环境限制 coder、delegate、路径、时间、内存、磁盘、面积、宽高和序列长度,并强调最后匹配规则生效。
下面只是面向常见 Web 位图的策略骨架,阈值必须由执行器容量验证:
<policymap>
<policy domain="resource" name="thread" value="2"/>
<policy domain="resource" name="time" value="60"/>
<policy domain="resource" name="memory" value="256MiB"/>
<policy domain="resource" name="map" value="512MiB"/>
<policy domain="resource" name="disk" value="1GiB"/>
<policy domain="resource" name="area" value="16KP"/>
<policy domain="resource" name="width" value="4KP"/>
<policy domain="resource" name="height" value="4KP"/>
<policy domain="resource" name="list-length" value="16"/>
<policy domain="delegate" rights="none" pattern="*"/>
<policy domain="path" rights="none" pattern="@*"/>
<policy domain="module" rights="none" pattern="*"/>
<policy domain="module" rights="read | write" pattern="{JPEG,PNG,WEBP,AVIF}"/>
</policymap>部署后用 magick identify -list policy 证明实际加载了目标策略,再用一个超宽图、超长序列、间接读取和禁止格式分别做反向实验。预期进程以可识别的 policy/resource 错误退出,临时文件被清理,服务返回受控错误而不是继续重试。某个环境没有 AVIF delegate 时,即使策略允许也不能编码;策略是授权上限,不会创造能力。
EXIF 可能包含 GPS、设备、拍摄时间和内嵌缩略图;SVG 外链可能把访问者请求发送到第三方;构建日志可能打印本地素材路径。日志只保留资产 ID、哈希、尺寸、规则版本和错误分类,不打印上传者路径、完整元数据或带签名的对象存储 URL。对象存储凭证使用短期身份,只允许读取隔离输入前缀、写入候选前缀,发布动作使用另一个最小权限身份。
跨平台一致性来自锁定工具链与黄金样本
Windows、macOS 和 Linux 的 ImageMagick 包可能拥有不同 quantum depth、HDRI、字体、librsvg 和编码 delegates,同一 quality 在编码器升级后也可能产生不同字节。不能要求所有输出哈希永远跨平台一致,却可以让正式制品只由固定容器或固定 CI 镜像生成,并用黄金样本约束可观察结果。
黄金样本至少包含:带 EXIF 方向的照片、CMYK/ICC 图片、透明边缘、细字截图、宽色域渐变、多帧动画、带 viewBox 和渐变 ID 的 SVG、含脚本和外链的恶意 SVG、超大尺寸与超长序列。每次工具或配置升级先在候选分支运行,比较尺寸、元数据、感知指标、差异图、浏览器截图、峰值资源和耗时。只有预期变化被复核后,才更新基线与锁文件。
字体参与 SVG 栅格化时,缺字体会改变换行和字形;Ghostscript、librsvg 等 delegate 也扩大供应链与攻击面。若输出必须确定,优先把授权允许的文字转路径,或固定字体文件与渲染镜像;同时记录许可证,避免“为了构建稳定”把不可再分发字体塞进公开仓库。
容量、成本与长期治理
图片链的成本分为仓库存储、CI 编码 CPU、临时磁盘、对象存储、备份、CDN 传输、缓存未命中和浏览器解码。AVIF 可能降低传输,却增加编码时长;保留三种格式和多档尺寸会放大对象数量;对每次提交全量重编码会浪费 CI。使用“源哈希 + 规则版本 + 工具链版本”作为缓存键,只重建变化资产,同时让缓存命中不能绕过验证报告。
每类资产要有 owner 和处理契约:设计或内容团队负责来源、授权与视觉验收,开发团队负责引用和响应式布局,平台团队维护可复现镜像、缓存与门禁,安全团队维护不可信输入策略。紧急例外必须绑定具体资产哈希、风险 owner、退出条件和到期检查点,不能以“先上线”永久跳过 SVG 安全或像素上限。
治理看板应观察源资产与生成资产总量、每次发布新增字节、页面图片传输分位、CDN 命中率、重编码耗时、缓存命中、质量拒绝、policy 拒绝、失败格式和孤儿制品。数值异常要能回到资产 ID、输入哈希和规则版本。删除资产时先证明没有 HTML、CSS、manifest 和历史可回滚版本引用,再从 CDN、对象存储和备份保留链分阶段清理。
工具退场也要可执行:仓库保留原始输入、开放格式、转换配置、manifest 模型、黄金样本和验证规则;先用替代编码器并行生成候选,比较视觉与运行成本,再切换 manifest;旧制品保持只读直到回滚窗口关闭,最后撤销构建身份和清理缓存。只留下某个平台生成的二进制、没有来源与规则记录,团队就无法证明新工具输出仍是同一项资产。
当新人能从一张原图生成可用候选,维护者能从失败报告定位到输入、delegate、配置、质量或缓存层,架构师能解释格式、资源、安全和成本取舍,并且任一版本都能通过旧哈希回退时,图片优化才真正成为交付能力,而不是一次性的压缩动作。
