1天前
在现代的前端项目中,我们常常需要根据不同的条件动态加载本地图片资源。Vite 支持多种方式实现这一需求,但不恰当的用法可能导致打包体积膨胀或运行时问题。例如,如果直接使用new URL(../assets/images/${name}.png, import.meta.url).href
进行动态导入,Vite 构建时会将目录下所有匹配的图片文件都包含进来,而非仅仅我们需要的那一个图片。
Vite 官方文档对此进行了说明:在构建阶段,它会把所有可能的文件名导入并放到一个对象里,从而保证运行时可以访问对应图片,但也意味着所有文件都被打包了。
new URL
动态导入Vite 允许使用原生 ESM 的 import.meta.url
配合 URL
构造函数来获取静态资源的路径vite.dev。例如:
typescript// 最基础的动态导入方式 const getImageUrl = (name: string) => { return new URL(`../assets/images/${name}.png`, import.meta.url).href; };
上面代码会在运行时根据 name
生成图片 URL。Vite 文档的示例也展示了类似的用法vite.dev:
typescriptfunction getImageUrl(name) { return new URL(`./dir/${name}.png`, import.meta.url).href }
⚠️ 注意:这种写法虽然方便,但模板字符串中的路径会被 Vite 视为多种可能的导入目标,从而把目录下所有匹配的图片导入打包vite.dev。例如,当 Vite 看到 ``new URL(./dir/${name}.png
, import.meta.url)时,它会自动先导入
img0.png、
img1.png` 等所有可能的文件vite.dev。这通常会导致打包体积比预期要大。
此外,new URL
动态导入在服务端渲染(SSR)模式下并不适用[vite.dev](https://vite.dev/guide/assets#:~:text=Does not work with SSR),因为 Node.js 环境下 import.meta.url
与浏览器环境不同,无法提前确定资源的主机地址。
import.meta.glob
批量导入Vite 提供了 import.meta.glob
函数,可以一次性匹配并导入多个文件[vite.dev](https://vite.dev/guide/features#:~:text=You can then iterate over,to access the corresponding modules)[vite.dev](https://vite.dev/guide/features#:~:text=Matched files are by default,as the second argument)。其用法如下:
typescript// eager 模式:立即导入所有匹配的文件 const images = import.meta.glob('@/assets/images/*.png', { eager: true }); // 懒加载模式(默认):按需动态导入 const images = import.meta.glob('@/assets/images/*.png');
以上代码会返回一个对象 images
,键为每个匹配文件的路径,值为对应的导入函数或模块(取决于是否设置了 eager: true
)。如 Vite 文档所示,你可以遍历这个对象来访问每个模块[vite.dev](https://vite.dev/guide/features#:~:text=You can then iterate over,to access the corresponding modules):
typescriptfor (const path in modules) { modules[path]().then((mod) => { console.log(path, mod) }) }
默认情况下,import.meta.glob
会将匹配文件懒加载(各文件打包到独立的 chunk),即只有调用对应函数时才会加载该文件[vite.dev](https://vite.dev/guide/features#:~:text=Matched files are by default,as the second argument)。如果使用 { eager: true }
,Vite 会在构建时将所有匹配文件同步导入(相当于在代码中直接写了多个 import
),无需运行时再动态加载[vite.dev](https://vite.dev/guide/features#:~:text=Matched files are by default,as the second argument)。这种模式适用于文件数量较少且始终需要使用的场景。
import.meta.glob
支持更灵活的匹配规则。例如,你可以只匹配特定前缀的文件:
typescript// 只匹配以 status- 前缀开头的图片 const statusImages = import.meta.glob('@/assets/images/status-*.png', { eager: true });
也可以使用数组同时匹配多个目录或文件类型[vite.dev](https://vite.dev/guide/features#:~:text=Multiple Patterns):
typescript// 同时匹配 icons 目录下的 PNG 以及 images 目录下的图片(png/jpg/jpeg/svg) const images = import.meta.glob([ '@/assets/icons/*.png', '@/assets/images/*.{png,jpg,jpeg,svg}' ], { eager: true });
通过精确的 glob 模式,我们可以避免使用过于宽泛的通配符(如 **/*.png
)带来的打包冗余,将匹配范围限定在需要的目录和后缀之内。
import.meta.glob
可以通过选项 query
和 import
定制导入方式。例如,使用 { as: 'url' }
(相当于 query: '?url'
)可以将文件以 URL 字符串形式导入;使用 { as: 'raw' }
(即 query: '?raw'
)可以导入文件的原始内容vite.dev:
typescript// 导入为 URL(字符串形式) const imagesAsUrl = import.meta.glob('@/assets/images/*.png', { eager: true, as: 'url' }); // 导入为原始内容(字符串形式) const imagesAsRaw = import.meta.glob('@/assets/images/*.png', { eager: true, as: 'raw' });
这对于需要以不同方式使用图片的场景非常有用,比如将部分图片作为 Base64 字符串直接插入样式、或获取图片路径以供 <img>
标签使用。
假设我们有一组状态对应的图片资源,可以先定义状态到图片名的映射,然后使用 import.meta.glob
导入所有图片并进行匹配:
typescript// 定义状态到图片名称的映射 const statusImageMap: Record<string, string> = { 'success': 'status-success', 'warning': 'status-warning', 'error': 'status-error' }; // 导入所有状态图片(例如 status-success.png、status-warning.png 等) const statusImages = import.meta.glob('@/assets/images/status-*.png', { eager: true }); // 获取对应状态的图片 URL const getStatusImage = (status: string) => { const imageName = statusImageMap[status]; const imageKey = Object.keys(statusImages).find((key) => key.includes(imageName)); return imageKey ? (statusImages[imageKey] as any).default : ''; };
上面代码中,我们根据状态名拼接出图片前缀(如 'status-success'
),然后在导入的 statusImages
对象的键列表中查找包含这个前缀的路径。找到对应键后,取出它的默认导出即为图片的 URL。
对于大量图片的场景,我们可以使用懒加载模式按需导入:
typescriptconst lazyImages = import.meta.glob('@/assets/gallery/*.{jpg,png}'); // 使用时按需加载图片 const loadImage = async (name: string) => { const key = Object.keys(lazyImages).find((key) => key.includes(name)); if (key) { const module = await (lazyImages as any)[key](); return module.default; } return ''; };
以上代码会在需要时才加载指定名称的图片文件,避免一次性打包过多图片。比如调用 await loadImage('sunset')
时,才会动态导入对应的图片模块并返回其 URL。
选择合适的导入模式: 对于确定一定会使用的小型图片集,可使用 { eager: true }
同步导入,在构建时直接打包,运行时无需再次加载。对大量图片或不确定会否使用的资源,可选择默认的懒加载模式,避免初始打包体积过大[vite.dev](https://vite.dev/guide/features#:~:text=Matched files are by default,as the second argument)。
使用精确的匹配模式: 避免使用过于宽泛的 glob 模式(如 **/*.png
),否则会匹配到不需要的文件。应将 glob 范围缩小到具体目录和必要的文件类型,比如 @/assets/icons/*.png
或 @/assets/images/*.png
,以减少匹配结果。
缓存导入结果: 对于需要频繁访问的动态图片,可以将结果缓存起来。例如:
typescriptconst imageCache = new Map<string, string>(); const getImage = async (name: string) => { if (imageCache.has(name)) { return imageCache.get(name); } const images = import.meta.glob('@/assets/images/*.png'); const key = Object.keys(images).find((key) => key.includes(name)); if (key) { const module = await (images as any)[key](); const url = module.default; imageCache.set(name, url); return url; } return ''; };
这种方式可以避免重复导入同一资源,提高访问速度。
路径别名与大小写: 在使用 @
等路径别名时,确保已在 Vite 配置(vite.config.js
)中正确设置别名映射。路径匹配对大小写敏感,写路径时请与实际文件名完全一致,否则可能找不到资源。
类型声明: 如果项目使用 TypeScript,可以为 glob 导入添加类型声明。例如:
typescriptinterface ImageModule { default: string; } const images: Record<string, () => Promise<ImageModule>> = import.meta.glob('@/assets/images/*.png');
这样能获得更好的类型提示。
构建与打包: 使用 { eager: true }
的文件会被打包工具直接导入到主包中,而默认的懒加载模式会为每个文件生成单独的 chunk[vite.dev](https://vite.dev/guide/features#:~:text=Matched files are by default,as the second argument)。根据图片大小和使用场景选择合适方式,以避免生成过多的静态资源文件或导致初始加载变慢。
动态路径限制: glob 模式中的路径不能完全动态,必须是完整的路径片段(例如 ${name}.png
),不能使用变量拼接出完全未知的路径[vite.dev](https://vite.dev/guide/assets#:~:text=During the production build%2C Vite,import.meta.url)。否则 Vite 无法预先分析文件列表,构建时不会进行转换,可能导致运行时错误。
SSR 场景: 如果项目使用服务器端渲染,new URL(…, import.meta.url)
的方式无法工作[vite.dev](https://vite.dev/guide/assets#:~:text=Does not work with SSR),因为服务器端无法像浏览器那样解析 import.meta.url
。在 SSR 情况下应谨慎使用动态导入资源的方法。
status-success.png
, icon-search.svg
),便于通过代码逻辑或正则匹配到正确的文件。new URL
;需要批量处理或按需加载的图片推荐使用 import.meta.glob
。合理组合两者可以发挥各自优势。vite-imagetools
等插件进行构建时优化。同时利用浏览器缓存,避免重复加载相同资源。通过以上方法和建议,我们可以在 Vite 项目中高效、安全地实现图片的动态导入,兼顾开发便利性和运行时性能。参考 Vite 官方文档vite.dev[vite.dev](https://vite.dev/guide/features#:~:text=Matched files are by default,as the second argument)可以获得更多细节信息。
在上述 Vue 组件代码中,我们使用了 CSS 伪元素(::before
和 ::after
)为列表项添加左右两部分的背景图,并通过 CSS 自定义属性(变量)和 JS 逻辑动态改变这些背景图。CSS 伪元素是“添加到选择器的关键字,可对选中元素的特定部分进行样式设置” ;例如 ::before
会创建元素的第一个子元素,::after
会创建最后一个子元素 。在本例中,.item::before
和 .item::after
分别作为虚拟层插入到列表项前后,用于显示不同状态下的背景图。
CSS 自定义属性(如 --status-bg
等)可以在普通 CSS 属性中通过 var()
函数引用。在父元素上声明一个变量后,其伪元素也可通过 var(--name)
访问该值 。在 JS/Vue 中,我们可以通过调用 element.style.setProperty('--prop', value)
或 :style
绑定样式对象的方式来动态改变自定义属性值 。这样,就可以借助 CSS 变量让伪元素的样式随父元素的状态而变化。
批量导入图片资源: 使用 Vite 的 import.meta.glob()
函数批量导入符合通配符的图片(如 card-*.png
),它会返回一个以文件路径为键、导入模块为值的对象 。在本代码中:
typescriptconst statusImages = import.meta.glob('@/assets/images/cstj/zbtj/card-*.png', { eager: true });
这会将 zbtj
目录下所有以 card-
开头的 PNG 文件同步导入并映射到 statusImages
对象中 。
状态到图片的映射: 定义一个 statusImageMap
对象,将业务状态码映射到对应的图片文件名(不带后缀)。如:
typescriptconst statusImageMap = { '0': 'card-tzzb', '1': 'card-zth', '2': 'card-ztjh', '3': 'card-zjzt', '4': 'card-ztjr', '5': 'card-ztr', '6': 'card-common', '7': 'card-active' };
动态查找并获取图片 URL: 通过函数 getStatusImage(status)
,遍历 statusImages
对象的键,查找包含目标文件名(statusImageMap[status]
)的项,返回其默认导出(即图片在打包后的 URL)。例如:
typescriptconst imageKey = Object.keys(statusImages).find(key => key.includes(imageName)); return imageKey ? (statusImages[imageKey]).default : '';
绑定 CSS 变量作为背景: 在组件模板中,使用 :style="getItemStyles(item)"
将一个样式对象绑定到 .item
元素。getItemStyles
返回的对象中定义了三个 CSS 变量:--status-bg
、--common-bg
、--active-bg
,其值都为对应图片的 url(...)
。例如:
typescriptconst getItemStyles = (item) => ({ '--status-bg': `url(${getStatusImage(item.zbpgzt || '0')})`, '--common-bg': `url(${getStatusImage('6')})`, '--active-bg': `url(${getStatusImage('7')})` });
在 CSS 部分,.item::before
使用 background: var(--status-bg) no-repeat
将左侧背景设置为对应状态图片;默认情况下,.item::after
使用 var(--common-bg)
显示右侧背景。在 .item.active::after
选择器中,将右侧背景改为 var(--active-bg)
。通过这种方法,伪元素的背景图直接取自绑定的 CSS 变量。由于 CSS 变量支持继承并可在任何属性值位置使用 (通过 var()
函数引用),当 Vue 组件更新这些变量时,伪元素背景会自动更新 。