SVGR 插件

默认情况下,Rsbuild 会将 SVG 图片当作静态资源处理,处理规则可参考:引用静态资源

通过添加 SVGR 插件,Rsbuild 支持调用 SVGR,将 SVG 图片转换为一个 React 组件使用。

快速开始

安装插件

你可以通过如下的命令安装插件:

npm
yarn
pnpm
bun
npm add @rsbuild/plugin-svgr -D

注册插件

你可以在 rsbuild.config.ts 文件中注册插件:

rsbuild.config.ts
import { pluginSvgr } from '@rsbuild/plugin-svgr';

export default {
  plugins: [pluginSvgr()],
};

示例

注册插件后,当你在 JS 文件中引用 SVG 资源时,如果导入的路径包含 ?react 后缀,Rsbuild 会调用 SVGR,将 SVG 图片转换为一个 React 组件。

App.jsx
import Logo from './logo.svg?react';

export const App = () => <Logo />;
TIP

@rsbuild/plugin-svgr 从 0.4.14 版本开始支持上述用法。

如果导入的路径不包含 ?react 后缀,那么 SVG 会被当做普通的静态资源来处理,你会得到一个 URL 字符串:

import logoURL from './static/logo.svg';

console.log(logoURL); // => "/static/logo.6c12aba3.png"

Rsbuild 也支持具名导入 ReactComponent 来使用 SVGR:

App.jsx
import { ReactComponent as Logo } from './logo.svg';

export const App = () => <Logo />;

选项

如果你需要自定义 SVGR 的编译行为,可以使用以下配置项:

  • 类型:
type PluginSvgrOptions = {
  /**
   * 修改 SVGR 选项
   */
  svgrOptions?: import('@svgr/core').Config;
  /**
   * 是否允许同时使用默认导入和具名导入
   * @default false
   */
  mixedImport?: boolean;
};

svgrOptions

用于修改 SVGR 的选项,传入的对象会与默认值进行 deep merge。完整文档请参考 SVGR - Options

  • 类型: import('@svgr/core').Config
  • 默认值:
const defaultSvgrOptions = {
  svgo: true,
  svgoConfig: {
    plugins: [
      {
        name: 'preset-default',
        params: {
          overrides: {
            removeViewBox: false,
          },
        },
      },
      'prefixIds',
    ],
  },
};
  • 示例:
pluginSvgr({
  svgrOptions: {
    svgoConfig: {
      datauri: 'base64',
    },
  },
});

当你设置 svgoConfig.plugins 时,同名 plugin 的配置会被自动合并,比如下面的配置会与内置的 preset-default 进行合并:

pluginSvgr({
  svgrOptions: {
    svgoConfig: {
      plugins: [
        {
          name: 'preset-default',
          params: {
            overrides: {
              cleanupIds: false,
            },
          },
        },
      ],
    },
  },
});

合并后的 svgoConfig `如下:

const mergedSvgoConfig = {
  plugins: [
    {
      name: 'preset-default',
      params: {
        overrides: {
          removeViewBox: true,
          cleanupIds: false,
        },
      },
    },
    'prefixIds',
  ],
};

svgrOptions.exportType

设置 SVG React 组件的导出方式。

  • 类型: 'default' | 'named'
  • 默认值: undefined

exportType 可以设置为:

  • default:使用默认导出
  • named:使用具名导出(ReactComponent

比如把 SVG 文件默认导出的内容设置为 React 组件:

pluginSvgr({
  svgrOptions: {
    exportType: 'default',
  },
});

此时再使用默认导入,你会得到一个 React 组件,而不是 URL:

import Logo from './logo.svg';

console.log(Logo); // => React 组件

同时,你也可以通过指定 ?url 的 query 来导入 url,比如:

import logo from './logo.svg?url';

console.log(logo); // => 资源 url
TIP

svgrOptions.exportType 被设置为 'default' 时,具名导入(ReactComponent)将无法使用。

mixedImport

  • 类型: boolean
  • 默认值: false
  • 版本: >= 0.5.0

是否开启混合导入,允许同时使用默认导入和命名导入。

混合导入通常和 svgrOptions.exportType: 'named' 同时使用,比如:

pluginSvgr({
  mixedImport: true,
  svgrOptions: {
    exportType: 'named',
  },
});

此时引用的 SVG 文件会同时导出 URL 和 React 组件:

import logoUrl, { ReactComponent as Logo } from './logo.svg';

console.log(logoUrl); // -> string
console.log(Logo); // -> React component

局限性

建议优先使用 ?react 来将 SVG 转换为 React 组件,而不是使用混合导入。因为混合导入有如下局限性:

  1. 包体积增加:混合导入会导致单个 SVG 模块被编译为两种代码(即使部分导出没有被使用),这会增加产物的包体积。
  2. 编译速度下降:混合导入会产生额外的编译开销。即使代码中未使用到 ReactComponent 导出,SVG 文件仍然会被 SVGR 编译。而 SVGR 是基于 Babel 实现的,性能开销较大。

query

  • 类型: RegExp
  • 默认值: /react/
  • 版本: >= 0.5.2

用于自定义匹配 SVGR 转换的 query 后缀。

比如需要匹配带有 ?svgr 后缀的 import 路径:

pluginSvgr({
  query: /svgr/,
});
App.jsx
import Logo from './logo.svg?svgr';

export const App = () => <Logo />;

exclude

用于排除一部分 SVG 模块,这些 SVG 模块不会经过 SVGR 处理。

比如,项目中包含 a.svgb.svg,你可以将 b.svg 添加到 exclude:

pluginSvgr({
  svgrOptions: {
    exportType: 'default',
  },
  exclude: /b\.svg/,
});

在引用时,a.svg 会被转换为 React 组件,b.svg 会被当做普通的静态资源来处理:

src/index.ts
import component from './a.svg';
import url from './b.svg';

console.log(component); // => React 组件
console.log(url); // => 资源 url

excludeImporter

用于排除一部分模块,这些模块引用的 SVG 文件不会经过 SVGR 处理。

比如,项目中包含 page-a/index.tspage-b/index.ts,你可以将 page-b 添加到 excludeImporter:

pluginSvgr({
  svgrOptions: {
    exportType: 'default',
  },
  excludeImporter: /\/page-b\/index\.ts/,
});
  • page-a 中引用的 SVG 会被转换为 React 组件:
page-a/index.ts
import Logo from './logo.svg';

console.log(Logo); // => React 组件
  • page-b 中引用的 SVG 会被当做普通的静态资源来处理:
page-b/index.ts
import url from './logo.svg';

console.log(url); // => 资源 url
TIP

模块路径中的 query 比 excludeexcludeImporter 具有更高的优先级。比如某个模块被 exclude,添加 ?react 依然可以使它被 SVGR 转换。

类型声明

当你在 TypeScript 代码中引用 SVG 资源时,TypeScript 可能会提示该模块缺少类型定义:

TS2307: Cannot find module './logo.svg?react' or its corresponding type declarations.

此时你需要为 SVG 资源添加类型声明文件,请在项目中创建 src/env.d.ts 文件,并添加相应的类型声明。

  • 默认情况下,你可以添加如下类型声明:
declare module '*.svg' {
  const content: string;
  export default content;
}
declare module '*.svg?react' {
  const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
  export default ReactComponent;
}
  • 如果 svgrOptions.exportType 的值为 'default',则将类型声明设置为:
declare module '*.svg' {
  const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
  export default ReactComponent;
}
declare module '*.svg?react' {
  const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
  export default ReactComponent;
}
  • 如果 svgrOptions.exportType 的值为 'named',则将类型声明设置为:
declare module '*.svg' {
  export const ReactComponent: React.FunctionComponent<
    React.SVGProps<SVGSVGElement>
  >;
}
declare module '*.svg?react' {
  const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
  export default ReactComponent;
}
  • 如果 svgrOptions.exportType 的值为 'named',且开启了 mixedImport,则将类型声明设置为:
declare module '*.svg' {
  export const ReactComponent: React.FunctionComponent<
    React.SVGProps<SVGSVGElement>
  >;
  const content: string;
  export default content;
}
declare module '*.svg?react' {
  const ReactComponent: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
  export default ReactComponent;
}

添加类型声明后,如果依然存在上述错误提示,请尝试重启当前 IDE,或者调整 env.d.ts 所在的目录,使 TypeScript 能够正确识别类型定义。