23

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.

johannchopin
  • 13,720
  • 10
  • 55
  • 101

4 Answers4

52

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;
    } 
};
Max Sinev
  • 5,884
  • 2
  • 25
  • 35
  • I said in my question that if I make a `console.log` of my rules there is no `/\.(svg|ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/`. `fileLoaderRule` still `undefined` in my case. – johannchopin May 11 '20 at 08:24
  • according to your error message some webpack configuration has file-loader configured ... so whats your storybook version? – Max Sinev May 11 '20 at 18:59
  • Sorry for the laaaate answer. I updated my question. – johannchopin Sep 16 '20 at 07:32
  • This finally allowed me to stop storybook from using url-loader for svgs! Thanks! – Pascal Pixel Nov 06 '20 at 12:20
  • Thank you, after fiddling around for long, this finally works. Your answer should be marked as the correct answer. – mrsimply Apr 09 '21 at 08:54
  • This worked when I changed it to unshift: `config.module.rules.unshift({ ... }) – einaralex Sep 04 '21 at 20:12
  • 1
    I got it working via this, however, I first had to downgrade svgr to v5.4.0, so it would require webpack v4 - I think in my case this was also an issue. – whitezo Jan 24 '22 at 10:24
0

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
  }
};
tim-phillips
  • 997
  • 1
  • 11
  • 22
0

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.

yar
  • 47
  • 6
-2

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} />
Cyber Valdez
  • 317
  • 3
  • 2