🍇 前言
在前端开发中,除了通过 API 动态请求的数据外,还有一些诸如 HTML 文件、图片、字体等文件需要在项目中被用到,通常这些被视为静态资源。对于静态资源的管理和引用方式有时也会产生让人困惑的问题,就比如在 Vite 构建的项目中引用图片资源时,如果引用方式不正确就可能会出现意外的 "图裂" 情况。
本文将基于 Vite + React 来讲述并解锁 Vite 引用本地静态资源的正确姿势,主要包括 Vite 对本地资源的静态引用和动态引用两种方式,并结合一个案例来演示动态引用资源的实现过程。
🍈 静态资源目录结构
静态资源的存放位置基本上是约定俗成的,public
目录和src/assets
。
public
:目录中一般会放着index.html
和favicon.ico
。src/assets
:而其他业务编码中会使用的静态资源,如图片、字体等会统一放在src
的assets
目录下。
比如这是我所开发的一个项目中所用到的部分图片资源:
🍉 引用图片资源的常规方式
在基础前端项目开发中,通常会用到三种常规的加载图片的方式,<img>
元素、background
样式、JS 脚本动态指定src
属性。
<img>
:在 HTML中通过<img>
元素指定src
引用图片资源。
html
<img src="./assets/logo.png" alt="" />
background
:通过background: url()
或background-image: url()
引用本地资源。
css
#logo {
background: url('./assets/logo.png');
}
css
#logo {
background-image: url('./assets/logo.png');
}
- JS 脚本动态指定图片
src
属性。
javascript
document.getElementById('logo').src = './assets/logo.png'
🍊 配置路径别名 @assets
为了更灵活地引用项目中的静态资源,往往会给特定的路径配置别名以方便引用,比如常见的src
对应@
,src/assets
对应@assets
。
如果项目中使用 Vite 构建,可以在vite.config.ts
中作如下配置:
typescript
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
base: './',
plugins: [react()],
resolve: {
alias: {
'@': path.join(__dirname, './src'),
'@assets': path.join(__dirname, './src/assets'),
},
},
});
🍋 Vite 项目中引用图片资源
在以 Vite 为底层构建的 React 项目中引用图片资源就不能再同于以往常规的方式了(这也包括以 Webpack 为底层构建的 CRA 项目)。
由于 Vite 或者 Webpack 这类打包工具对静态资源有特定的处理方式,在这里我们需要给引用资源的方式分为两类:静态引入和动态引入。
静态引用图片资源
静态引入图片资源就是使用像 Tailwind CSS、CSS 样式或者指定<img>
元素的src
属性这样的。
需要注意的是在<img>
元素中,src
属性使用@
路径别名是无效的,这是因为对于 Vite 来说src
中的内容就是单纯的字符串,即使是配置了路径别名也并不会当做资源文件去处理;当然如果想使用@
路径别名指定src
也是有方法的,这时则需要通过动态引入的方式。
- Tailwind CSS:
jsx
<div className='
w-full h-full
bg-[url("@assets/images/boat.png")]
bg-contain bg-no-repeat bg-center'>
</div>
- CSS:
css
div {
background-image: url("@assets/images/boat.png");
}
<img>
元素指定src
属性,使用常规的绝对路径:
jsx
<img
src="/src/assets/images/boat.png"
alt="Preview"
className='max-w-full max-h-full object-contain'
/>
<img>
元素指定src
属性,使用@
路径别名是无效的:
jsx
<img
src="@assets/images/boat.png"
alt="Preview"
className='max-w-full max-h-full object-contain'
/>
动态引用图片资源
动态引用图片资源就是用import
、new URL(url, import.meta.url)
这样的方式。
import.meta.url
是一个 ESM 的原生功能,会暴露当前模块的 URL。将它与原生的 URL 构造器组合使用,在一个 JavaScript 模块中,通过相对路径我们就能得到一个被完整解析的静态资源 URL。
import
:Vite 中引入一个静态资源会返回解析后的公共路径,生产构建后生成散列文件名:
jsx
import boatImage from '@assets/images/boat.png';
const Main = () => {
return (
<img
src={boatImage}
alt="Preview"
className='max-w-full max-h-full object-contain'
/>
);
};
export default Main;
new URL(url, import.meta.url)
:
jsx
const boatImage = new URL('@assets/images/boat.png', import.meta.url).href;
const Main = () => {
return (
<img
src={boatImage}
alt="Preview"
className='max-w-full max-h-full object-contain'
/>
);
};
export default Main;
🍌 Vite 动态引用本地图片案例
我们以一个菜单列表为案例演示 Vite 项目中动态引用图片资源。在业务需求中,菜单列表中会展示固定的一些图标,这些就是本地图片资源。
菜单数据列表
在菜单数据列表中,icon
属性就是本地图片路径,这里用了一个自定义函数获取,后面会给到。
jsx
const menuItems = [
{
id: 'profile',
name: 'Profile',
icon: getAssetsFile('menu/Profile.png')
},
{
id: 'favorites',
name: 'Favorites',
icon: getAssetsFile('menu/Favorites.png')
}
]
页面结构
页面结构中,主要分为菜单图片和菜单的描述文本。
在这里菜单图标是通过行内样式指定图片路径的,如果使用静态引用的方式那么icon
就是绝对路径,如果是动态引用,那么icon
的路径就是需要通过new URL()
获取。
jsx
<div className='flex w-[72px] h-full pt-[24px] pr-0 pb-[24px] pl-0 flex-col gap-[24px] items-center flex-nowrap bg-[#fff] border-solid border-r border-r-[rgba(0,0,0,0.1)] absolute top-[64px] left-0 overflow-hidden z-[36]'>
{menuItems.map((item) => (
<div
key={item.id}
className={`flex h-[49px] flex-col justify-center items-center self-stretch shrink-0 flex-nowrap relative cursor-pointer border-l-4 ${activeMenu === item.id ? 'border-l-[#ffd05a]' : 'border-l-transparent'}`}
onClick={() => handleMenuClick(item.id)}
>
{/* 菜单图标 */}
<div
className="w-[32px] h-[32px] shrink-0 bg-cover bg-no-repeat relative overflow-hidden"
style={{ backgroundImage: `url(${item.icon})` }}
/>
{/* 菜单描述 */}
<span className="h-[20px] shrink-0 basis-auto font-['Inter'] text-[14px] font-normal leading-[20px] text-[#323232] relative text-left whitespace-nowrap">
{item.name}
</span>
</div>
))}
</div>
动态获取图片路径
前面我们提到icon
的路径是由一个自定义函数getAssetsFile
获取的。实际上就是通过new URL()
的方式动态获取图片路径。
typescript
// 获取assets静态资源
export const getAssetsFile = (url: string) => {
return new URL(`../assets/images/${url}`, import.meta.url).href;
};
🍍 回顾总结
最后回顾总结一下:
- 静态资源常见存放目录
public
src/assets
- 引用图片资源的常规方式
<img>
元素指定src
background: url()
、background-image: url()
样式- JS 脚本动态指定
src
属性
- Vite 静态引用图片资源
background: url()
、background-image: url()
样式<img>
元素指定src
属性,使用绝对路径或相对路径
- Vite 动态引用图片资源
import
导入new URL(url, import.meta.url)