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

Created on 19 Mar 2019  路  23Comments  路  Source: storybookjs/storybook

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

To Reproduce

git clone [email protected]: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.

babel / webpack question / support

Most helpful comment

@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"],
})

All 23 comments

@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.

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!

Were you able to get this working @elie222 ?

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

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.

@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! That 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 '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 @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;
};

@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. 馃

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;
};`

@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 馃拑馃徎馃拑馃徎馃拑馃徎

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

my version

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

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,
        },
      },
    },
  }],
},

@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.

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.

@tbelch-at-eHealth-Tec Storybook supports changing its default webpack config: https://storybook.js.org/docs/configurations/custom-webpack-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;
};

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.

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

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 />
Was this page helpful?
0 / 5 - 0 ratings

Related issues

wahengchang picture wahengchang  路  3Comments

miljan-aleksic picture miljan-aleksic  路  3Comments

tlrobinson picture tlrobinson  路  3Comments

sakulstra picture sakulstra  路  3Comments

dnlsandiego picture dnlsandiego  路  3Comments