Skip to content

在Vite中项目优雅的使用Svg Icon

Posted on:2021年10月26日 at 14:09

1. 安装 svg-sprite-loader

npm i svg-sprite-loader --save-dev

2. SvgIcon component

<!-- /src/components/SvgIcon/icon.vue -->
<script setup lang="ts">
import { computed, useCssModule, useAttrs } from "vue";

export interface SvgIconProps {
  name: string;
}

const props = defineProps<SvgIconProps>();

const styles = useCssModule();
const attrs = useAttrs();

const iconName = computed(() => `#icon-${props.name}`);
const svgClass = computed(() => {
  const className = [styles["svg-icon"]];
  if (props.name) className.push(`icon-${props.name}`);
  return className;
});
</script>

<template>
  <svg :class="svgClass" v-bind="attrs">
    <use :xlink:href="iconName"></use>
  </svg>
</template>

<style module>
.svg-icon {
  /* width: 1em;
  height: 1em;*/
  fill: currentColor;
  vertical-align: middle;
  overflow: hidden;
}
</style>
// /src/components/SvgIcon/index.ts
import SvgIcon from "./icon.vue";
import type { SvgIconProps } from "./icon.vue";

export { SvgIcon };

export type { SvgIconProps };

3. 新建plugins文件下新建svgBuilder.ts

// /plugins/svgBuilder.ts
import { readFileSync, readdirSync } from "fs";
import { join as pathJoin } from "path";
import { Plugin } from "vite";

let idPrefix = "";
const svgTitle = /<svg([^>+].*?)>/;
const clearHeightWidth = /(width|height)="([^>+].*?)"/g;

const hasViewBox = /(viewBox="[^>+].*?")/g;

const clearReturn = /(\r)|(\n)/g;

const findSvgFile = (dir: string) => {
  const svgRes: string[] = [];
  const directory = readdirSync(dir, { withFileTypes: true });
  for (const dirent of directory) {
    if (dirent?.isDirectory()) {
      svgRes.push(...findSvgFile(pathJoin(dir, dirent.name, "/")));
    } else {
      const svg = readFileSync(pathJoin(dir, dirent.name))
        .toString()
        .replace(clearReturn, "")
        .replace(svgTitle, ($1: string, $2: string) => {
          let width = "0";
          let height = "0";
          let content = $2.replace(
            clearHeightWidth,
            (s1, s2: string, s3: string) => {
              if (s2 === "width") {
                width = s3;
              } else if (s2 === "height") {
                height = s3;
              }
              return "";
            }
          );
          if (!hasViewBox.test($2)) {
            content += `viewBox="0 0 ${width} ${height}"`;
          }
          return `<symbol id="${idPrefix}-${dirent.name.replace(
            ".svg",
            ""
          )}" ${content}>`;
        })
        .replace("</svg>", "</symbol>");
      svgRes.push(svg);
    }
  }
  return svgRes;
};

const svgBuilder = (path: string, prefix = "icon"): Plugin => {
  idPrefix = prefix;
  const res = findSvgFile(path);
  return {
    name: "svg-transform",
    transformIndexHtml(html) {
      return html.replace(
        "<body>",
        `<body>
                <svg xmlns="http://www.w3.org/2000/svg" 
                    xmlns:xlink="http://www.w3.org/1999/xlink" 
                    style="position: absolute; width: 0; height: 0">
              ${res.join("")}
              </svg>`
      );
    },
  };
};

export default svgBuilder;

4. 更新tsconfig.node.json

// tsconfig.node.json
{
  // ...
  "include": ["vite.config.ts", "./plugins/*"]
}

5. src 目录下新建icons文件夹内存放 svg 图标

6. vite.config.ts 中

import svgBuilder from "./plugins/svgBuilder";
import { resolve } from "path";

// ...
plugins: [
  // ...
  svgBuilder(resolve("./src/icons")),
];

7. Usage

<script setup lang="ts">
import { SvgIcon } from "@/components/SvgIcon";
</script>

<template>
  <!-- name 为 svg icon 的文件名 -->
  <SvgIcon name="cookie" />
</template>