开发了一个rsbuild的svg-sprite-loader和插件

733 阅读1分钟

在准备使用rsbuild替换webpack的过程中,发现有个svg插件不支持,随手撸了一个简单的插件,

没有原来的svg-sprite-loader那么多功能,只是一个简单的尝试

rsbuild-svg-sprite-loader

rsbuild-svg-sprite-loader is a Rsbuild plugin to replace webpack svg-sprite-loader.

github rsbuild-plugin-svg-sprite-loader

Usage

Install:

pnpm add rsbuild-svg-sprite-loader -D

Add plugin to your rsbuild.config.ts:

// rsbuild.config.ts
import { pluginSvgSpriteLoader } from "rsbuild-svg-sprite-loader";

export default {
  plugins: [
    pluginSvgSpriteLoader({
      path: join(__dirname, 'src/icons'),
      symbolId: 'icon-[name]',
    }),
  ],
};

Options

SvgSpriteLoaderOptions

Some description.

  • path: string
  • symbolId: string
  • Example:
pluginSvgSpriteLoader({
  path: join(__dirname, 'src/icons'),
  symbolId: 'icon-[name]',
})

源码

import { readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
import type { RsbuildPlugin } from '@rsbuild/core';

export type SvgSpriteLoaderOptions = {
  path: string;
  // default: icon-[name]
  symbolId?: string;
};

// 正则表达式定义
const svgTitle = /<svg([^>]*)>/;
const clearHeightWidth = /(width|height)="([^"]*)"/g;
const hasViewBox = /viewBox="[^"]*"/;
const clearReturn = /[\r\n]/g;

// 查找并处理 SVG 文件
function svgFind(directoryPath: string, idPrefix: string): string[] {
  const svgs: string[] = [];
  const directs = readdirSync(directoryPath, { withFileTypes: true });

  for (const dirent of directs) {
    if (dirent.isDirectory()) {
      svgs.push(...svgFind(join(directoryPath, dirent.name, '/'), idPrefix));
    } else if (dirent.name.endsWith('.svg')) {
      const svgContent = readFileSync(join(directoryPath, dirent.name), 'utf-8')
        .replace(clearReturn, '')
        .replace(svgTitle, ($1, $2: string) => {
          let width = 0;
          let height = 0;
          let content = $2.replace(
            clearHeightWidth,
            (match: string, prop: string, value: number) => {
              if (prop === 'width') width = value;
              else if (prop === 'height') height = value;
              return ''; // 移除匹配的宽度和高度属性
            },
          );

          // 添加 viewBox 属性,如果不存在
          if (!hasViewBox.test(content)) {
            content += `viewBox="0 0 ${width} ${height}"`;
          }

          const name = idPrefix.replace(
            '[name]',
            dirent.name.replace('.svg', ''),
          );
          return `<symbol id="${name}" ${content}>`;
        })
        .replace('</svg>', '</symbol>');

      svgs.push(svgContent);
    }
  }

  return svgs;
}

// 创建 SVG 字符串
function createSvg(path: string, prefix: string): string {
  if (path === '') return '';

  const res = svgFind(path, prefix);
  return res.join('');
}

export const pluginSvgSpriteLoader = (
  options: SvgSpriteLoaderOptions,
): RsbuildPlugin => ({
  name: 'plugin-svg-sprite-loader',

  setup(api) {
    const str = createSvg(options.path, options.symbolId || 'icon-[name]');
    api.modifyHTMLTags(({ headTags, bodyTags }) => {
      bodyTags.unshift({
        tag: 'svg',
        attrs: {
          xmlns: 'http://www.w3.org/2000/svg',
          'xmlns:xlink': 'http://www.w3.org/1999/xlink',
          style: 'position: absolute; width: 0; height: 0',
        },
        children: str,
      });

      return { headTags, bodyTags };
    });
  },
});