🍎 系列文章
在系列中,将从零学习Vite,系统梳理 Vite 本身的知识,也包括 Vite 底层所使用的 Esbuild、Rollup 双引擎、Babel 编译工具链、模块规范标准等等构建生态。
前言
静态资源处理是前端工程化经常遇到的问题,在传统的 Webpack 中,需要通过 image-loader
file-loader
等来加载处理:比如图片、视频、字体类文件等静态资源。而 Vite 对静态资源(除了svg)是开箱即用的,我们将从如何加载静态资源和如何在生产环境中对静态资源进行优化来学习 Vite 怎么开箱即用的。
加载静态资源
图片加载
图片是最常用的静态资源之一,包括png、jpeg、webp、avif、git、svg等
路径别名的配置
Vite
允许开发者通过配置路径别名来简化模块引用时的路径书写,常见的别名符号包括@
、#
或$
等。在vite.config.js
中通过resolve.alias
配置项设置别名。
// vite.config.ts
import path from 'path';
{
resolve: {
//别名配置
alias: {
'@assets':path.join(__dirname, 'src/assets')
}
}
}
这样 Vite 在遇到@assets
路径的时候,会自动定位至根目录下的src/assets目录。alias 别名配置不仅在 JavaScript
的 import
语句中生效,在 CSS 代码的 @import
和 url
导入语句中也同样生效。
svg 加载
我们通常将 svg 封装成一个组件来引入,这样可以很方便地修改 svg 的各种属性了。svg 组件加载在不同的前端框架中的实现不太相同,我们需要使用对应的插件:
- Vue2 项目中可以使用 vite-plugin-vue2-svg 插件。
- Vue3 项目中可以引入 vite-svg-loader 插件。
- React 项目使用 vite-plugin-svgr 插件。
我们搭建的是 React 项目,所以我们以安装 vite-plugin-svgr
为例:
pnpm add -D vite-plugin-svgr
然后需要在 vite 配置文件添加这个插件:
// vite.config.ts
import svgr from'vite-plugin-svgr';
{
plugins: [
//其它插件省略
svgr()
]
}
随后注意要在tsconfig.json
添加如下配置,否则会有类型错误:
{
"compilerOptions":{
//省略其它配置
"types": ["vite-plugin-svgr/client"]
}
}
接下来让我们在项目中使用 svg 组件,引入的时候注意加上?react
后缀:
import ReactLogo from '@assets/react.svg?react';
<ReactLogo />
打开浏览器,可以看到 svg 已经成功渲染:
JSON 加载
Vite 内置了对于JSON 文件的解析,底层使用@rollup/pluginutils
的dataToEsm
方法将 JSON 对象转换为一个包含各种具名导出的 ES 模块,使用如下:
import { version } from '../package.json';
<div>version: { version }</div>
Web Worker 加载
我们新建 components/WebWorker/example.ts
const workerFun = () => {
let count = 0;
setInterval(() => {
// 向主线程传值
postMessage(++count);
}, 3000);
};
workerFun();
然后在 WebWorker 组件中引入,引入的时候注意加上 ?worker
后缀,也就是告诉 Vite 这是一个 Web Worker 脚本文件:
import Worker from'./components/WebWorker/example.ts?worker';
// 初始化Worker实例
const worker = new Worker();
// 主线程监听worker的信息
worker.addEventListener('message', (e) => {
console.log(e);
});
在浏览器的控制面板,可以看到 Worker 每3秒向主线程传递信息:
Vite 提供了许多静态资源格式的内置支持,这里就不一一列举了,可以自行查看 静态资源处理 | Vite 官方中文文档 (vitejs.dev) Vite 除了内置支持的格式外,如果你的项目中还存在其它格式的静态资源,你可以通过 assetsInclude
配置让 Vite 来支持加载:
// vite.config.ts
{ assetsInclude: ['.gltf'] }
生产环境处理
在生产环境下,我们将对自定义部署域名、base64、图片压缩、svg雪碧图等问题进行探讨和实践
自定义部署域名
在 Vite 中我们可以自动化的来实现地址的替换,只需要在配置文件中指定base
参数即可:
// vite.config.ts
// 是否为生产环境,在生产环境一般会注入NODE_ENV这个环境变量
const isProduction = process.env.NODE_ENV === 'production';
// CDN域名地址
const CDN_URL = 'https://xxx';
//具体配置
{ base: isProduction ? CDN_URL: '/' }
// .env.development
NODE_ENV=development
// .env.production
NODE_ENV=production
在项目根目录新增的两个环境变量文件.env.development
和.env.production
,分别在开发环境和生产环境注入一些环境变量,这里为了区分不同环境我们加上了NODE_ENV,你也可以根据需要添加别的环境变量。打包的时候 Vite 会自动将这些环境变量替换为相应的字符串。
值得注意的是,如果某个环境变量要在 Vite 中通过 import.meta.env
访问,它必须以VITE_
开头,如 VITE_IMG_URL
。我们在组件中来使用这个环境变量:
<imgsrc={newURL('./vite.png', import.meta.env.VITE_IMG_URL).href} />
Base64
在 Vite 中,静态资源有两种构建方式,一种是打包成一个单文件,另一种是通过 base64 编码的格式内嵌到代码中。对于比较大的资源,就推荐单独打包成一个文件,而不是内联了。
- 如果静态资源体积 >= 4KB,则提取成单独的文件
- 如果静态资源体积 < 4KB,则通过 base64 格式内嵌到代码中
可以通过build.assetsInlineLimit
配置,如下代码:
// vite.config.ts
{
build: {
// 4 K
BassetsInlineLimit: 4 * 1024
}
}
svg 格式的文件不受这个配置值的影响,始终会打包成单独的文件,因为它和普通格式的图片不一样,需要动态设置一些属性
图片压缩
图片资源的大小往往是项目产物大小的大头,如果能尽可能精简图片的大小,对项目打包产物体积的优化将得到明显的提升。我们可以使用图片压缩工具来精简图片的体积,比如 Webpack 的 image-webpack-loader,Vite 插件 vite-plugin-imagemin,我们来安装Vite 插件:
pnpm add -D vite-plugin-imagemin
随后在 Vite 配置文件中引入:
// vite.config.ts
import viteImagemin from 'vite-plugin-imagemin';
{
plugins: [
viteImagemin({
// 无损压缩配置,无损压缩下图片质量不会变差
optipng: {
optimizationLevel: 7
},
// 有损压缩配置,有损压缩下图片质量可能会变差
pngquant: {
quality: [0.8, 0.9],
},
// svg优化
svgo: {
plugins: [
{ name: 'removeViewBox' },
{ name: 'removeEmptyAttrs',active: false}
]
}
})
]
}
svg雪碧图
Vite 中对于 svg 文件始终打包成单文件,大量的图标引入会导致网络请求增加,大量的 HTTP 请求会导致网络解析耗时变长,页面加载性能受到影响。我们可以通过合并图标的方案来解决,我们通过vite-plugin-svg-icons
来实现这个方案:
pnpm add -D vite-plugin-svg-icons
接着在 Vite 配置文件中增加如下内容:
// vite.config.ts
import { createSvgIconsPlugin }from 'vite-plugin-svg-icons';
{
plugins: [
// 省略其它插件
createSvgIconsPlugin({
iconDirs: [path.join(__dirname, 'src/assets')]
})
]
}
在src/main.tsx
文件中添加一行代码:
import 'virtual:svg-icons-register';
在src/components目录下新建SvgIcon
组件:
// SvgIcon/index.tsx
export interface SvgIconProps {
name?: string;
prefix: string;
color: string;
[key: string]: string;
}
export default function SvgIcon({
name,
prefix = 'icon',
color = '#333',
...props
}:SvgIconProps) {
const symbolId = `#${prefix}-${name}`;
return(
<svg {...props} aria-hidden="true">
<use href={symbolId} fill={color} />
</svg>
);
}
接着使用 SvgIcon组件:
const icons = import.meta.glob('./assets/react-*.svg');
const iconUrls = [];
for (const path in icons) {
const module = await icons[path]()
const fileName = module.default.split('/').pop();
const [svgName] = fileName.split('.');
iconUrls.push(svgName)
}
// 渲染svg组件
{
iconUrls.map((item) => (
<SvgIcon name={item} key={item} width="50" height="50" />
))
}
这样我们就能将所有的 svg 内容都内联到 HTML 中,省去了大量 svg 的网络请求。
后续
下一章
学习Vite 第四章:利用 Lint 工具链来保证代码规范的落地