React.js で SVGファイルのサイズや色をCSSで変える方法

# 前提
- React.js, webpack
- ReactでSVGファイルをimgとして読むとスタイルをCSSで変更できない（サイズはできるが色はできない）のでinlineで読む必要がある
- 複数のサイズや色のために複数のSVGファイルを用意したくない
- SVGファイルを変換したReact Componentのファイルも作りたくない


# 概要
- inline SVGとして埋め込む方法３つ
- CSSでスタイルを上書きする方法

を紹介
利点としてはSVGをReact Component化せずそのまま使うことで、（デザイナーの用意した）素材を極力そのままにし、ファイル数も増やさずに色を変えることができる

# SVGの埋め込み方
## 1. raw-loader + dangerouslySetInnerHTML の場合
```js:webpack.config.js
        test: /\.svg$/i,
        loader: 'raw-loader',
```

```jsx:jsx
import Hoge from '... .svg'
...
<div dangerouslySetInnerHTML={{ __html: Hoge }} />
```

typescriptならパスを通したglobal型置き場に次のように書く

```typescript:global.d.ts
declare module '*.svg' {
  const content: string;
  export default content;
}
```

### 利点
- 変換も何もされないので詰まったり失敗しない

### 欠点
- dangerously

## 2. react-svg-loader の場合
https://github.com/boopathi/react-svg-loader/tree/master/packages/react-svg-loader を使う

```js:webpack.config.js
      {
        test: /\.svg$/,
        use: [
          "babel-loader",
          {
            loader: 'react-svg-loader',
            options: {
              svgo: {
                plugins: [
                  { removeViewBox: false }, // to enable overwriteing width/height by CSS
                  { moveElemsAttrsToGroup: false }, // to prevent attribute destruction for overwriting color by CSS
                ],
                floatPrecision: 2,
              },
            },
          },
        ],
      },
```
ここでsvgoのオプションをdisableしているのは自分の扱うsvgが壊れないように設定した項目だが、扱うsvgによって別のdisableが必要かもしれない

```jsx:jsx
import Hoge from '... .svg'
...
<Hoge />
```

typescriptならパスを通したglobal型置き場に次のように書く

```typescript:global.d.ts
declare module '*.svg' {
  const content: React.ComponentType;
  export default content;
}
```


### 利点
- 巨大ツールでない
- 噛ませるbabel-loaderを自由に指定できる
  - next.config.js の `defaultLoaders.babel` とか

### 欠点
- 最近メンテされてないっぽくてリリースがない
  - https://github.com/boopathi/react-svg-loader/issues/303 が起きたりする
- svgoを無効化できない
  - SVGの中身によっては更にsvgoのオプションをdisableしないとスタイルを上手く上書きできないかもしれない


## 3. svgr の場合
https://github.com/gregberge/svgr を使う以外のコードの部分はreact-svg-loaderと同じなので差分だけ書く

1. https://react-svgr.com/docs/webpack/ に従う
2. `svgr.config.js` を設定する

```js:svgr.config.js
module.exports = {
  svgoConfig: {
    plugins: {
      removeViewBox: false, // to enable overwriteing width/height by CSS
      moveElemsAttrsToGroup: false, // to prevent attribute destruction for overwriting color
    },
  },
};
```

最適化不要、またはファイルに直接かけておく方針なら、svgoを無効化してしまう

```js:svgr.config.js
module.exports = {
  svgo: false,
};
```

### 利点
- メンテ・リリースが続いている
- svgoを無効化できる
  - 破壊的変更のせいでうまくいかなかったら無効化で逃げれる
- star多し
- webpack以外にも対応

### 欠点
- 依存ライブラリがちょっと多そう

# スタイルの当て方
埋め込んだSVGやそのラッパーdivに対しCSSで以下のように書けばサイズや色を変えれる

```css
    svg {
      width: 70px;
      height: 70px;
    }
    svg * :not([stroke='none' i]) {
      stroke: red;
    }
    svg * :not([fill='none' i]) {
      fill: red;
    }
```

ポイントは、SVGの色指定はstrokeとfillがあるので、それらがnoneでない箇所を上書きする点

# 所感

- サクッと実現するならraw-loader + dangerouslySetInnerHTML
- キレイにimportして埋め込みたいなら svgr
- 2つのツールがデフォルトで使うsvgoのデフォルト設定がSVGを破壊的に変更するので曲者
- `@svgr/cli` などを使ってSVG用のReact Componentを作ってPropsで柔軟に中身を変えるのもアリだけど、SVG素材とReact Componentでほぼ重複するのも悲しいし、用意された素材との同期などが少し面倒

# 参考
- https://blog.kwst.site/201908203549/
- https://github.com/boopathi/react-svg-loader/tree/master/packages/react-svg-loader
- https://github.com/boopathi/react-svg-loader/issues/295
- https://github.com/boopathi/react-svg-loader/issues/261
- https://github.com/boopathi/react-svg-loader/issues/303
- https://github.com/gregberge/svgr
- https://react-svgr.com/docs/webpack/
- https://github.com/svg/svgo