如何在 Parcel 项目引用 SVG 图片作为组件直接渲染

228 阅读2分钟

背景:项目里引用了大量的SVG图片。正常情况下,我们直接 import 一个 svg 图片,作为 img 标签的 src 属性可以直接渲染。但是这样存在一个问题, 如果同一个 svg 图片需要在不同的容器下显示,怎么做? (所谓的不同容器, 就是宽度不同, 一个宽度100px, 一个宽度20px, 我们怎么做? 用两张图片来做?那有点浪费了,因为只是容器宽度不一样,但是图片内容一样。考虑到 svg 本身是矢量图,可以支持缩放,如果想用一张图来做,我们怎么做?)

最终的目标

  1. 只要图片内容一样,无论在什么宽度容器下展示,我们都希望用同一张图来展示,这张图可以支持放大与缩小
  2. 希望 SVG 图片 可以像 组件一样直接渲染,而不是作为 img 标签的 src 属性
  3. 我们如何在业务中如何把 SVG 当成一个组件来用的, 我们有一个 SvgWrapper 组件,接受 SVG 作为 children 渲染,同时它也有自己的样式属性,直接设置 宽度就可以让渲染的 SVG 放大或者缩小
export default function SvgWrapper({ children, className = '' }) {
  return (
    <div className={`w-[20px] ${className}`}>
      {
        children
      }
    </div>
  );
}
  1. 下面的案例可以让一张 SVG 在不同的宽度下渲染 (SvgWrapper 给的宽度不一样),并且保持图片不模糊
const Demo1 = () => {
  return <SvgWrapper className="w-[20px]">
    <SVGImg1/>
  </SvgWrapper>
}

const Demo2 = () => {
  return <SvgWrapper className="w-[100px]">
    <SVGImg1/>
  </SvgWrapper>
}

先看 Parcel 给的方案

  1. 官网给的配置

  1. 按照官网的配置打包结果 (直接报错)

如何解决把 SVG 图片当成组件来渲染

  1. 最终在 github 找到相关的办法, 参考链接 github.com/parcel-bund…

  1. 按照github 提供的方案 确实可以把一个 SVG 当作 组件来渲染,但是 SVG 的放大缩小没了(因为 svg ViewBox 属性丢失,这个是问题的根本)

新的问题 SVG 缩放功能没了

  1. 这次耶稣也没能救了,因为官方的 issues 上也有类似的问题,并且 大半年了也没人来解决 (看 issues 都是open,并且有一个 issues 时间有大半年了)

  1. 那我不一样,我喜欢尝试一点不一点的bug, 直接 debugger 看源码, 查看源码发现 Parcel 解析 SVG 用的是 @parcel/transformer-svg-react 这个 npm 包 的源码大概是如下

  1. 发现了 其实用的是 @svgr/plugin-svgo @svgr/plugin-jsx, 并且默认读取了 svgrrc.* 配置文件
  2. 接着去看 @svgr/plugin-svgo @svgr/plugin-svgo这两个包,最终关键的源码如下

  1. 看到这里就很好解决了

最终方案

  1. .parcelrc 配置文件
{
  "extends": "@parcel/config-default",
  "transformers": {
    "jsx:*.svg": ["@parcel/transformer-svg-react"],
    "jsx:*": ["..."]
  }
}

2. 在项目根目录建立文件 .svgrrc.js, 因为 Parcel 依赖了 svgr 这个仓库,这个仓库需要这个文件来处理 svg

module.exports = {
  dimensions: false
}
  1. 最终解决问题