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:
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.
@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 push
ing 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 thanpush
ing 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 />
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: