Closed12

webpack5 + React + TypeScript で svg を import して Component として使いたい

nbstshnbstsh

jsx として impot して使えるようになりたい

こんな感じが理想↓

import Svg from './sample.svg'

const MyComponent = () => {
  // ...
  return <Svg />
}
nbstshnbstsh

svgr が良さそう

SVGR is an universal tool to transform SVG into React components.

svg を React Component に変換してくれる

https://react-svgr.com/docs/what-is-svgr/

nbstshnbstsh

Webpack setup

Install

npm install --save-dev @svgr/webpack
# or use yarn
yarn add --dev @svgr/webpack
nbstshnbstsh

Usage

webpack config の modules.rules に loader 追加

webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.svg$/i,
        issuer: /\.[jt]sx?$/,
        use: ['@svgr/webpack'],
      },
    ],
  },
}

これでOK
あとは svg file を React Component として import して使える

import Star from './star.svg'

const Example = () => (
  <div>
    <Star />
  </div>
)
nbstshnbstsh

type declaration がない問題

svg を import しようとすると、型情報がないため怒られる。

import Star from './star.svg' // ここで怒られる

.svg 用のカスタム型定義ファイルを作って、project 内に置いておくことで対応。

src/custom.d.ts
declare module '*.svg' {
  const content: React.FC<React.SVGProps<SVGElement>>;
  export default content;
}
nbstshnbstsh

Storybook で使えるようにする

main.js 編集

.storybook/main.jswebpackFinal@svgr/webpack loader 追加。

main.js
module.exports = {
  //...

  webpackFinal: async (config) => {
    // ...

    // svg loader 追加
    config.module.rules.push({
      test: /\.svg$/i,
      issuer: /\.[jt]sx?$/,
      use: ['@svgr/webpack'],
    });

    return config;
  },
  core: {
    builder: 'webpack5',
  },
};
nbstshnbstsh

エラー出た

Failed to execute 'createElement' on 'Document': The tag name provided ('static/media/src/assets/logo.svg') is not a valid name.

nbstshnbstsh

webpack5 の Asset Modules の asset/resource をデフォで使ってるみたい

https://webpack.js.org/guides/asset-modules/

asset/resource emits a separate file and exports the URL. Previously achievable by using file-loader.

今まで file-loader で実現していたことができると

All .png files will be emitted to the output directory and their paths will be injected into the bundles, besides, you can customize outputPath and publicPath for them.

import mainImage from './images/main.png';

img.src = mainImage; // '/dist/151cfcfa1bd74779aadb.png'

AssetModule によって、import した file の bundle 内の path に置き換えられるのね。

つまり、

import Star from './star.svg'

 // Start は fille の path  => React Component ではないのでエラー
const Example = () => (
  <div>
    <Star />
  </div>
)

この場合、Start は fille の path の striing が入るので、jsx で使おうとしててエラーが出てるのか。
そしたら、このデフォルトの loader の設定を取り除けばOKってことかな。

nbstshnbstsh

エラー解決

https://github.com/storybookjs/storybook/issues/9070#issuecomment-635895868

これでOK

module.exports = {
  //...

  webpackFinal: async (config) => {
    // ...

    // デフォの svg 用の loader から svg を対象外に設定
    const fileLoaderRule = config.module.rules.find(
      (rule) => rule.test && rule.test.test('.svg'),
    );
    fileLoaderRule.exclude = /\.svg$/;

    // svg loader 追加
    config.module.rules.push({
      test: /\.svg$/i,
      issuer: /\.[jt]sx?$/,
      use: ['@svgr/webpack'],
    });

    return config;
  },
  core: {
    builder: 'webpack5',
  },
};

やっていることとしては、この↓ asset/resource type の Asset Module から svg を除外している

        {
          test: /\.(svg|ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/,
          type: 'asset/resource',
          generator: {
            filename: isProd
              ? 'static/media/[name].[contenthash:8][ext]'
              : 'static/media/[path][name][ext]',
          },
        }
このスクラップは2022/12/07にクローズされました