图像引用优化

发布于 2026/05/22 | 个人博客的更新记录

记录个人博客中图像引用方式的一次集中整理。


这次调整统一文章了中的图片组件写法、去掉硬编码路径、把若干旧资源切换到 WebP

一、概要

相关提交参见feat: 优化图片引用

统一博客里的图片组件、取消硬编码路径和不一致的资源格式。

Github Commit
Github Commit

调整范围包括:

  • public/* (图片添加与路径规范化)
  • .gitignore (排除非WebP文件)
  • .vitepress/config.mts (优化vite引用alias)
  • collections/* (修改文集封面)
  • posts/* (修改文章图像引用)

二、原始方案问题

1. 图像样式不成组件

在之前的文章中,图像要么使用Markdown原生图片[](..),要么使用<img>标签,样式极其混乱,而且图像排版也很难看。

例如

html
<div class="media-grid">  <article class="media-card">    <img src="/Imgs/operating-sys-synchronize/单一实例死锁.png" alt="单一实例死锁"/>    <h3 class="media-title">单一实例包含环死锁</h3>  </article>  <article class="media-card">    <img src="/Imgs/operating-sys-synchronize/多实例死锁.png" alt="多实例死锁"/>    <h3 class="media-title">多实例包含环死锁</h3>  </article> <article class="media-card">    <img src="/Imgs/operating-sys-synchronize/多实例未死锁.png" alt="多实例未死锁"/>    <h3 class="media-title">多实例包含环但未死锁</h3>  </article></div>

使用CSS类名将样式应用到图片上,无论是在样式管理还是图片本身路径管理上都是潜在的石山生成器。

2. 图片路径硬编码

文章里大量直接写 /Imgs/... 或其他静态路径字符串。维护体验很差:

  • 资源迁移时需要手动逐篇替换
  • 图像引用无静态检查,导致部署后才发现的问题

3. 未使用WebP

旧文章里同时存在几种形式:

  • 原生 &lt;img&gt;
  • 原生 Markdown 图片
ts
const props = withDefaults(defineProps<{  src: string //图像文件路径  alt?: string   align?: Align //图像对齐方式  wrap?: boolean //文本环绕方式  caption?: string //脚注  captionLink?: string //链接  maxHeight?: number | string //最大高度(数字单位默认px,也可以使用字符串如32rem)}>(), {  alt: '',  align: 'center',  wrap: false,  caption: '',  captionLink: '',  maxHeight: '32rem',})
text
.├── GroupPhoto(Raw)-Convert│         ├── 乐队合照1.webp│         ├── 乐队合照2.webp│         ├── 乐队合照3.webp│         ├── 千禧年的孩子们.webp│         ├── 妮露和爱丽丝1.webp│         ├── 游戏开发部合照.webp│         ├── 游戏开发部合照2.webp│         ├── 爱丽丝和柯伊.webp│         ├── 联邦理事会长和老师.webp│         └── 阿洛娜与普拉娜.webp├── GroupPhoto(Raw)│         ├── 乐队合照1.PNG│         ├── 乐队合照2.PNG│         ├── 乐队合照3.PNG│         ├── 千禧年的孩子们.jpg│         ├── 妮露和爱丽丝1.PNG│         ├── 游戏开发部合照.jpg│         ├── 游戏开发部合照2.jpg│         ├── 爱丽丝和柯伊.PNG│         ├── 联邦理事会长和老师.jpg│         └── 阿洛娜与普拉娜.PNG
ts
export const path = {  // /Image/Miscellaneous/essay-openai-image-2  "海报": "/Image/Miscellaneous/essay-openai-image-2/海报.webp",  "漫画": "/Image/Miscellaneous/essay-openai-image-2/漫画.webp",  "漫画_翻嵌": "/Image/Miscellaneous/essay-openai-image-2/漫画_翻嵌.webp",  "阿洛娜与普拉娜": "/Image/Miscellaneous/essay-openai-image-2/阿洛娜与普拉娜.webp",  "阿洛娜与普拉娜_原图": "/Image/Miscellaneous/essay-openai-image-2/阿洛娜与普拉娜_原图.webp",  // /Image/Miscellaneous/operating-sys  cover: "/Image/Miscellaneous/operating-sys/cover.webp",  // /Image/Miscellaneous/operating-sys/synchronize  "单一实例死锁": "/Image/Miscellaneous/operating-sys/synchronize/单一实例死锁.webp",  "多实例未死锁": "/Image/Miscellaneous/operating-sys/synchronize/多实例未死锁.webp",  "多实例死锁": "/Image/Miscellaneous/operating-sys/synchronize/多实例死锁.webp",   // /Image/Miscellaneous/personal-profile-hobbies/Books  "发现的乐趣": "/Image/Miscellaneous/personal-profile-hobbies/Books/发现的乐趣.webp",  "娱乐至死": "/Image/Miscellaneous/personal-profile-hobbies/Books/娱乐至死.webp", 
ts
import { path as miscellaneousImagePath } from '@public/Image/Miscellaneous/path'
ts
  const mangaResultImage = {  src: miscellaneousImagePath['漫画_翻嵌'],  alt: '漫画翻嵌结果',  align: 'right',  wrap: true,  maxHeight: '28rem',  caption: '处理后:中文文本可读性明显提升,画面氛围基本保留。',} as const
tsx
  <Image {...mangaResultImage} />
联邦理事会长和老师
图像效果展示

这种写法使得

  1. 路径不再硬编码
  2. TypeScript 区域有正常高亮
  3. 通过Key引用图像,IDE支持静态扫描

3. build期间静态检查

截止上文的步骤结束,正确方式使用的图像组件已经具有了 IDE 高亮,意味着空引用会在编辑器中被提示。但如果只把文章当作 Markdown 渲染,pnpm build 仍然不会天然检查这些 TypeScript 片段。

text
/tmp/post-image-check-xxxx/posts_blog_change_log_01_to_cloudflare_md.ts:63:31 - error TS2339: Property 'errorTest' does not exist on type '{ readonly 海报: "/Image/Miscellaneous/essay-openai-image-2/海报.webp"; readonly 漫画: "/Image/Miscellaneous/essay-openai-image-2/漫画.webp"; readonly 漫画_翻嵌: "/Image/Miscellaneous/essay-openai-image-2/漫画_翻嵌.webp"; ... 37 more ...; readonly dns: "/Image/Miscellaneous/project-blog-to-cloudflare/dns.webp"; }'.63   src: miscellaneousImagePath.errorTest,                                 ~~~~~~~~~Found 1 error in posts/blog-change-log-01-to-cloudflare.md:63