webpack5 + React + TypeScript で svg を import して Component として使いたい
jsx として impot して使えるようになりたい
こんな感じが理想↓
import Svg from './sample.svg'
const MyComponent = () => {
// ...
return <Svg />
}
svgr が良さそう
SVGR is an universal tool to transform SVG into React components.
svg を React Component に変換してくれる
webpack 用の loader も用意されてる
Webpack setup
Install
npm install --save-dev @svgr/webpack
# or use yarn
yarn add --dev @svgr/webpack
Usage
webpack config の modules.rules
に loader 追加
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>
)
type declaration がない問題
svg を import しようとすると、型情報がないため怒られる。
import Star from './star.svg' // ここで怒られる
.svg
用のカスタム型定義ファイルを作って、project 内に置いておくことで対応。
declare module '*.svg' {
const content: React.FC<React.SVGProps<SVGElement>>;
export default content;
}
Storybook で使えるようにする
main.js
編集
.storybook/main.js
の webpackFinal
に@svgr/webpack
loader 追加。
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',
},
};
エラー出た
Failed to execute 'createElement' on 'Document': The tag name provided ('static/media/src/assets/logo.svg') is not a valid name.
Storybook webpack5 default config
ここで svg 用の loader 設定してるな
webpack5 の Asset Modules の asset/resource
をデフォで使ってるみたい
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ってことかな。
エラー解決
これで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]',
},
}