19

I'm trying to add Storybook to an existing React app but getting errors with imported svg files. The svg is imported and used like:

import Border from './images/border.inline.svg'
...
<Border className="card__border" />

This works when the app is run and built, but I get an error in Storybook. How come?


Failed to execute 'createElement' on 'Document': The tag name provided ('static/media/border.inline.258eb86a.svg') is not a valid name.
Error: Failed to execute 'createElement' on 'Document': The tag name provided ('static/media/border.inline.258eb86a.svg') is not a valid name.

The default webpack.config.js has:

  ...
  {
    test: /\.inline.svg$/,
    loader: 'svg-react-loader'
  },
  ...

Also, the existing code uses webpack 3, and I'm using Storybook V4.

Mark Robson
  • 1,298
  • 3
  • 15
  • 40

6 Answers6

19

This is happening because Storybook's default webpack config has its own svg config:

{ 
  test: /\.(svg|ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani)(\?.*)?$/,
  loader: 'file-loader',
  query: { name: 'static/media/[name].[hash:8].[ext]' }
},

I'm pretty sure this is the cause, because you can see the path outlined in error message: query: { name: 'static/media/[name].[hash:8].[ext]' } -> static/media/border.inline.258eb86a.svg

The solution can be to find the existing loader & change / or add an exclude rule to it. Here's an example of a custom .storybook/webpack.config.js:

// storybook 4
module.exports = (_, _, config) => {
// storybook 5
module.exports = ({ config }) => {
  const rules = config.module.rules;

  // modify storybook's file-loader rule to avoid conflicts with your inline svg
  const fileLoaderRule = rules.find(rule => rule.test.test('.svg'));
  fileLoaderRule.exclude = /\.inline.svg$/;

  rules.push({
    test: /\.inline.svg$/,
    ...
    }],
  });

  return config;
};
Derek Nguyen
  • 11,294
  • 1
  • 40
  • 64
  • Thanks! I tried but getting some different errors. I've just realised too, the existing code uses webpack 3. Storybook expects v4? I'll update the question – Mark Robson Jan 21 '19 at 17:17
  • 1
    Hey Mark! If you're using Storybook 4, it'll be using webpack v4. What different errors did you get? – Derek Nguyen Jan 21 '19 at 17:22
  • Thanks! I get SyntaxError: Duplicate parameter name not allowed in this context with your snippet. I'll see if I can reuse the one we have in webpack now. Thanks again! – Mark Robson Jan 22 '19 at 10:49
  • ERROR in ../border.inline.svg Module build failed (from ./node_modules/file-loader/dist/cjs.js): TypeError: Cannot read property 'context' of undefined at Object.loader (/xxx/node_modules/file-loader/dist/index.js:34:49) @ ./src/js/components/Card.js 39:0-82 122:29-43 @ ./src/js/components/stories/Card.story.js @ ./src sync .story.js @ ./.storybook/config.js @ multi ./node_modules/@storybook/core/dist/server/common/polyfills.js ./node_modules/@storybook/core/dist/server/preview/globals.js ./.storybook/config.js ./node_modules/webpack-hot-middleware/client.js?reload=true – Mark Robson Jan 22 '19 at 10:53
  • Thanks @Derek Nguyen ! – Mark Robson Jan 22 '19 at 10:56
  • Hhm I’m not sure what causes the new error... Glad you got it working in the end @MarkRobson! – Derek Nguyen Jan 23 '19 at 02:06
  • I also had to upgrade file-loader too. Cheers! – Mark Robson Jan 23 '19 at 09:44
  • FWIW, I just excluded /\.svg/ from storybooks rule - excluding inline.svg didn't work for me, I had my own svg loader in the projects webpack config that was being merged with storybooks config in this function. Thanks for the hint, though! – Adam Jenkins Apr 14 '23 at 12:04
7

It appears that Storybook V6 they have changed the default Webpack config. I found that the above answers didn't work for me.

They no longer have an SVG rule, therefore testing for SVG will either error or return back undefined.

There is a oneOf rule on the module.rules which contains a loader without a test as the last rule:

{
      loader: '/Users/alexwiley/Work/OneUp/resources/client/node_modules/react-scripts/node_modules/file-loader/dist/cjs.js',
      exclude: [Array],
      options: [Object]
}

This is the culprit, you need to make sure that the file load is excluding all inline SVG file otherwise it will error.

Add the following to your .storybook/main.js file:

webpackFinal: async(config, { configType }) => {
  config.module.rules.forEach((rule) => {
    if (rule.oneOf) {
      // Iterate over the oneOf array and look for the file loader
      rule.oneOf.forEach((oneOfRule) => {
        if (oneOfRule.loader && oneOfRule.loader.test('file-loader')) {
          // Exclude the inline SVGs from the file loader
          oneOfRule.exclude.push(/\.inline\.svg$/);
        }
      });
      // Push your SVG loader onto the end of the oneOf array
      rule.oneOf.push({
        test: /\.inline\.svg$/,
        exclude: /node_modules/,
        loader: 'svg-react-loader', // use whatever SVG loader you need
      });
    }
  });
  return config;
}
SomeKittens
  • 38,868
  • 19
  • 114
  • 143
Alex Wiley
  • 146
  • 2
  • 6
6

In Storybook 6, You have to import it like this:

import { ReactComponent as Border } from './images/border.inline.svg'

Try that if it also works for your version since this question is from a year ago.

Cyber Valdez
  • 317
  • 3
  • 2
2

I got it working with

...
module.exports = {
  module: {
    rules: [
      {
        test: /\.inline.svg$/,
        loader: 'svg-react-loader'
      }
    ]
  }
}
Mark Robson
  • 1,298
  • 3
  • 15
  • 40
2

This is another way that fixed the issue for me

import Border from './images/border.inline.svg'

And then in your code

<img src={Border} alt="Border" className="w-25"/>
user2210411
  • 1,497
  • 1
  • 9
  • 7
1

For me it was happening as I was using wrong tag name:

import React from 'react';
import RMDBLogo from '../../images/react-movie-logo.svg';
import TMDBLogo from '../../images/tmdb_logo.svg';


import { Wrapper, Content,LogoImg,TMDBLogoImg } from './Header.styles';

const Header = () => (
    <Wrapper>
        <Content>
            <LogoImg src={RMDBLogo} alt='RMDBLogo' />
            <TMDBLogo src={TMDBLogo} alt='TMDBLogo'/>
        </Content>
    </Wrapper>
);

export default Header;

I had imported TMDBLogoImg component while I'm using TMDBLogo tag inside Content tag.

RBT
  • 24,161
  • 21
  • 159
  • 240