引言
Vite 里的静态资源(图片、字体、SVG、Worker)有一套看似自动、实则有很多开关的处理管线:为什么小图会被内联成 Base64、大图会被加 hash、public/ 下的文件又完全不处理?本文把 Vite 的资产管线讲透:先讲 assetsInlineLimit 与资源哈希的内联/外链决策,再讲图片的正确姿势(publicDir vs import、懒加载、压缩),接着讲字体(格式、子集化、font-display)、SVG(文件 vs 组件 vs 雪碧图)、Worker 的加载(new Worker 的 Vite 原生支持、模块 Worker),最后给一套资源治理清单与常见排查。
前置:/vite-build-optimization/(资源哈希与产物优化)、/vite-config-guide/(
publicDir/build.assetsInlineLimit配置)。构建产物视角见 [[frontend]]。
目录
- 1. 资源管线的两条路:publicDir 与 import
- 2. 内联 vs 外链:assetsInlineLimit 与资源哈希
- 3. 图片处理:压缩、懒加载与响应式
- 4. 字体:格式、子集化与 font-display
- 5. SVG:文件、组件与雪碧图
- 6. Worker:Vite 原生 Worker 加载
- 7. import.meta.url 与运行时资源引用
- 8. 资源治理清单与性能预算
- 9. 常见问题排查
- 10. 速查表与一句话记忆
- 延伸阅读
1. 资源管线的两条路:publicDir 与 import
Vite 对静态资源有两条完全不同的管线,先分清:
| 方式 | 处理 | 何时用 |
|---|---|---|
public/ 目录 | 原样拷贝、路径用 /xxx.png、无 hash | 图标、favicon、无需版本控制的小文件 |
import img from './x.png' | 走构建:hash、内联决策、压缩、可树摇 | 组件内引用的业务资源 |
关键差异:
public/ 下的文件:
- 不进构建、不加 hash → 文件名变时要手动同步缓存策略
- 无法 tree-shake、无法按需加载
- 路径固定为 /xxx,适合 favicon、robots.txt、公开静态
import 的资源:
- 生成内容 hash(img-3f4k2a.png)→ 缓存友好(immutable)
- 可懒加载、可参与代码分割
- 超过 assetsInlineLimit 才外链,否则内联
// 正确:业务资源用 import
import logo from "./assets/logo.png";
<img src={logo} alt="logo" />;
// 仅当确实要"绝对路径直出"才用 public/
// <img src="/logo.png" />
记忆:
public/是"原样直出",import是"进管线加工"——业务资源一律 import,公开/一次性资源才放 public。
2. 内联 vs 外链:assetsInlineLimit 与资源哈希
build.assetsInlineLimit(默认 4096 字节 = 4KB):小于阈值 → 内联成 Base64 data URI;大于 → 输出独立文件并加 hash。
// vite.config.js
export default {
build: {
assetsInlineLimit: 4096, // 4KB 以下内联
assetsDir: "assets", // 产物目录
},
};
内联的权衡:
内联(data URI):
+ 少一次 HTTP 请求
- HTML/JS/CSS 变大、无法缓存、破坏 CSS/JS 的缓存命中
外链(独立文件):
+ 可独立缓存(immutable hash)、可并发下载
- 多一次请求
工程建议:SVG 图标与超小图标适合内联(几 KB、重复使用率高、避免雪碧图复杂度);照片/大图走外链 + 懒加载。别无脑把阈值调高到"全内联"——HTML 体积会失控。
资源哈希与缓存:
产物名带 hash(logo-a1b2c3.png)
→ 内容变了 hash 变 → 浏览器重新拉取
→ 内容没变 hash 不变 → 长缓存(immutable)
// 静态资源哈希在生产产物里的形态
// dist/assets/logo-a1b2c3d4e5.png
记忆:内联省请求但撑大 HTML、外链可缓存但多请求——SVG/小图标内联、照片大图外链;hash 让"内容不变缓存不破"。
3. 图片处理:压缩、懒加载与响应式
① 压缩(降体积的第一步):Vite 不做图片压缩,交给构建插件或 CDN:
// vite-plugin-imagemin / vite-plugin-image-optimizer
import imageOptimizer from "vite-plugin-image-optimizer";
export default {
plugins: [imageOptimizer({ png: { quality: 80 }, jpeg: { quality: 75 } })],
};
② 懒加载(Lazy Loading):首屏外图片用 loading="lazy" + 占位:
<img src={bigImg} loading="lazy" alt="" />
③ 响应式图片:srcset + 现代格式:
<img
src={img}
srcSet={`${img} 1x, ${img2x} 2x`}
sizes="(max-width: 600px) 100vw, 50vw"
alt=""
/>
<!-- 现代格式:AVIF/WebP 更小,回退 JPEG -->
<picture>
<source srcSet="hero.avif" type="image/avif" />
<source srcSet="hero.webp" type="image/webp" />
<img src="hero.jpg" alt="hero" />
</picture>
④ 体积预算:图片是页面体积大头——首屏图片 ≤ 150KB、全页 ≤ 500KB 是常见红线(见 /vite-build-optimization/ 的性能基线)。
格式选型:
| 格式 | 特点 | 何时用 |
|---|---|---|
| WebP | 有损/无损、动画 | 通用现代首选 |
| AVIF | 压缩率最高 | 支持时用(性能敏感) |
| PNG | 无损、透明 | 图标、截图 |
| JPEG | 照片、有损 | 照片 |
记忆:图片三步——压缩(插件/CDN)、懒加载(首屏外)、响应式(srcset/现代格式);预算红线是首屏 ≤150KB。
4. 字体:格式、子集化与 font-display
字体是"隐形重量"——动辄几 MB,加载策略决定感知性能。
① 格式:现代只发 woff2(压缩率最好),加 woff 兜底:
@font-face {
font-family: "MyFont";
src: url("/fonts/my.woff2") format("woff2"),
url("/fonts/my.woff") format("woff");
font-display: swap; /* 关键:文本先渲染、字体后换 */
}
② 子集化(font subsetting):中文字体含几千字符,只保留用到的字符可减 90%+:
// vite-plugin-subfont 或本地工具(glyphhanger/pyftsubset)
// 只保留常用中文 6763 常用字 / 页面实际用字
# pyftsubset 示例:保留 "前端构建" 等字
pyftsubset my.woff2 --text="前端构建Vite资源" --output-file=subset.woff2
③ font-display 策略:
swap → 文本先显示系统字体,字体加载后替换(最常用,不阻塞)
block → 白屏等待字体(FOUC 少但慢)
optional → 网络差就不等字体,用系统字体
④ 加载优化:用 link rel="preload" + crossorigin,配合 CSS 里统一 @font-face:
<link rel="preload" href="/fonts/my.woff2" as="font" type="font/woff2" crossorigin />
记忆:字体四件套——只发 woff2、按需子集化、font-display swap、preload 提前加载。
5. SVG:文件、组件与雪碧图
SVG 三种使用姿势:
| 方式 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| 文件引用 | <img src="/icon.svg"> | 简单、缓存 | 无法改色/交互 |
| 组件化 | import Icon from "./icon.svg"(vite-plugin-svgr) | 可传 props 改色、React/Vue 内联 | 每处内联重复 |
| 雪碧图 | 合成 sprite.svg + <use> | 一次加载、多图标 | 构建复杂 |
组件化(vite-plugin-svgr):
import { ReactComponent as Logo } from "./logo.svg";
// 可传 color/className,随框架走
<Logo color="var(--primary)" className="logo" />;
雪碧图(sprite):
<svg>
<use href="/sprite.svg#icon-home"></use>
</svg>
关键:SVG 默认会内联吗? 小于 assetsInlineLimit 的 SVG 会内联成 data URI——内联的 SVG 无法改色(data URI 形式),所以"可改色图标"要么组件化、要么外链文件。
工程建议:
- 静态图标(不改色)→ 外链或雪碧图
- 交互/改色图标 → vite-plugin-svgr 组件化
- 小批量图标 → 内联(省请求)
记忆:SVG 三选一——静态用文件/雪碧、交互用组件化;内联的 SVG 改不了色,别用它做主题图标。
6. Worker:Vite 原生 Worker 加载
Vite 原生支持 new Worker()——自动打包 Worker 脚本并正确处理 hash 与加载路径:
// 正确:Vite 会打包 worker 文件
const worker = new Worker(new URL("./my-worker.js", import.meta.url));
// worker 里也能用模块语法(type: "module")
const worker = new Worker(new URL("./worker.ts", import.meta.url), {
type: "module",
});
Vite 会为 Worker 生成独立 chunk,与主代码分开打包——主线程引用 Worker 时自动加 hash。
Worker 与 WASM 的组合(WASM 见 /vite-worker-wasm/):
// 在 Worker 内加载 WASM(避免阻塞主线程)
// worker.ts
import wasmUrl from "./heavy.wasm?url"; // 拿到 WASM 的 URL
WebAssembly.instantiateStreaming(fetch(wasmUrl));
Worker 加载的注意点:
- new URL(x, import.meta.url) 是"相对当前模块"解析(不要写死路径)
- Worker 数量要节制:每个 Worker 是独立线程(内存、启动开销)
- 大量并行任务用 Worker 池(Pool),别逐任务开 Worker
// 动态 import Worker(按需、代码分割)
const { default: WorkerClass } = await import("./heavy.worker?worker");
const w = new WorkerClass();
记忆:Vite 原生打包 Worker——用
new URL(..., import.meta.url)、?worker后缀做按需加载;Worker 开线程要节制,重活交给池。
7. import.meta.url 与运行时资源引用
动态资源引用(路径含变量)Vite 无法静态分析,要用 import.meta.url 或 ?url:
// 静态已知 → import
import img from "./a.png";
// 运行时才知道的目录 → new URL + import.meta.url
const url = new URL(`./icons/${name}.png`, import.meta.url);
// 明确只要 URL(不要 Vite 处理内容)→ ?url 后缀
import assetUrl from "./file.pdf?url";
?url 等查询后缀:
| 后缀 | 效果 |
|---|---|
?url | 只返回资源 URL(不解析内容) |
?raw | 返回文件原始文本 |
?worker | 作为 Worker 打包 |
?inline | 强制内联(覆盖 assetsInlineLimit) |
// ?raw:读文本内容(SVG 字符串、代码高亮源)
import svgStr from "./icon.svg?raw";
// ?inline:小资源强制内联
import tiny from "./dot.png?inline";
public 与 import.meta.url 的区别:import.meta.url 相对模块自身解析——用于"我这个组件旁边的资源",天然随组件目录走。
记忆:静态路径用 import、运行时路径用
new URL(x, import.meta.url)、只要 URL 用?url、读文本用?raw。
8. 资源治理清单与性能预算
一套资源治理清单(发布前过一遍):
□ 业务资源全部 import(别堆 public/)
□ 小图标/小 SVG 内联,照片大图外链 + 懒加载
□ 图片已压缩、现代格式(WebP/AVIF)
□ 首屏图 ≤ 150KB、关键字体子集化 + preload + swap
□ SVG:交互图标组件化、静态图标雪碧/外链
□ Worker 走 Vite 原生加载、数量克制
□ 产物资产走 hash 缓存(内容变 hash 变)
□ 用 vite build --report 看最终体积
性能预算对照:
| 指标 | 建议 |
|---|---|
| 首屏图片总重 | ≤ 300KB |
| 关键字体 | woff2 子集化 ≤ 100KB |
| SVG 数量 | 雪碧图合并,避免几十个请求 |
| 内联总量 | HTML ≤ 100KB(别全内联) |
| Worker 数 | ≤ 2-3 个(量大走池) |
# 看产物与资产体积
npx vite build --report
du -sh dist/assets/
记忆:资源治理 = 压缩 + 懒加载 + 子集化 + 缓存 + 预算——发布前用 –report 对着清单过一遍。
9. 常见问题排查
| 现象 | 原因 | 解法 |
|---|---|---|
| 图片加载 404 | public 路径写错 | 用 /xxx.png 绝对路径或改 import |
| 图片不更新 | public 下无 hash | 业务资源改 import;或手动加版本 |
| SVG 改不了色 | 被内联成 data URI | 组件化(svgr)或外链 |
| 字体 FOUC | 未设 font-display | 加 font-display: swap |
| 字体文件巨大 | 未子集化 | 子集化 + woff2 |
| Worker 加载失败 | 路径写死 | 用 new URL(x, import.meta.url) |
| 构建产物超大 | 图片没压缩 | 压缩插件 + 现代格式 |
| import 的资源找不到 | 文件名大小写/路径 | 检查相对路径与实际文件名 |
调试技巧:
# 看 Vite 实际输出了哪些资产
ls dist/assets/
# 检查页面引用的资源 hash 与 dist 是否一致
grep -o 'assets/[^"]*' dist/index.html | head
记忆:资源翻车九成是"路径/hash/格式"——public vs import、内联 vs 外链、woff2 子集化、import.meta.url 四项对上了,大多数问题自解。
10. 速查表与一句话记忆
| 需求 | 做法 |
|---|---|
| 业务资源 | import(进管线、hash、树摇) |
| 公开静态 | public/ 直出 |
| 小资源内联 | assetsInlineLimit(SVG/小图标) |
| 图片压缩 | 插件/CDN + WebP/AVIF |
| 懒加载 | loading="lazy" + srcset |
| 字体 | woff2 + 子集化 + swap + preload |
| SVG 交互 | svgr 组件化 |
| Worker | new URL(..., import.meta.url) / ?worker |
| 运行时路径 | new URL(x, import.meta.url) |
| 只要 URL/文本 | ?url / ?raw |
一句话记忆:Vite 资源两条管线——public/ 原样直出、import 进管线加工;内联省请求(assetsInlineLimit 管 4KB 分界)外链换缓存(hash immutable);图片压缩 + 懒加载 + srcset、字体 woff2 子集化 + font-display swap、SVG 交互用组件化、Worker 走 new URL(x, import.meta.url) 原生打包;运行时路径用 import.meta.url、只取 URL/文本用 ?url/?raw——资源治理按清单过一遍、对着 –report 看预算,Vite 资产就是可控的工程。
延伸阅读
- /vite-build-optimization/ — 构建产物与性能基线
- /vite-config-guide/ — publicDir 与 assetsInlineLimit 配置
- /vite-worker-wasm/ — Worker 与 WASM 的组合玩法
- /vite-env-production-best-practices/ — 生产构建最佳实践
- [[frontend]] — 前端性能与资源优化全景
- [[network]] — HTTP 缓存策略(immutable hash)
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。