Appearance
第12章 静态资源处理
Web 应用不仅仅由 JavaScript 和 CSS 构成。图片、字体、音视频、JSON 文件、纯文本等静态资源构成了应用的血肉。在原始的开发流程中,开发者需要手动管理这些资源的路径、大小优化和缓存策略——复制文件到正确的目录、为文件名添加 hash、配置 CDN 路径、决定哪些小图片应该内联为 Data URL 以减少 HTTP 请求。这些繁琐但重要的工作在 Vite 中被自动化了。
Vite 的静态资源处理系统让这一切变得透明而高效:你只需 import 一个图片文件,Vite 就会在开发时返回正确的 URL,在构建时自动决定是内联为 Data URL 还是生成带 hash 的独立文件。这种 "import 即使用" 的体验消除了资源管理的心智负担,让开发者专注于业务逻辑。
本章将深入 plugins/asset.ts,剖析 Vite 如何识别资源文件、处理不同的导入模式、基于大小阈值决定内联策略、生成 hash 文件名、管理资源清单,以及处理 public 目录中的静态文件。
本章要点
- 理解 Vite 资源插件的注册与工作原理
- 掌握 URL、raw、inline 三种资源导入模式的实现细节
- 深入
assetsInlineLimit的判定逻辑与回调扩展 - 了解 hash 文件名生成与
__VITE_ASSET__占位符机制 - 掌握 manifest 资源清单的生成与使用
- 理解 public 目录与项目资源的处理差异
12.1 资源类型识别
默认资源类型
Vite 维护了一个详尽的默认资源文件扩展名列表,定义在 constants.ts 中。这个列表涵盖了 Web 开发中最常见的静态资源类型,从图片格式(包括现代的 AVIF 和 WebP)到音视频格式,再到字体和其他二进制文件:
typescript
// constants.ts
export const DEFAULT_ASSETS_RE: RegExp = new RegExp(
`\\.(` +
// 图片:涵盖了从传统 PNG/JPEG 到现代 AVIF/WebP 的所有常见格式
'apng|bmp|gif|ico|cur|jpg|jpeg|jfif|pjpeg|pjp|png|svg|tif|tiff|webp|avif|' +
// 媒体:音视频格式,包括字幕文件 VTT
'mp4|webm|ogg|mp3|wav|flac|aac|opus|mov|m4a|vtt|' +
// 字体:所有主流 Web 字体格式
'woff2?|eot|ttf|otf|' +
// 其他:Web 应用清单、PDF、纯文本
'webmanifest|pdf|txt' +
`)(\\?.*)?$`
)
export const DEFAULT_ASSETS_INLINE_LIMIT = 4096 // 4 KiB这个正则表达式末尾的 (\\?.*)?$ 部分确保了带有查询参数的资源引用也能被正确识别。例如 logo.png?v=2 或 icon.svg?inline 都会被匹配。
除了内置的资源类型列表,Vite 还提供了 assetsInclude 配置选项,允许用户扩展资源类型的识别范围。这对于使用非标准文件格式的项目(如 3D 模型文件 .glb、地理信息文件 .geojson 等)非常有用。
MIME 类型注册
正确的 MIME 类型对于浏览器正确渲染资源至关重要。Vite 使用 mrmime 库来查询文件的 MIME 类型,但这个库对某些常见类型的注册存在偏差。Vite 通过 registerCustomMime 函数进行修正,确保在开发服务器返回资源和构建时生成 Data URL 时使用最佳的 Content-Type:
typescript
export function registerCustomMime(): void {
// ico 文件应使用 image/x-icon 而非 IANA 注册的 image/vnd.microsoft.icon
// 这是因为 image/x-icon 有更好的浏览器兼容性
mrmime.mimes['ico'] = 'image/x-icon'
// cur 是光标文件,与 ico 共享相同的文件格式
mrmime.mimes['cur'] = 'image/x-icon'
// flac 无损音频格式
mrmime.mimes['flac'] = 'audio/flac'
// eot 是一种旧的嵌入式 OpenType 字体格式
mrmime.mimes['eot'] = 'application/vnd.ms-fontobject'
}关于 .ico 文件的 MIME 类型选择值得说明:虽然 IANA 正式注册的类型是 image/vnd.microsoft.icon,但 image/x-icon 在实践中有更广泛的浏览器支持,HTML5 Boilerplate 等知名项目也推荐使用后者。
12.2 资源插件架构
assetPlugin 是 Vite 资源处理的核心插件。它通过 resolveId、load、renderChunk 和 generateBundle 四个钩子覆盖了资源处理的完整生命周期。从开发者写下 import logo from './logo.png' 的那一刻起,到最终产物中出现正确的资源路径或内联 Data URL,每一步都由这个插件精心编排。
resolveId:资源识别门户
resolveId 钩子是资源处理管线的第一道关卡。它利用 Rolldown 的 filter 机制进行高效的预筛选——只有匹配资源模式的模块 ID 才会进入处理逻辑,其他模块在正则匹配阶段就被快速排除。这种过滤器设计对于大型项目非常重要,因为它避免了对每个模块 ID 调用 JavaScript 函数的开销:
typescript
resolveId: {
filter: {
id: [urlRE, DEFAULT_ASSETS_RE, .../* 用户自定义资源模式 */],
},
handler(id) {
if (!config.assetsInclude(cleanUrl(id)) && !urlRE.test(id)) {
return
}
// 处理 public 目录中的资源引用
const publicFile = checkPublicFile(id, config)
if (publicFile) {
return id
}
},
},对于 public 目录中的文件引用,resolveId 直接返回原始 ID,让后续的 load 钩子来处理路径转换。这是因为 public 目录中的文件不参与模块解析——它们的路径在最终产物中保持不变。
load:资源加载的核心逻辑
load 钩子是资源处理的灵魂。根据查询参数和运行环境的不同,它采取完全不同的处理策略。所有的资源最终都被转换为一个 JavaScript 模块,导出一个字符串——要么是 URL,要么是文件内容。这种统一的抽象使得资源可以像普通模块一样被 import、被 tree-shaking 和被代码分割:
typescript
load: {
filter: {
id: {
include: [rawRE, urlRE, DEFAULT_ASSETS_RE, .../* 用户自定义 */],
exclude: /^\0/, // 排除虚拟模块(以 \0 开头的 ID 是 Rollup 约定的虚拟模块标识)
},
},
async handler(id) {
// raw 模式:返回文件内容字符串
if (rawRE.test(id)) {
const file = checkPublicFile(id, config) || cleanUrl(id)
this.addWatchFile(file)
return {
code: `export default ${JSON.stringify(await fsp.readFile(file, 'utf-8'))}`,
moduleType: 'js',
}
}
// URL 或默认模式
if (!urlRE.test(id) && !config.assetsInclude(cleanUrl(id))) return
id = removeUrlQuery(id)
let url = await fileToUrl(this, id)
// 开发模式下继承 HMR 时间戳,确保文件变更时浏览器重新请求
if (!url.startsWith('data:') && this.environment.mode === 'dev') {
const mod = this.environment.moduleGraph.getModuleById(id)
if (mod && mod.lastHMRTimestamp > 0) {
url = injectQuery(url, `t=${mod.lastHMRTimestamp}`)
}
}
return {
code: `export default ${JSON.stringify(encodeURIPath(url))}`,
moduleSideEffects: config.command === 'build' && this.getModuleInfo(id)?.isEntry
? 'no-treeshake' : false,
moduleType: 'js',
}
},
},12.3 三种导入模式
Vite 为静态资源提供了三种导入模式,每种模式适用于不同的使用场景。开发者通过在导入路径上附加查询参数来选择模式。这种基于查询参数的模式选择是一个优雅的设计——它不需要额外的配置文件,意图直接表达在代码中,一目了然:
URL 模式(默认)
这是最常用的导入模式。导入一个资源文件时,Vite 返回该资源的 URL。在开发模式下这是开发服务器的路径(如 /src/assets/logo.png),在构建模式下则根据 assetsInlineLimit 阈值决定是返回 Data URL 还是输出文件的路径(如 /assets/logo-a1b2c3.png)。这种模式适用于需要在 JavaScript 中引用资源路径的场景,最典型的就是图片的 src 属性。