SVGR 与资源 URL 保留

问题背景

SVGR 常用于 React 组件库,把 SVG 转成 React 组件:

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

这类组件转换本身不是 Rslib 需要特殊处理的重点。真正麻烦的是 SVG 同时作为组件和资源 URL 使用:

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

这里有两种语义:

  • Logo 是 React component。
  • logoUrl 是 SVG 文件的资源 URL。

应用构建和库构建对资源 URL 的处理目标不同。应用可以把 URL 绑定到 runtime publicPath;库应该保留可发布的相对资源引用,让消费者决定最终资源加载方式。

应用构建里的问题形态

在普通应用构建中,url-loader 或 file-loader 可能生成:

const logo = __webpack_require__.p + 'static/svg/logo.svg';

__webpack_require__.p 是 bundler runtime publicPath。对应用来说合理,因为应用控制资源部署位置。

但对库来说不合理。库产物不应该假设消费者应用的 publicPath,也不应该把 Rspack runtime publicPath 暴露给资源 URL。

Rslib 当前策略

Rslib 对 SVGR 做两层处理:

  1. 在 asset rule 层,把 SVGR url-loader 的 publicPath 替换成占位符。
  2. 在 processAssets 阶段,把占位符替换成相对 import 或 require。

占位符是:

__RSLIB_SVGR_AUTO_PUBLIC_PATH__;

后续 LibSvgrPatchPlugin 会扫描 JS asset,找到这个占位符,然后根据当前输出文件位置生成相对路径。

ESM 输出形态

源码:

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

export { logoUrl, Logo };

ESM 产物大致是:

import __rslib_svgr_url__0__ from '../static/svg/logo.svg';
import { jsx } from 'react/jsx-runtime';

const SvgLogo = (props) =>
  jsx('svg', {
    ...props,
  });

const logoUrl = __rslib_svgr_url__0__;

export { SvgLogo as Logo, logoUrl };

真实 SVG 文件也会 emit:

dist/esm/static/svg/logo.svg

CJS 输出形态

CJS 下大致是:

const jsxRuntime = require('react/jsx-runtime');

const SvgLogo = (props) =>
  jsxRuntime.jsx('svg', {
    ...props,
  });

const logoUrl = require('../static/svg/logo.svg');

exports.Logo = SvgLogo;
exports.logoUrl = logoUrl;

这里没有 __webpack_require__.p,而是相对 require

关键源码

Asset 规则适配:

  • packages/core/src/asset/assetConfig.ts:19

SVGR patch plugin:

  • packages/core/src/asset/LibSvgrPatchPlugin.ts:6

测试:

  • tests/integration/asset/svgr
  • tests/integration/bundle-false/svgr

为什么真实 SVG 会 emit

Rslib 默认对 ESM/CJS asset 设置:

dataUriLimit: 0;
assetPrefix: 'auto';

dataUriLimit: 0 表示默认不 inline asset,而是 emit 文件。所以 SVGR mixed import 中的 SVG 文件会存在于 dist,例如:

dist/esm/static/svg/logo.svg
dist/cjs/static/svg/logo.svg

JS 文件引用这个真实资源。

JS issuer 与 CSS issuer

同一个 SVG 可能出现在 JS import 或 CSS url 中。它们不能走同一套规则:

import logo from './logo.svg';
.logo {
  background: url('./logo.svg');
}

JS import 可能需要 SVGR 或 JS wrapper。CSS url 应保持 CSS 资源路径语义。

因此 assetConfig.ts 会复制 asset rule,为 CSS issuer 单独设置 oneOf,避免 CSS 资源被 JS/SVGR 逻辑污染。

Bundleless 下的特殊点

Bundleless 下,Rslib不支持某些 query import 形态按应用构建方式直接外露。SVGR 逻辑会调整 SVG rule issuer,让 SVG asset 能由 svgr-loader 正确处理。

目标仍然是:

  • 组件部分变成 JS。
  • URL 部分保留为相对资源引用。
  • 真实 SVG 文件 emit。
  • 不输出 runtime publicPath。

坏产物示例

错误形态可能是:

const logo = __webpack_require__.p + 'static/svg/logo.svg';

或者:

const logo = '/assets/logo.svg';

这对库来说都不够稳。第一个依赖 bundler runtime publicPath,第二个假设部署根路径。

正确形态应是相对 import 或 require。

修改风险

SVGR 相关逻辑依赖 Rsbuild/Rspack/SVGR loader 的 rule 结构。升级相关插件时要检查:

  • CHAIN_ID.RULE.SVG 是否变化。
  • SVGR oneOf id 是否变化。
  • url-loader 是否仍有 publicPath option。
  • mixed import 输出是否仍包含 URL 和 ReactComponent。
  • CSS issuer 是否仍走单独规则。

这块改动一定要看产物快照,不要只看构建是否成功。