vite+vue3下如何使用动态导入的svg-sprite

2021-09-20

  几个月前我刚接触vue3时就研究过,一直搞忘了分享。

起因

  我的vue项目一般都使用动态导入的svg-sprite,意思是:既要是个雪碧图,用<use> 标签复用;而且页面的svg应该是动态按需加载的。我觉得这样性能比较好,在vue2中实现比较简单,但是vue3+vite下我并未找到合适的解决方案,只有雪碧图没有动态导入。于是乎我就想自己做一个。

思路

就两点

  • 首先要做一个vite plugin,作为一个loader加载.svg文件,读取svg文件的内容,类似raw-loader
  • 然后需要一个component,它去动态加载svg文件,并把svg文件的内容拼接到雪碧图里。

代码

vite.config.ts中这样写:

import {defineConfig, Plugin} from 'vite'
import vue from '@vitejs/plugin-vue'
import fs from "fs";
import {dataToEsm} from "rollup-pluginutils";

const rawSvgPlugin:Plugin = {
  name: 'raw-svg-file-loader',
  transform(svg: string, filepath: string) {
    // 判断后缀是否为svg
    if (filepath.slice(-4) !== '.svg') return null;
    const content = fs.readFileSync(filepath).toString()
    return {
      // 直接返回svg文件的原始内容
      code: dataToEsm(content)
    }
  },
}
export default defineConfig({
  plugins: [vue(), rawSvgPlugin],
})

IconSvg.vue文件:

<script setup lang="ts">
// index.html里写一个<div id="SVG_CONTAINER"></div>
const SvgContainerId = "SVG_CONTAINER";

const props = defineProps({
  name: { type: String, required: true }
});

const icon = ref("");

onMounted(() => {
  watch(
    () => props.name,
    (name) => {
      const id = `icon-${name}`;
      const eId = "#" + id;
      const container = document.getElementById(SvgContainerId);
      // 存储已经加载过的svg
      const svgIconList: Set<string> = (window as any).svgIconList || ((window as any).svgIconList = new Set());
      if (!svgIconList.has(id)) {
        svgIconList.add(id);
        // 动态引入
        import(`../assets/svg/${name}.svg`).then((res) => {
          const svgElement = new DOMParser()
            .parseFromString(res.default, "image/svg+xml")
            .querySelector("svg");
          if (svgElement) {
            // 去掉无用的属性
            for (const key of ["width", "height", "x", "y"]) {
              svgElement.removeAttribute(key);
            }
            svgElement.id = id;
            container!.appendChild(svgElement);
          }
        }).catch(() => svgIconList.delete(id));
      }
      icon.value = eId;
    },
    { immediate: true }
  );
});
</script>

<template>
  <svg class="--icon-svg common-svg" aria-hidden="true">
    <use :href="icon" />
  </svg>
</template>

<style lang="scss">
svg.--icon-svg {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
</style>

main.ts里只需要全局注册IconSvg组件就行了:

import { createApp } from 'vue'
import App from './App.vue'
import IconSvg from "./assets/svg/IconSvg.vue";

createApp(App).component('svg-icon', IconSvg).mount('#app')

这样使用:

<!-- 对应home.svg -->
<svg-icon name="home"/>

小结

这样做问题是解决了,可以动态导入svg并生成雪碧图,但是方式有点不优雅,有点投机取巧的感觉sticker

评论