在Vue3项目中使用svg-sprite-loader
1.普通的svg图片使用方式
1.1 路径引入
- 正常我们会把项目中的静态资源放在指定的一个目录,例如
assets
,使用起来就像
<img src="../assets/svgicons/about.svg" />
1.2封装组件使用
- 显然上面的这种方法在项目开发中不太适用,每次都需要通过路径引用
- 此次封装的目的就是简化写法,以更加简洁的方式引用图标并且具备更好的复用性
- 封装后使用如下:
<svg-Icon name="user"></SvgIcon>
//1. SvgIcon为全局组件,name表示要使用的svg图标名称
//2. user.svg图标存放于icons/svg目录下(路径不固定存放,跟随自己需求)
2.依赖安装与loader配置
2.1下载依赖
- 根据自己项目的版本需求,下载所兼容的svg-sprite-loader的版本
npm i svg-sprite-loader -D
// 或是
yarn add svg-sprite-loader -D
2.2 loader配置
- 打开vue.config.js文件,在chainWebpack函数中新增配置
- 这里有两种写法
2.2.1 方式一
- 如果你整个项目中的svg文件都希望使用雪碧图的方式进行处理,那么这么写
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
chainWebpack: (config) => {
const svgRule = config.module.rule('svg')
svgRule.uses.clear()
// 添加要替换的 loader
svgRule.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({symbolId: 'icon-[name]'})
}
})
2.2.2 方式二
- 或者说你只希望指定目录下的svg文件被
svg-sprite-loader
处理,其他的还是使用webpack5默认的svg处理方式,那么这么写
//这行代码引入了 Node.js 的 path 模块,该模块提供了用于处理文件路径的实用工具方法,后续会用于拼接路径等操作。
const path = require('path')
//定义了一个resolve的函数,它接收一个参数dir
//函数的功能是使用,path.join方法将当前模块所在目录和传入的dir参数所代表的路径进行拼接,最终返回拼接后的完整路径,这个函数常用于处理相对路径,使其更准确指向项目内的特定目录
//——dirname是是一个特殊变量。表示当前执行脚本所在的目录的绝对路径,它是一个全局变量,可以在任何地方使用,用于获取当前模块的文件所在的目录路径,
//为什么使用——而不使用.:“使用.来获取当前文件的路径可能导致路径不准确,因为.代表当前目录,可能会随着当前文件的深度变化而变化,而使用——dirname则可以确保获取到当前文件的真实路径,不会随着文件深度变化”
function resolve(dir) {
return path.join(__dirname, dir)
}
/**
* chainWebpack对象中的内容介绍
* 这里的config是webpack的链条式操作配置对象,下面通过调用它的一系列方法来修改webpack的模块处理规则等配置内容
*/
chainWebpack(config) {
// 设置 svg-sprite-loader
// config 为 webpack 配置对象
// config.module 表示创建一个具名规则,以后用来修改规则
/**
* 这部分代码开始配置 Webpack 对 svg 文件的处理规则。
* 首先通过 config.module.rule('svg') 创建了一个名为 svg 的规则,后续的操作都是针对这个规则来进行的。
* .exclude.add(resolve('src/icons')) 表示对于 Webpack 处理模块时,
* 当遇到 src/icons 目录下的 svg 文件时,要排除它们,不应用当前这个 svg 规则去处理(通常是因为后面会针对这个目录下的 svg 文件有特殊的处理规则设置),最后 .end() 用于结束当前这一步规则的链式操作,回到上一级配置链。
*/
config.module
// 规则
// 在 module 配置的规则集合里,通过调用 rule 方法并传入 'svg' 字符串来开始定义针对 .svg 文件类型的模块处理规则。后续会在这条规则上继续添加更多的配置,比如排除某些目录下的 .svg 文件不使用这条规则处理等。
.rule('svg')
// 忽略
// 在刚才定义的 .svg 文件模块处理规则里,调用 exclude 方法,它用于指定哪些目录下的对应文件类型(这里就是 .svg 文件)不需要应用这条规则去处理。这里调用了之前定义的 resolve 函数,传入 'src/icons' 参数,也就是将 src/icons 目录对应的绝对路径添加到排除列表中,意味着 src/icons 目录下的 .svg 文件不会按照这条常规的 .svg 模块处理规则去加载。
.exclude.add(resolve('src/icons'))
// 结束
.end()
// config.module 表示创建一个具名规则,以后用来修改规则
config.module
// 规则
// 创建一个新的名为 'icons' 的模块处理规则,虽然也是针对 .svg 文件,但和前面常规的 .svg 规则区分开,这里将用于配置 src/icons 目录下 .svg 文件的特定加载方式等。
.rule('icons')
// 正则,解析 .svg 格式文件
// 在这个新的 'icons' 规则里,通过 test 方法指定这条规则应用的文件类型匹配模式,这里传入的正则表达式 /\.svg$/ 表示匹配以 .svg 为后缀的文件,也就是明确这条规则是针对 .svg 文件的,和前面规则名称里的意图相呼应,专门来处理 src/icons 目录下符合此类型的文件
.test(/\.svg$/)
// 解析的文件
// 调用 include 方法并传入通过 resolve 函数获取的 src/icons 目录的绝对路径,include 方法的作用是指定哪些目录下的文件要应用这条规则进行处理,这里就明确了只有 src/icons 目录下的文件(结合前面的 .test 匹配 .svg 文件类型,实际就是 src/icons 目录下的 .svg 文件)才应用这条特殊的 'icons' 规则来处理。
.include.add(resolve('src/icons'))
// 结束
.end()
// 新增了一个解析的loader
// 在这个 'icons' 规则里,调用 use 方法并传入 'svg-sprite-loader',这是指定使用 svg-sprite-loader 这个 Webpack loader 来处理符合这条规则的文件(即 src/icons 目录下的 .svg 文件)。loader 是 Webpack 中用于对不同类型文件进行转换等操作的工具,svg-sprite-loader 可以将多个 .svg 文件合并为一个雪碧图(Sprite),便于在网页中更高效地使用图标等 SVG 资源。
.use('svg-sprite-loader')
// 具体的loader
// 再次明确指定要使用的 loader 名称为 svg-sprite-loader,这里其实和前面的 .use('svg-sprite-loader') 是相关联且作用基本一致的,是在链式配置中进一步确定使用的加载器信息,有些情况下这种重复指定可能是遵循特定配置语法要求或者为了更清晰明确加载器的使用情况。
.loader('svg-sprite-loader')
// loader 的配置
.options({
// 这是 options 对象里的一个配置项,对于 svg-sprite-loader 来说,symbolId 配置用于指定生成的雪碧图中每个 <symbol> 元素(SVG 里用于定义可复用图形元素的标签)的 id 属性的命名规则。这里设置为 'icon-[name]',意味着每个 <symbol> 元素的 id 会基于 SVG 文件的原名([name] 会被替换为实际文件名)来生成,格式如 icon-文件名,方便在 HTML 等地方通过这个 id 来引用对应的 SVG 图标元素。
symbolId: 'icon-[name]'
})
// 结束
.end()
}
}
/**
*
两个规则的区别
1. 适用范围不同
常规 .svg 规则(rule('svg') 定义的规则):
适用于除了 src/icons 目录之外的所有 .svg 文件。通过 .exclude.add(resolve('src/icons')) 操作,明确把 src/icons 目录下的 .svg 文件排除在这条规则的应用范围之外,意味着项目中其他位置的 .svg 文件会按照后续常规配置的加载方式(可能是转译为其他格式、进行常规的模块处理等,具体取决于整体项目中其他相关 Webpack 配置,这里未完整展示)来处理。
src/icons 目录下 .svg 专属规则(rule('icons') 定义的规则):
专门针对 src/icons 目录下的 .svg 文件生效。通过 .include.add(resolve('src/icons')) 精准指定只有该目录下的 .svg 文件才应用这条规则,并且后续配置了使用 svg-sprite-loader 来进行特定处理,与常规 .svg 文件处理区分开来。
2. 处理方式不同
常规 .svg 规则:
代码中未详细展示其具体的处理动作,但可以推测是按照一般的模块处理逻辑,比如可能是将 .svg 文件当作普通的静态资源模块,在构建时根据项目配置进行诸如复制到输出目录、进行必要的路径调整等常规操作,使其能在项目中正确被引用(例如在 HTML 里通过正确的相对路径引用 .svg 文件等)。
src/icons 目录下 .svg 专属规则:
使用了 svg-sprite-loader 这个特定的 loader 来处理。这个 loader 的作用是将多个 .svg 文件合并成一个雪碧图(Sprite),把每个 .svg 文件里的图形内容转化为 <symbol> 元素,并根据配置的 symbolId 规则(这里是 'icon-[name]')为每个 <symbol> 元素生成唯一的 id。这样在 HTML 等页面中就可以通过引用这个雪碧图以及对应的 id 方便地复用各个 SVG 图标,避免了多次单独加载 .svg 文件,提高了页面加载性能以及图标管理和使用的便利性。
*/
3.组件SvgIcon封装
- 在项目
src/compoent
目录下新增SvgIcon
文件夹,里面定义一个index.vue index.vue
组件作为子组件,将来被其他组件引用,- 以下代码中有内部和外部两种图标方式
3.1 index.vue
<template>
<!-- 外部 -->
<div
v-if="isExternal"
:style="styleExternalIcon"
class="svg-external-icon svg-icon"
:class="className"
></div>
<!-- 内部 -->
<svg v-else class="svg-icon" :class="className" aria-hidden="true">
<use :xlink:href="iconName"></use>
</svg>
</template>
<script setup>
import { defineProps, computed } from "vue";
import { isExternal as external } from "@/utils/validata.js";
// 创建为组件首先考虑到接收的参数
const props = defineProps({
icon: {
type: String,
required: true,
},
className: {
type: String,
default: "",
},
});
// 第二点考虑到是不是外部,还是内部,可以使用计算属性,computed.下面方法是判断外部的
const isExternal = computed(() => external(props.icon));
// 外部图标的话就添加一个样式
const styleExternalIcon = computed(() => ({
mask: `url(${props.icon}) no-repeat 50% 50%`,
"-webkit-mask": `url(${props.icon}) no-repeat 50% 50%`,
}));
console.log(isExternal.value, "isExternal");
// 内部图标
const iconName = computed(() => `#icon-${props.icon}`);
</script>
<style>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover !important;
display: inline-block;
}
</style>
4.关于require.context方法
- 核心就是使用了webpack内置的
require.context
方法 - 详细参数解释点击这里官网传送门
- 这里我的svg文件都放在了
src/icons/svg
目录下了(根据自己需求存放即可,不固定)在icons下再次创建index.js文件
/**
* 1:导入所有的svg图标
* 2:完成SvgIcon的全局注册
* https://webpack.docschina.org/guides/dependency-management/#requirecontext
* 通过 require.context() 函数来创建自己的 context
*/
import SvgIcon from '@/components/SvgIcon'
/**
* 搜索./svg下面的文件 ,是否有子目录,所以false,匹配的文件
*/
const svgRequire = require.context('./svg', false, /\.svg$/)//方法来svg注册,接收三个参数1:搜索./svg下面的文件 ,2是否有子目录,所以false,3匹配的文件.svg结尾的文件
//此时返回了一个require函,可以接收一个request的参数,用于requer的导入
//该函数提供三个属性,可以通过svgRequire.keys()获得所有的svg图标
//便利图标,吧图标作为request参数传入到,svgRequire,导入函数种,完成本地svg图标的导入
console.log(svgRequire.keys(), 'svgRequire.keys()')
svgRequire.keys().forEach(svgIcon => svgRequire(svgIcon))
export default app => {
//导出一个函数,这个函数接收app的一个参数,通过挨app函数的component方法完成注册,两个参数,一个名字,一个实例icon
app.component('svg-icon', SvgIcon)
}
5.全局注册
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'
import SvgIcon from '@/components/SvgIcon/index.vue'//注册的组件
import installIcons from '@/icons'//所有的icon
const app = createApp(App)
app.component('svg-icon', SvgIcon)
app.use(createPinia())
app.use(router)
app.use(installIcons)
app.mount('#app')
6.组件使用
<svg-icon icon="user"></svg-icon>
7.使用效果
8.目录结构