vite 插件制作

120 阅读5分钟

自己动手做一个vite插件

我希望可以实现一个SVG Sprite的效果,SVG Sprite 是指将多个 SVG 图标合并到一个 SVG 文件中,并通过 CSS 技术来控制显示其中一个图标的方法。它类似于 CSS Sprite,但是适用于 SVG 图标。

SVG Sprite解决的问题

  1. 减少 HTTP 请求次数:将多个 SVG 图标合并到一个文件中,可以减少 HTTP 请求的次数,从而提高页面的加载速度和性能。
  2. 更好的可维护性:将多个 SVG 图标合并到一个文件中,可以更方便地管理和维护这些图标,避免了出现多个版本和重复的图标。

了解vite的插件是怎么运作

Vite 的插件是基于 Rollup 插件系统设计的。它允许开发者编写插件来拦截 Vite 的构建过程,并对其进行扩展。

Vite 插件系统的核心概念是钩子函数(hook)。Vite 会在不同的构建阶段调用不同的钩子函数,插件可以在这些钩子函数中注册自己的逻辑。例如,当 Vite 构建项目时,它会依次调用以下几个钩子函数:

  1. configureServer: 在开发服务器启动前调用,可以在这个钩子函数中修改服务器配置。
  2. configResolved: 在 Vite 配置文件解析完毕后调用,可以在这个钩子函数中访问和修改 Vite 的配置。
  3. transform: 在编译源代码前调用,可以在这个钩子函数中对源代码进行转换。
  4. resolveId: 在解析模块标识符时调用,可以在这个钩子函数中修改模块标识符。
  5. load: 在加载模块时调用,可以在这个钩子函数中加载自定义的模块。

当 Vite 调用钩子函数时,插件可以访问到一些上下文对象,例如 Vite 的配置、构建状态等等。插件可以通过这些上下文对象来实现自己的逻辑。

实现思路

通过遍历指定的文件夹,读取其中的svg图片,让他们变成一个<symbol>添加到<svg>里面,最后在DOM 加载之后将这个含有全部svg图片的 <svg>,添加到页面中。

开造

第一步 导入要用到的工具

import path from 'path'
import fs from 'fs'
import store from 'svgstore' // 用于制作 SVG Sprites
import { optimize } from 'svgo' // 用于优化 SVG 文件

引入Node.js 中的 path 模块处理文件路径。

引入Node.js 中的fs 模块,它提供了对文件系统进行操作的 API。通过 fs 模块,我们可以读写文件、创建文件夹、删除文件等操作

svgstore 是一个 JavaScript 库,用于将多个 SVG 图标合并到一个 SVG Sprite

svgo 是一个基于 Node.js 的 SVG 优化工具,可以帮助我们压缩 SVG 图像,减小文件体积,从而提高页面性能。

第二步 给插件起个名

export const svgstore = (options = {}) => {
  const inputFolder = options.inputFolder || "src/assets/icons";
  return {
    name: "svgstore",
    resolveId(id) {},
    load(id) {},
  };
};
  1. 导出这个插件,方便vite调用我们,我们接受一个 options参数来确定用户的svg文件夹路径,当然还可以自定义一些其他选项,我并不打算发布所以可有可无
  2. vite规定每个插件都必须有名字,name: "svgstore"
  3. 确定自己需要使用的钩子,resolveId,load

第三步 返回虚拟模块

resolveId(id) {
      console.log(id);
      if (id === '@svgstore') {
        return 'svg_bundle.js'
      }
    },

当我在其他文件导入@svgstore时,返回一个虚拟模块,在浏览器中它会被编译成import "/XXX/dist/@id/svg_bundle.js";,显然它不存在于真实的文件中。

第四步 给虚拟文件补充内容

load(id) {
      if (id === 'svg_bundle.js') {
        const sprites = store(options);
        // 将svg所在文件夹的相对路径解析为一个绝对路径
        const iconsDir = path.resolve(inputFolder);
        for (const file of fs.readdirSync(iconsDir)) {
        // 同步读取指定目录下的所有文件名,然后拼接得到当前svg路径
          const filepath = path.join(iconsDir, file);
          const svgid = path.parse(file).name
          // 同步读取svg的内容,并用utf-8编码,然后将 SVG 图标添加到 Sprite 中
          let code = fs.readFileSync(filepath, { encoding: 'utf-8' });
          sprites.add(svgid, code)
          
        }
        // 优化svg文件
        const { data: code } = optimize(sprites.toString({ inline: options.inline }), {
          plugins: [
            'cleanupAttrs', 'removeDoctype', 'removeComments', 'removeTitle', 'removeDesc', 
            'removeEmptyAttrs',
            { name: "removeAttrs", params: { attrs: "(data-name|data-xxx)" } }
          ]
        })
        // 将SVG Sprite添加到dom中 
        return `const div = document.createElement('div')
        div.innerHTML = \`${code}\`
        const svg = div.getElementsByTagName('svg')[0]
        if (svg) {
             svg.style.position = 'absolute'
             svg.style.width = 0
             svg.style.height = 0
             svg.style.overflow = 'hidden'
             svg.setAttribute("aria-hidden", "true")
        }

      document.addEventListener('DOMContentLoaded', () => {
        if (document.body.firstChild) {
          document.body.insertBefore(div, document.body.firstChild)
         } else {
          document.body.appendChild(div)
       }
     })`
   }
 }

使用方法

// vite.config.js
export default defineConfig(() => {
  return {
  plugins: [
      svgstore()
     ] 
  }
 })
 
 // main.js
 import '@svgstore'

补充说明

使用 svgstore 可以方便地创建 SVG Sprite。以下是一个使用示例:

jsCopy code
import store from 'svgstore';

const sprite = store(); // 创建一个 SVG Sprite

// 添加多个 SVG 图标到 Sprite 中
sprite.add('icon1', '<svg>...</svg>');
sprite.add('icon2', '<svg>...</svg>');
sprite.add('icon3', '<svg>...</svg>');

// 获取合并后的 SVG Sprite
const svg = sprite.toString();

在这个示例中,我们首先通过 store() 方法创建了一个 SVG Sprite。然后,我们通过 add() 方法将多个 SVG 图标添加到 Sprite 中。最后,我们可以通过 toString() 方法获取合并后的 SVG Sprite 字符串。


通过 import { optimize } from 'svgo' 这样的方式引入 svgo 库,可以使用它提供的 optimize 方法来优化 SVG 图像。以下是一个使用示例:

import { optimize } from 'svgo';

const originalSvg = '<svg>...</svg>';

// 优化 SVG 图像
const optimizedSvg = optimize(originalSvg, {
  plugins: [
    "cleanupAttrs",
    "removeDoctype",
    "removeComments",
    "removeTitle",
    "removeDesc",
    "removeEmptyAttrs",
      {
        name: "removeAttrs",
        params: { attrs: "(data-name|data-xxx)" },
      },
  ],
}).data;

console.log(optimizedSvg);

在这个示例中,我们首先通过 optimize 方法将原始的 SVG 图像进行了优化。optimize 方法接收两个参数:原始的 SVG 字符串和一个配置对象。在配置对象中,我们可以指定要使用的优化插件。

最后,我们通过 data 属性获取优化后的 SVG 字符串。