图片加载
图片是项目中最经常使用到的静态资源之一,本身就包括了非常多的格式,比如png,jpeg,webp、avif、gif。当然,也包括经常用作图标的svg。这一部分我们主要讨论的是如何加载图片,也就是如何让图片在页面中正常展示。
1. 使用场景
在日常项目开发中,我们会碰到三种加载图片的场景:
(1)在HTML或者JSX中,通过img标签来加载图片,比如:
<img src="../../assets/a.png"></img>
(2)在 CSS 中通过 background 属性加载图片,如:
background: url('../../assets/b.png') norepeat;
(3)在 JavaScript 中,通过脚本的方式动态指定图片的src属性,如:
document.getElementById('hero-img').src = '../../assets/c.png'
当然,大家一般还会有别名路径的需求,比如地址前缀直接换成@assets,这样就不用开发人员手动寻址,降低开发时的心智负担。
2. 在vite中进行使用
接下来让我们在目前的脚手架项目来进行实际的编码,你可以在 Vite 的配置文件中配置一下别名,方便后续的图片引入:
pnpm install @types/node
vite.config.ts
import path from 'path';
import {defineConfig} from 'vite'
export default defineConfig({
resolve: {
alias: {
"@assets": path.join(__dirname,"src/assets")
}
}
})
main.ts
import './style.css'
import typescriptLogo from '@assets/typescript.svg'
import { setupCounter } from './counter'
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://www.typescriptlang.org/" target="_blank">
<img src="${typescriptLogo}" class="logo vanilla" alt="TypeScript logo" />
</a>
<h1>Vite + TypeScript</h1>
<div class="card">
<button id="counter" type="button"></button>
</div>
<p class="read-the-docs">
Click on the Vite and TypeScript logos to learn more
</p>
</div>
`
setupCounter(document.querySelector<HTMLButtonElement>('#counter')!)
这样我们还是能够正常的在页面中正常的访问src/assets下面的图片资源
2. 加载SVG图片
刚才我们成功地在 Vite 中实现了图片的加载,上述这些加载的方式对于 svg 格式来说依然是适用的。不过,我们通常也希望能将 svg 当做一个组件来引入,这样我们可以很方便地修改 svg 的各种属性,而且比 img 标签的引入方式更加优雅。
SVG 组件加载在不同的前端框架中的实现不太相同,社区中也已经了有了对应的插件支持:
- Vue2 项目中可以使用 vite-plugin-vue2-svg插件。
- Vue3 项目中可以引入 vite-svg-loader。
- React 项目使用 vite-plugin-svgr插件。
现在让我们在 React 脚手架项目中安装对应的依赖:
pnpm i vite-plugin-svgr -D
然后需要在 vite 配置文件添加这个插件:
// vite.config.ts
import svgr from 'vite-plugin-svgr';
{
plugins: [
// 其它插件省略
svgr()
]
}
随后注意要在 tsconfig.json 添加如下配置,否则会有类型错误:
{
"compilerOptions": {
// 省略其它配置
"types": ["vite-plugin-svgr/client"]
}
}
接下来让我们在项目中使用 svg 组件:
import { ReactComponent as ReactLogo } from '@assets/icons/logo.svg';
export function Header() {
return (
// 其他组件内容省略
<ReactLogo />
)
}
回到浏览器中,你可以看到 svg 已经成功渲染:
JSON加载
Vite 中已经内置了对于 JSON 文件的解析,底层使用@rollup/pluginutils 的 dataToEsm 方法将 JSON 对象转换为一个包含各种具名导出的 ES 模块,使用姿势如下:
import { version } from '../../../package.json';
不过你也可以在配置文件禁用按名导入的方式:
// vite.config.ts
{
json: {
stringify: true
}
}
这样会将 JSON 的内容解析为export default JSON.parse("xxx"),这样会失去按名导出的能力,不过在 JSON 数据量比较大的时候,可以优化解析性能。
Web Worker 脚本
Vite 中使用 Web Worker 也非常简单,我们可以在新建Header/example.js文件:
const start = () => {
let count = 0;
setInterval(() => {
// 给主线程传值
postMessage(++count);
}, 2000);
};
start();
然后在 Header 组件中引入,引入的时候注意加上?worker后缀,相当于告诉 Vite 这是一个 Web Worker 脚本文件:
import Worker from './example.js?worker';
// 1. 初始化 Worker 实例
const worker = new Worker();
// 2. 主线程监听 worker 的信息
worker.addEventListener('message', (e) => {
console.log(e);
});
WASM脚本
Vite 对于 .wasm 文件也提供了开箱即用的支持,我们拿一个斐波拉契的 .wasm 文件
export function fib(n) {
var a = 0,
b = 1;
if (n > 0) {
while (--n) {
let t = a + b;
a = b;
b = t;
}
return b;
}
return a;
}
让我们在组件中导入fib.wasm文件:
// Header/index.tsx
import init from './fib.wasm';
type FibFunc = (num: number) => number;
init({}).then((exports) => {
const fibFunc = exports.fib as FibFunc;
console.log('Fib result:', fibFunc(10));
});
Vite 会对.wasm文件的内容进行封装,默认导出为 init 函数,这个函数返回一个 Promise,因此我们可以在其 then 方法中拿到其导出的成员——fib方法。
回到浏览器,我们可以查看到计算结果,说明 .wasm 文件已经被成功执行:
其他静态资源
除了上述的一些资源格式,Vite 也对下面几类格式提供了内置的支持:
- 媒体类文件,包括
mp4、webm、ogg、mp3、wav、flac和aac。 - 字体类文件。包括
woff、woff2、eot、ttf和otf。 - 文本类。包括
webmanifest、pdf和txt。
也就是说,你可以在 Vite 将这些类型的文件当做一个 ES 模块来导入使用。如果你的项目中还存在其它格式的静态资源,你可以通过assetsInclude配置让 Vite 来支持加载:
// vite.config.ts
{
assetsInclude: ['.gltf']
}
特殊资源后缀
Vite 中引入静态资源时,也支持在路径最后加上一些特殊的 query 后缀,包括:
?url: 表示获取资源的路径,这在只想获取文件路径而不是内容的场景将会很有用。?raw: 表示获取资源的字符串内容,如果你只想拿到资源的原始内容,可以使用这个后缀。?inline: 表示资源强制内联,而不是打包成单独的文件。
Vite中文件资源内联与单文件
在 Vite 中,所有的静态资源都有两种构建方式,一种是打包成一个单文件,另一种是通过 base64 编码的格式内嵌到代码中。
这两种方案到底应该如何来选择呢?
对于比较小的资源,适合内联到代码中,一方面对代码体积的影响很小,另一方面可以减少不必要的网络请求,优化网络性能。而对于比较大的资源,就推荐单独打包成一个文件,而不是内联了,否则可能导致上 MB 的 base64 字符串内嵌到代码中,导致代码体积瞬间庞大,页面加载性能直线下降。
Vite 中内置的优化方案是下面这样的:
- 如果静态资源体积 >= 4KB,则提取成单独的文件
- 如果静态资源体积 < 4KB,则作为 base64 格式的字符串内联
上述的4 KB即为提取成单文件的临界值,当然,这个临界值你可以通过build.assetsInlineLimit自行配置,如下代码所示:
// vite.config.ts
{
build: {
// 8 KB
assetsInlineLimit: 8 * 1024
}
}
svg 格式的文件不受这个临时值的影响,始终会打包成单独的文件,因为它和普通格式的图片不一样,需要动态设置一些属性