storybook: Unable to run Storybook when importing svgs - Storybook v5

Describe the bug Unable to run Storybook when importing svgs into a component. (TS/Gatsby project).

To Reproduce

git clone git@github.com:elie222/elie-tech.git
cd elie-tech
git checkout storybook
yarn storybook

Expected behavior Storybook should run.

Screenshots

Code snippets

System:

  • OS: MacOS
  • Device: Macbook Pro 2015
  • Browser: chrome
  • Framework: react
  • Addons: -
  • Version: 5.x

Additional context I’m using Gatsby and TypeScript. The Gatsby site imports the svgs and runs fine. It uses the gatsby-plugin-svgr Gatsby plugin.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 12
  • Comments: 37 (4 by maintainers)

Most upvoted comments

With storybook 6

npm install --save-dev babel-plugin-inline-react-svg

main.js

module.exports = {
    babel: async (options) => {
        options.plugins.push('babel-plugin-inline-react-svg');
        return options;
    }
}

@elie222 looks like your svg rule is wrong for this use case, have a look at https://github.com/smooth-code/svgr/tree/master/packages/webpack#using-with-url-loader-or-file-loader.

My webpack.config.js contains this which works like a charm:

// remove svg from existing rule
config.module.rules = config.module.rules.map(rule => {
  if (
    String(rule.test) === String(/\.(svg|ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani)(\?.*)?$/)
  ) {
    return {
      ...rule,
      test: /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|ttf|woff|woff2|cur|ani)(\?.*)?$/,
    }
  }

  return rule
})

// use svgr for svg files
config.module.rules.push({
  test: /\.svg$/,
  use: ["@svgr/webpack", "url-loader"],
})

Thank you @snc , it is a nice solution but somehow it didn’t work for me. It was really helpful to solve mine! I am using this example in version 5.2.8

module.exports = async ({ config }) => {
  const fileLoaderRule = config.module.rules.find(rule => rule.test.test('.svg'));
  fileLoaderRule.exclude = /\.svg$/;
  config.module.rules.push({
    test: /\.svg$/,
    use: ["@svgr/webpack", "url-loader"],
  });
  return config;
};

Thank you @snc , it is a nice solution but somehow it didn’t work for me. It was really helpful to solve mine! I am using this example in version 5.2.8

module.exports = async ({ config }) => {
  const fileLoaderRule = config.module.rules.find(rule => rule.test.test('.svg'));
  fileLoaderRule.exclude = /\.svg$/;
  config.module.rules.push({
    test: /\.svg$/,
    use: ["@svgr/webpack", "url-loader"],
  });
  return config;
};

In storybook^6.0.0 beta one of the rules had an array as the test field for me like this: [ /\.stories\.(jsx?$|tsx?$)/ ], so the code above fails. This is my .storybook/webpack.config.js which disregards arrays:

module.exports = ({ config }) => {
  const fileLoaderRule = config.module.rules.find(
    (rule) => !Array.isArray(rule.test) && rule.test.test(".svg"),
  );
  fileLoaderRule.exclude = /\.svg$/;
  config.module.rules.push({
    test: /\.svg$/,
    use: ["@svgr/webpack", "url-loader"],
  });
  return config;
};

So ^ the above solution works … however if Storybook uses SVGs in their internal rendering some day, it would break. A slightly safer solution would be something like :

const Path = require('path');

const AppSourceDir = Path.join(__dirname, '..', 'src');

module.exports = ({ config }) => {
    // Disable the Storybook internal-`.svg`-rule for components loaded from our app.
    const svgRule = config.module.rules.find((rule) => 'test.svg'.match(rule.test));
    svgRule.exclude = [ AppSourceDir ];

    config.module.rules.push({
        test: /\.svg$/i,
        include: [ AppSourceDir ],
        use: [
            /* any-svg-loader */
        ]
    });

    return config;
};

If you so choose, you could have a more elaborate rule for evaluating which rule is the svgRule here.

Hmm… Well the linked project runs, but stories for components that make use of svgs no longer work:

Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your compo

An example of a problematic line:

import { ReactComponent as Favorite } from '../../assets/icons/favorite.svg'

Continuing to play with this to find a solution.

it works for me

webpackFinal: async (config, { configType }) => {
    // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
    // You can change the configuration based on that.
    // 'PRODUCTION' is used when building the static version of storybook.

    // Make whatever fine-grained changes you need
    config.module.rules = [
      ...config.module.rules.map(rule => {
        if (/svg/.test(rule.test)) {
          // Silence the Storybook loaders for SVG files
          return { ...rule, exclude: /\.svg$/i }
        }
        return rule
      }),
      // Add your custom SVG loader
      {
        test: /\.svg$/i,
        use: ['@svgr/webpack']
      }
    ]

    // Return the altered config
    return config;
  },

@LuisOsta by default it imports an SVG as a file path, just like any other static asset. turning it into a react component is a choice for your specific project. we should make it easier to override this with a storybook addon that does this configuration on your behalf.

  stories: ['../src/components/**/*.stories.@(js|mdx)'],
  addons: [
    '@storybook/addon-postcss',
    '@storybook/addon-essentials',
    '@storybook/addon-a11y',
  ],
  webpackFinal: async (config, { configType }) => {
    const file_loader_rule = config.module.rules.find(
      (rule) => rule.test && rule.test.test('.svg')
    );
    file_loader_rule.exclude = /svg$/;
    config.module.rules.push({
      test: /\.svg$/,
      use: ['@svgr/webpack', 'url-loader'],
    });

    return {
      ...config,
    };
  },
};

The babel-plugin-inline-react-svg solution ~worked, in that I could render the SVGs, but I couldn’t manage to customize the optimization options, which were removing viewBox and breaking responsivity.

Luckily the solution @csaden provided did the trick (thanks!).

Here’s my implementation of it in case it’s helpful

const fixSvg = async (config) => {
  // remove svg loader from webpack appConfig and use Storybook's svg loader instead
  config.module.rules = config.module.rules.filter(({ test }) => !test.test(".svg"));

  // add chill SVG config
  config.module.rules.unshift({
    test: /\.svg$/,
    use: [{
      loader: '@svgr/webpack',
      options: {
        replaceAttrValues: { '#FFF': "currentColor", '#000': "currentColor", '#FFFFFF': "currentColor", '#000000': "currentColor" }, // allows for color to be passed
        svgoConfig: { plugins: { removeViewBox: false } } // need viewbox for responsive
      },
    }],
  })
}

module.exports = {
  webpackFinal: async (config) => {
    await fixSvg(config)
    return config;
  },

  stories: [],
  addons: ['@storybook/addon-essentials'],
};

Hi everyone! Seems like there hasn’t been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don’t have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

You can try storybook-preset-inline-svg. It uses svg-inline-loader and you can specify inlining all SVG or only some.

For example

// Simple (inline all SVG with default options for svg-inline-loader)
addons: ['storybook-preset-inline-svg']

// Advanced (pass options to svg-inline-loader and filter which files to inline)
addons: [
  {
    name: 'storybook-preset-inline-svg',
    options: {
      include: /source\/.+\.svg$/,
      svgInlineLoaderOptions: {
        removeTags: true,
        removingTags: ['circle']
      }
    }
  }
]

Thanks, @snc for a solution that pointed me in the right direction. I only had to teak it a little bit by adding pdf to the test string.

@storybook 6.3.7

//.storybook/webpack.config.js
    // remove svg from existing rule
    config.module.rules = config.module.rules.map((rule) => {
        if (
            String(rule.test) ===
            String(
                /\.(svg|ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/,
            )
        ) {
            return {
                ...rule,
                test: /\.(ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/,
            }
        }

        return rule
    })

    // use svgr for svg files
    config.module.rules.push({
        test: /\.(svg)(\?.*)?$/,
        use: ['@svgr/webpack'],
    })

For me using babel-plugin-inline-react-svg in my .storybook/.babelrc does the trick.

{
    "plugins": [
      "@babel/plugin-transform-typescript",
      "@babel/plugin-proposal-class-properties",
      "@babel/plugin-proposal-optional-chaining",
      "babel-plugin-styled-components",
      "@babel/plugin-proposal-export-default-from",
      "babel-plugin-inline-react-svg"                                    // this line
    ],
    "presets": [
      "@babel/preset-env",
      "@babel/preset-typescript",
      "@babel/preset-react"
    ]
}

@elie222 I ran into problems here too. We have custom webpack config that includes svgr-loader, and are merging our config with Storybook’s webpack config.

I believe the issue is the Preview config of file-loader here: https://github.com/storybooks/storybook/blob/4da246bbe9413510b48d1ab8b9faf4fae4656d92/lib/core/src/server/preview/base-webpack.config.js#L36-L40

In our case, we needed to have our svgr-loader rule occur before that file-loader rule. You might be able to unshift your loader rule here so that it takes precedence, rather than pushing it.

if you have customer svg loaders and aliases.

`

// add SVGR instead

config.module.rules.filter((rule) => rule.test.test(".svg")).forEach((rule) => (rule.exclude = /\.svg$/i));

config.module.rules.unshift({
  test: /\.svg$/,
  oneOf: [
    {
      dependency: { not: ["url"] },
      use: [
        {
          loader: require.resolve("@svgr/webpack"),
          options: {
            prettier: false,
            svgo: false,
            svgoConfig: {
              plugins: [{ removeViewBox: false }]
            },
            titleProp: true,
            ref: true
          }
        },
        path.resolve(__dirname, "../webpack-custom-loaders/svg-url-loader.js")
      ]
    }
  ]
});

`

svg-url-loader.js

module.exports = function () { const path = JSON.stringify(this.resourcePath); return "export default new URL(${path}, import.meta.url).toString();" };

Yes. I was. If anyone is running into these issues check the linked repo for a solution. Thanks for help!

What worked out for me is from this post, basically:

npm install --save-dev @svgr/webpack
// .storybook/main.js
module.exports = ({ config }) => {
  const fileLoaderRule = config.module.rules.find(
    (rule) => !Array.isArray(rule.test) && rule.test.test(".svg"),
  );
  fileLoaderRule.exclude = /\.svg$/;
  config.module.rules.push({
    test: /\.svg$/,
    use: ["@svgr/webpack", "url-loader"],
  });
  return config;
};

I’m using 6.2.9 none of them didn’t work for me. Is there any other solution for this?

Thank you @snc , it is a nice solution but somehow it didn’t work for me. It was really helpful to solve mine! I am using this example in version 5.2.8

module.exports = async ({ config }) => {
  const fileLoaderRule = config.module.rules.find(rule => rule.test.test('.svg'));
  fileLoaderRule.exclude = /\.svg$/;
  config.module.rules.push({
    test: /\.svg$/,
    use: ["@svgr/webpack", "url-loader"],
  });
  return config;
};

Added the above to webpack.config.js and it worked for me. Importing the SVG as below:

import { ReactComponent as Icon } from "icon.svg";

<Icon />

Thanks @bkiac, that worked almost perfectly for my usecase. I’m importing .svg files into my components as such: import ClockIcon from '@icons/clock.svg'; so url-loader would transform it into base64, which would eventually fail to render. Keeping just @svgr/webpack in the use array fixed it.

So ^ the above solution works … however if Storybook uses SVGs in their internal rendering some day, it would break. A slightly safer solution would be something like :

const Path = require('path');

const AppSourceDir = Path.join(__dirname, '..', 'src');

module.exports = ({ config }) => {
    // Disable the Storybook internal-`.svg`-rule for components loaded from our app.
    const svgRule = config.module.rules.find((rule) => 'test.svg'.match(rule.test));
    svgRule.exclude = [ AppSourceDir ];

    config.module.rules.push({
        test: /\.svg$/i,
        include: [ AppSourceDir ],
        use: [
            /* any-svg-loader */
        ]
    });

    return config;
};

If you so choose, you could have a more elaborate rule for evaluating which rule is the svgRule here.

@hnryjms Where would I put this configuration? I haven’t touched the default config of storybook yet, so I’m not sure where this would go.

i was running into this problem in a monorepo setup and @snc’s solution mostly fixed it, but i also had to set removeViewBox: false to prevent the viewBox attribute from getting removed.

posting in case anyone has the same issue and happens upon this thread

config.module.rules.unshift({
  test: /\.svg$/,
  use: [{
    loader: '@svgr/webpack',
    options: {
      svgoConfig: {
        plugins: {
          removeViewBox: false,
        },
      },
    },
  }],
},

@storybook@6.2.9 webpack@5.39.1

I was able to get svgs working by removing Storybook’s svg loader and using the one suggested in Webpack5 docs. { test: /\.(svg)$/, type: "asset/inline", }

const appConfig = require("../webpack.config");
  webpackFinal: async (config) => {

    // remove svg loader from Storybook's config and use webpack appConfig svg loader instead
    config.module.rules = config.module.rules.filter(({ test }) => !test.test(".svg"));

    config.plugins.push(...appConfig.plugins);
    config.module.rules.push(...appConfig.module.rules);

    config.resolve = appConfig.resolve;
    return config;
  },

So ^ the above solution works … however if Storybook uses SVGs in their internal rendering some day, it would break. A slightly safer solution would be something like :

const Path = require('path');

const AppSourceDir = Path.join(__dirname, '..', 'src');

module.exports = ({ config }) => {
    // Disable the Storybook internal-`.svg`-rule for components loaded from our app.
    const svgRule = config.module.rules.find((rule) => 'test.svg'.match(rule.test));
    svgRule.exclude = [ AppSourceDir ];

    config.module.rules.push({
        test: /\.svg$/i,
        include: [ AppSourceDir ],
        use: [
            /* any-svg-loader */
        ]
    });

    return config;
};

If you so choose, you could have a more elaborate rule for evaluating which rule is the svgRule here.

That’s precisely what happens with Storybook ^6.0.28 and @svgr/webpack. Your solution fixed my build, thanks a lot!

@jackmccloy I had similar problem in a monorepo, but solved by using removeViewBox: false option. The other possible cause is that I was using a material design icon. Anyway, that code helped a lot.

my version

function removeSvg (rules) {
  return rules.map(({ test, ...rest }) => ({
    test: RegExp(test.source.replace('svg|', '')),
    ...rest
  }))
}

@OmarZeidan that PR was closed almost a year ago, not merged. I left it around for documentation’s sake.

@elie222 I ran into problems here too. We have custom webpack config that includes svgr-loader, and are merging our config with Storybook’s webpack config.

I believe the issue is the Preview config of file-loader here: https://github.com/storybooks/storybook/blob/4da246bbe9413510b48d1ab8b9faf4fae4656d92/lib/core/src/server/preview/base-webpack.config.js#L36-L40

In our case, we needed to have our svgr-loader rule occur before that file-loader rule. You might be able to unshift your loader rule here so that it takes precedence, rather than pushing it.


Thanks for sharing this! ☺️

However, the new version of Storybook has added pdf to the test, See here, just incase anyone is wondering why yours is not working as expected 😃

I am also expecting a fix for this coming soon, currently is in the next version of Storybook. Check out this PR 💃🏻💃🏻💃🏻

This is how i resolved it. `

module.exports = async ({ config, mode }) => {
const rules = config.module.rules;
const fileLoaderRule = rules.find(rule => rule.test.test('.js'));
fileLoaderRule.use[0].options.plugins.push([

    require.resolve('babel-plugin-named-asset-import'),
    {
        loaderMap: {
            svg: {
                ReactComponent:
                    '@svgr/webpack?-svgo,+titleProp,+ref![path]',
            },
        },
    },
])
console.log("svg rule", fileLoaderRule.use[0].options.plugins)
return config;
};`

@hnryjms FYI the “manager” webpack config (that renders Storybook’s UI) and “preview” config (that renders user’s components) are completely separate, so you should be able to do whatever you want that’s suitable to your codebase without having to worry about Storybook. 🤞

@hnryjms 's method works fine for me. I used the SVG Inline Loader to load every kind of svg (svg files, svg in html, inline svg via URI).

Thank you! That fixed it.

Were you able to get this working @elie222 ?