'Storybook: Failed to execute 'createElement' on svg files using @svgr/webpack
My error in the first place is exactly the same as the one described here. I'm using the @svgr/webpack package to import my .svg files as a React component like that:
import Shop from './icons/shop.svg'
return <Shop />
It works fine on my app but when I tried to do the same in Storybook I get this error:
Failed to execute 'createElement' on 'Document': The tag name provided ('static/media/shop.61b51e05.svg') is not a valid name.
So I added the loader into my .storybook/main.js file to extends the default Storybook webpack config:
// ...
webpackFinal: async config => {
config.module.rules.push({
test: /\.svg$/,
enforce: 'pre',
loader: require.resolve('@svgr/webpack'),
});
The error still occurred so I tried to override the default Storybook test for .svg files as suggested in the answer of the previous question:
const fileLoaderRule = config.module.rules.find(rule => { rule.test !== undefined ? rule.test.test('.svg') : '' });
fileLoaderRule.exclude = /\.svg$/;
But then I get this error:
TypeError: Cannot set property 'exclude' of undefined
So I decided to make a console.log of the rule.test and strangely the only default tests coming from Storybook config are theses:
{
test: /\.md$/,
...
}
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
...
}
{
test: /\.js$/,
...
}
{
test: /\.(stories|story).mdx$/,
...
}
{
test: /\.mdx$/,
...
}
{
test: /\.(stories|story)\.[tj]sx?$/,
...
}
{
test: /\.(ts|tsx)$/,
...
}
As you can see there is no test that affects a .svg file. So does someone have an idea why my configuration is not working using:
{
test: /\.svg$/,
enforce: 'pre',
loader: require.resolve('@svgr/webpack'),
}
My storybook version is 6.0.0-beta.3.
Solution 1:[1]
Your find callback always returns undefined. Change it to return correct bool:
const fileLoaderRule = config.module.rules.find(rule => rule.test && rule.test.test('.svg'));
Anyway storybook default config should have a rule for images with this test:
/\.(svg|ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/.
So your code (but with correct find callback) works fine for me.
Final main.js:
module.exports = {
stories: ['../src/**/*.stories.[tj]s'],
webpackFinal: config => {
// Default rule for images /\.(svg|ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/
const fileLoaderRule = config.module.rules.find(rule => rule.test && rule.test.test('.svg'));
fileLoaderRule.exclude = /\.svg$/;
config.module.rules.push({
test: /\.svg$/,
enforce: 'pre',
loader: require.resolve('@svgr/webpack'),
});
return config;
}
};
Solution 2:[2]
Since I'm using webpack@4, and therefore @svgr/webpack@5, I had to use url-loader.
module.exports = {
stories: ['../src/**/*.stories.@(ts|tsx|js|jsx)'],
webpackFinal: config => {
// remove svg from existing rule
const fileLoaderRule = config.module.rules.find(
(rule) => rule.test && rule.test.test('.svg')
)
fileLoaderRule.exclude = /\.svg$/
config.module.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack', 'url-loader'],
})
return config
}
};
Solution 3:[3]
I tried all methods written here, but always got an error:
TypeError: Cannot set property 'exclude' of undefined
Finally, i consoled config.module.rules and saw its structure: the needed rule is config.module.rules[5].oneOf[2]. So i've just hardcoded it with: config.module.rules[5].oneOf[2] = ['@svgr/webpack'].
I think that it's a temporary dirty solution, but it works. Hope somebody will point out how to make it more clean and bulletproof.
Solution 4:[4]
Just import it like this:
import { ReactComponent as Shop } from './icons/shop.svg'
It's transforming it as a React Component so that you can pass properties to it:
<Shop fill="tomato" {...args} />
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Rohmer |
| Solution 3 | yar83 |
| Solution 4 | Cyber Valdez |
