My own path of using SVGs with webpack has settled me comfortably with loading .svg files as react components via https://github.com/jhamlet/svg-react-loader (svg-react-loader@next as of time of writing). This allows you to treat an SVG just like any other React component, and renders out inline SVGs. For example:
const MySvg = require('./mySvg.svg')
const MyApp = () =>
<div>
// some components!
<MySvg /> // could also pass fill='#ccc' or style={styleObj}
</div>
// renders
/*
<div>
<svg ...> ...etc </svg>
</div>
*/
Inline SVGs offer the most amount of flexibility in terms of styling, animation, and manipulating props. SVGs in the browser are now more supported than ever. They are quickly replacing icon fonts as the preferred way to do icons in your app.
I'd enjoy hearing the community's thoughts on this.
So, for example, on the starting page, instead of
import logo from './logo.svg';
...
<img src={logo} className="App-logo" alt="logo" />
...
it would be
import Logo from './logo.svg';
...
<Logo />
...
Isn't that a little opinionated?
This video convinced me that the way we should be using SVG is inline. This table, showing the features and benefits of implementing SVG using the three available techniques, cinched it for me:
An SVG is different from all other assets (image, sound, video, etc) in that isn't a binary blob, and so can be more easily manipulated in our app. An SVG has more in common with a stateless react component than a JPEG.
You can still do this:
import Logo from './logo.svg';
...
<Logo className='App-logo' ariaLabel='logo' />
...
Then again, I suppose I haven't thought through the scenario of using an SVG asset in CSS…
😬
I've ran into a similar problem/issue, I'd like to be able to reference SVGs and then adjust their fill
colour based on some API call response. You can't do this with an external reference to the SVG, you'd need to inline them. Is there a way right now to basically get the SVG text and even just dump it in?
as a workaround you can do something like:
import React from 'react';
export default () =>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M2.001 9.352c0 1.873.849 2.943 1.683 3.943.031"/>
</svg>
then you can take in props and use those to adjust the fill
@kauffecup I'm new to ES6, React and this library so apologies if silly questions... Are you saying make a file called something.js
that is essentially a wrapper around an SVG? How would you call this in another file.
Also, thank you.
@kauffecup I tried your method of inlining an SVG into a component, but am seeing a webpack hot dev client error that it cannot build the module...
Any ideas what might be causing this?
Update for anybody else seeing this: the issue was resolved when I removed namespaces from the svg, since jsx
is not xml
, they cause errors.
@kauffecup that's adding extra js cruft to what should be a standalone svg resource. Not ideal.
@edhenderson I think using svg-react-loader that I mention in the OP will get you what you want. It's working perfectly for me, of course you have to eject from create-react-app first.
I appreciate the suggestion but I know some people are already using SVGs as sprites so I wouldn't like to break them. Especially given that it is easy to declare SVG inside a React component anyway.
We have some options:
Assume we have components/Pin.svg
it can be transformed (by webpack, in memory) to:
export default "components/Pin.hash.svg"
export function Pin(props) {
return (
<svg ...>...</svg>
)
}
This way it will backward compatible with current CRA convention about SVGs.
Advantage of using SVGs directly (instead of copy-pasting it to React component) is that you can edit it in visual editor, like Inkscape.
Create CLI tool which will generate (and save to the disk) React Components from SVG files. It additionally can use svgmin. Developer can edit generated files afterwords or regenerate components whenever SVG changes.
I feel like this issue has been closed prematurely. The main motivation seems to be BC (which is understandable) but the following statement is not necessarily true:
Especially given that it is easy to declare SVG inside a React component anyway.
While it is easy to declare SVG in React code, that means it's all the more complicated to work with actual SVG files if you want them inline (as is the current recommendation).
You basically need to manually create one component per icon and paste the SVG contents in them (and god knows some app can have a lot of icons), accounting for attributes changes (class => className) and such.
That or run some kind of third party script to do the conversion for you; in both cases you run the risk of your React SVG components getting out of sync with the actual SVG files, which is what your designer is gonna work with and give to you.
My point is, considering the current recommendation seems to be to use inline SVGs it seems counterproductive for the default behavior of CRA to be using <img>
tags – all the more since CRA is all about convention over configuration and sane defaults. In that spirit I think it'd better long term to not prioritize BC over good defaults.
Agree with @Anahkiasen , I found this looking for a solution to using inline SVGs without ejecting and modifying the Webpack loader, and was disappointed to find that loading them as images was the expected behavior. I understand the reasoning, but the future-proof path and what I've done on every other project is Graphics Program => SVGO => Webpack loader => inline in document.
Thanks for chiming in and keeping this discussion going. The fact that new people keep finding their way to this thread means more and more people expect CRA to allow them to:
Handling svgs as their own media type with their own loader allows for all of this. I hope we can re-open this issue one day.
I realized that my first comment is not clear on what I'm proposing. My solution (1) is backward compatible with existing convention.
import Pin from 'Pin.svg'; // you get svg url or base64 encoded data url
// which is the same as
import { default as Pin } from 'Pin.svg';
// but if you do this
import { Pin } from 'Pin.svg'; // you get React component with inline svg
I achieved this using https://github.com/jhamlet/svg-react-loader explicitly on the one SVG I wanted to inline.
import Logo from '-!svg-react-loader!./logo.svg';
The -!
allows it to bypass the existing loaders so I can get away without ejecting.
@gwagener I'm getting module resolve issues when using -!svg-react-loader!../path/to/file.svg
;
Are there any additional steps required?
@kaumac Hm, I'm actually using create-react-app-ts so I had to add a custom.d.ts
with
declare module '*.svg' {
const content: string;
export default content;
}
For the type definitions of SVG. Without that I also got module resolve issues. Also, I'm still getting module resolve issues testing with Jest.
Hm, have tested in vanilla CRA instead of TypeScript and see that my suggestion won't work because Webpack loader syntax isn't allowed. ⏏
This worked for me after installing raw-loader
.
// Icon.js
const Icon = ({ icon, ...props }) => {
const svg = require(`!raw-loader!./icons/${icon}.svg`);
return <span {...props} dangerouslySetInnerHTML={{__html: svg}}/>;
};
Then I just use something like <Icon icon="checkmark" className="icon"/>
. I originally saw this pattern in a boilerplate but I can't remember which one...
@kme211
Please note that using Webpack loaders like this in a Create React App project is not supported and can break in any patch release. If you want to use Webpack loaders directly please eject (unless you’re okay with potential breakage in the future).
Regarding proposal in https://github.com/facebookincubator/create-react-app/issues/1388#issuecomment-308054716, it’s a bit counter intuitive to me. People are already confused by naming vs default exports, and I think repurposing it for an escape hatch doesn’t help.
Let’s reopen as this seems popular. It’s not clear to me yet how to approach it in a way that wouldn’t break existing use cases.
@gaearon does not break existing case
import Pin from 'Pin.svg'; // you get svg url or base64 encoded data url
// which is the same as
import { default as Pin } from 'Pin.svg';
// but if you do this
import { Pin } from 'Pin.svg'; // you get React component with inline svg
Yes, but as I mentioned in https://github.com/facebookincubator/create-react-app/issues/1388#issuecomment-313976907, it’s pretty confusing. Maybe that’s okay for an escape hatch. If you send a PR for this, I can take a good look and make a more informed decision.
I should point out that any of the above proposed workarounds that use custom loader syntax no longer work due to #803.
@gaearon It's not a scape hatch, it's a discriminator on when to use the svg React component or when not to. I can't see a more elegant way for it. True that can be misleading, but documentation could minimize it.
As an alternative to the default vs named import, how about updating the CRA webpack config to exclude a given extension (__.inline.svg__?) from url-loader/file-loader; and handling that extension with svg-react-loader
.
eg.
// webpack.config
loaders: [
{
test: /\.(svg|png|jpg|etc)$/,
exclude: [/\.inline\.svg$/],
use: ['url-loader']
},
{
test: /\.inline\.svg$/,
use: ['svg-react-loader']
}
]
I like this. How about .component.svg
? Could also do .component.html
while we're at it since SVG is only a subset of valid things you can put in innerHTML. Would you like to raise a new issue with more detailed proposal?
Or maybe .react.svg
and .react.html
.
I filed https://github.com/facebookincubator/create-react-app/issues/2961.
If we implement this, it's the most likely way forward, so I'll close this issue.
+1 for .component.(svg|html). .react.svg implies you'll be using the asset in a react app forever, but there are plenty of other frameworks where the same svg could be used as a 'component'.
really glad I posted this issue!
I have a question for you fine people, including @gaearon. I've been able to render some <svg>
s from React:
https://github.com/mocon/svg-constellations-react
However, I would like to animate some of the attributes within the SVGs, such as the radius of the circles. I tried to do so using the animated
library (see the example component below), but React gets mad and doesn't allow the returning of non-SVG elements (in my case, an <Animated.svg />
).
Does anyone have any tips on how to animate an internal SVG attribute? Many thanks in advance!
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Animated from 'animated';
class Star extends Component {
constructor() {
super();
this.state = {
coronaWidth: new Animated.Value(4.8)
};
}
componentDidMount() {
Animated.spring(this.state.coronaWidth, { toValue: 8 }).start();
}
render() {
const { width, height, xPos = 0, yPos = 0 } = this.props;
return (
<Animated.svg
width={ `${width}px` }
height={ `${height}px` }
x={ `${xPos - (width / 2)}px` }
y={ `${yPos - (height / 2)}px` }
version="1.1"
viewBox="0 0 43.9 43.9"
style={ { 'enableBackground': 'new 0 0 43.9 43.9' } }
xmlSpace="preserve"
>
<filter id="blurInner" x="-100%" y="-100%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="0.3" />
</filter>
<filter id="blurOuter" x="-100%" y="-100%" width="400%" height="400%">
<feGaussianBlur in="SourceGraphic" stdDeviation="3.5" />
</filter>
<filter id="blurTail">
<feGaussianBlur in="SourceGraphic" stdDeviation="0.5" />
</filter>
<circle className="star__outer-glow" filter="url(#blurOuter)" cx="22" cy="22" r={ this.state.coronaWidth } />
<path className="star__tail" filter="url(#blurTail)" d="M22,0c0,0-0.5,20.7-1.2,22H22h1.2C22.4,20.7,22,0,22,0z"/>
<path className="star__tail" filter="url(#blurTail)" d="M22,43.9c0,0,0.5-20.7,1.2-22H22h-1.2C21.5,23.2,22,43.9,22,43.9z"/>
<path className="star__tail" filter="url(#blurTail)" d="M43.9,22c0,0-20.7-0.5-22-1.2V22v1.2C23.2,22.4,43.9,22,43.9,22z"/>
<path className="star__tail" filter="url(#blurTail)" d="M0,22c0,0,20.7,0.5,22,1.2V22v-1.2C20.7,21.5,0,22,0,22z"/>
<circle className="star__inner-glow" filter="url(#blurInner)" cx="22" cy="22" r="3"/>
</Animated.svg>
);
}
}
Star.propTypes = {
width: PropTypes.number,
height: PropTypes.number,
xPos: PropTypes.number,
yPos: PropTypes.number
};
export default Star;
I do not know if this issue is still under consideration. I had the same problem and I made svgr. If you are OK I could submit a PR.
I am using this popular library to load SVGs: https://www.npmjs.com/package/react-svg-loader
import IconEnvelope from '-!react-svg-loader!./envelope.svg';
Thanks for the tricky -!
with react-svg-loader
, helps a ton.
This is how I did it, it's also required to disable the eslint for that particular line, or an error will be thrown at compilation. ("react-scripts": "1.0.14")
import IconLogo from "-!react-svg-loader!../assets/logo.svg"; // eslint-disable-line import/no-webpack-loader-syntax
FYI, my use case was to load the company logo, an SVG file and display it, that's it. I wish it had been easier to do, rather than installing a 3rd party software and figure out how to do the setup without ejecting. ;)
@Vadorequest unfortunately it breaks production build. Please look my question and my answer on StackOverflow :D
I haven't tested it in production yet...
But I just found an online app to convert non-react SVG to react valid SVG, very useful:
https://github.com/balajmarius/svg-to-jsx-gui
I appreciate the suggestion but I know some people are already using SVGs as sprites so I wouldn't like to break them. Especially given that it is easy to declare SVG inside a React component anyway.
Backwards compatibility should never be the reason to prevent features from being deployed. Apps don't auto-update to newer react versions, which allows developers for fixes if they were using it differently.
Not implying that it's valid in this case, SVG is XML namespace. It should work the same way as import what from './something.html'
so...
I haven't tested it in production yet..
Just run yarn build
. Very interesting - is it will work for you or not.
@inferusvv Indeed...
Creating an optimized production build...
Failed to compile.
Failed to minify the code from this file:
./node_modules/react-svg-loader/lib/loader.js!./src/assets/logo.svg:6
Read more here: http://bit.ly/2tRViJ9
npm ERR! Linux 4.4.0-81-generic
npm ERR! argv "/root/.nvm/versions/node/v6.12.0/bin/node" "/root/.nvm/versions/node/v6.12.0/bin/npm" "run" "build"
npm ERR! node v6.12.0
npm ERR! npm v3.10.10
npm ERR! code ELIFECYCLE
npm ERR! [email protected] build: `npm run build-css && react-scripts build`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] build script 'npm run build-css && react-scripts build'.
npm ERR! Make sure you have the latest version of node.js and npm installed.
npm ERR! If you do, this is most likely a problem with the student-loan-simulator package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR! npm run build-css && react-scripts build
npm ERR! You can get information on how to open an issue for this project with:
npm ERR! npm bugs student-loan-simulator
npm ERR! Or if that isn't available, you can get their info via:
npm ERR! npm owner ls student-loan-simulator
npm ERR! There is likely additional logging output above.
Gotta find a new way to use SVG, such a PAIN.
import IconEnvelope from '-!react-svg-loader!./envelope.svg';
Answers with tricks like !-
are likely to randomly break in future versions. Really, if you're trying to work around the tool so hard, you should just eject and customize the tool as you like it.
Backwards compatibility should never be the reason to prevent features from being deployed.
Auto updating is not my concern. It's that it's bad to make an existing use case impossible.
I'm reopening because I think I'm happy with using an explicit names export for this.
Something like
import { ReactComponent as MyButton } from './Button.svg';
@gaearon you mean:
import { ReactComponent as MyButton } from './Button.svg' // get React Component
import Button from './Button.svg' // get something else?
I didn't catch what is the benefit of a named export instead of using the default one?
I am interested because I took the approach of default export in SVGR and I do not have any issue about it.
Yes.
I didn't catch what is the benefit of a named export instead of using the default one?
Because the default one gives you the URL, just like with any other kind of image. If we used default export for this, we'd make existing use cases broken (people already depend on current behavior).
A named export is both explicit (so you can search for what it means), and is backwards compatible.
Does anyone want to send a PR for https://github.com/facebookincubator/create-react-app/issues/1388#issuecomment-346275504?
@gaearon I've been watching this issue for a while and am interested in doing this. I'm not entirely sure how the named export would be implemented though. If you can point me in the right direction I can start working on it.
I'm not really sure. Maybe @neoziro has ideas? Ideally I'd like this to be an option for svgr
webpack loader.
So in your example Button.svg
is just a plain SVG file and importing it with a named import causes webpack to use the svgr
loader instead of the regular SVG loader?
Yeah. Or, maybe we always use svgr
loader but it has a special mode in which it re-exports the result of a previous loader through default export.
I was thinking something along the same lines: have svgr
export both the raw SVG as the default export and the component as a named export. However, as you point out, the default isn't just the raw SVG it's actually the URL of the file as generated by another loader.
It looks like the svgr
template
option might be useful here: https://github.com/smooth-code/svgr#template
@neoziro any thoughts on this?
@gaearon I understand what do you want to achieve.
@iansu yes template
could help, we could inspire from lqip-loader (https://github.com/zouhir/lqip-loader/blob/master/index.js), it supports url-loader
and file-loader
out of the box.
That's helpful. So the URL from file-loader
would get passed as the input to the svgr
loader and we could access the original SVG file using this.resourcePath
inside the loader. The next step would be passing both of those into the template
function.
@neoziro that seems like kind of a big change to svgr
. Is that something you want? Alternatively we could create a new webpack loader that worked this way and used svgr
internally.
@iansu yes I think it is a good option to be able to use it with file-loader
or url-loader
. It will not be a breaking change and it will be a great addition!
Do you want to work on it?
@neoziro Sure, I can work on it. I think with these changes to svgr
the changes to CRA's webpack config would be pretty straightforward. Just add a loader entry for .svg
and use file-loader
then svgr
. The only issue I see is that this increases the size of SVG imports. @gaearon what do you think about this approach?
My thinking was that if we use ES6 exports then tree shaking should just ignore unused exports (either component, file URL, or both).
I did it https://github.com/smooth-code/svgr/pull/32:
When you use url-loader
+ svgr/webpack
or file-loader
+ svgr/webpack
:
import { ReactComponent }, url from './file.svg'
When you use only svgr/webpack
:
import ReactComponent from './file.svg'
Does it fit your needs? @gaearon @iansu
@neoziro I was just about to send you a PR. Your change looks very similar to mine so I think it should work. I can test it with CRA and let you know for sure.
@neoziro Looks like it does work. 👍
If you can publish a new version of svgr
I can commit the necessary create-react-app
changes.
@iansu thanks, I will do it.
@iansu just released https://github.com/smooth-code/svgr/releases/tag/v1.6.0
Please participate in a wider discussion about this approach: https://github.com/facebookincubator/create-react-app/issues/3722.
use:
xlink:href => xlinkHref
working...
problem is == > Syntax error: Namespace tags are not supported. ReactJSX is not XML.
This is now implemented in https://github.com/facebookincubator/create-react-app/pull/3718.
Support for this has been released in the first v2 alpha version. See https://github.com/facebookincubator/create-react-app/issues/3815 for more details!
This might not make it after all 😞 https://github.com/facebookincubator/create-react-app/issues/3856
I'm sorry to add noise, but what is considered the current stable best practice for inline SVGs? I got my SVGs from a designer (over 100 of them) and would not like to convert them to JSX. My react-scripts is at version 1.0.17 and I have no problem updating to a more recent stable version if it will make the difference.
@rivertam you can convert them statically using svgr, I think this is the simplest thing to do. This is what I do in all of my projects.
svgr -d src/components --icon src/svgs
@neoziro I don't love it, but it'll work for me. Thanks!
as a workaround you can do something like:
import React from 'react'; export default () => <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> <path d="M2.001 9.352c0 1.873.849 2.943 1.683 3.943.031"/> </svg>
then you can take in props and use those to adjust the fill
I've gone with this approach, keeping my svgs in one file and importing them where needed, for example:
import React from 'react';
const Logo = () => (
<svg viewBox="0 0 636 76">
<g
id="Page-1"
stroke="none"
stroke-width="1"
fill="none"
fill-rule="evenodd"
font-size="111"
font-family="Limelight"
letter-spacing="3.80571437"
font-weight="normal"
>
<text id="logo" fill="#313131">
<tspan x="-1.59871466" y="76">
LOGO
</tspan>
</text>
</g>
</svg>
);
export { Logo };
It's then imported as a complete standalone svg with full control.
This was released in 2.0 stable. Workarounds are no longer needed.
There is some bug or limitation you have to keep in mind when you have multiple svg's on one page. You need to check for unique id's in svg file path's and mask's as second, third svg will grab first match id from page. So you can find all svg's to be the same as first in the document.
Probably my answer will be helpful anybody
Add .svg
extension to extenstions list for url-loader
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/],
loader: require.resolve("url-loader"),
options: {
limit: 10000,
name: "static/media/[name].[hash:8].[ext]"
}
}
Import .svg
like that
import * as React from "react";
import { Icon } from "components";
import * as downArrow from "common/assets/down-arrow.svg";
const CollapseIcon = ({ condition }) => (
<Icon asset={downArrow} />
);
export default CollapseIcon;
Current props can use in src
attribute, example, for my code
const Icon = styled.img.attrs<IconProps>({
src: ({ asset }: IconProps) => asset,
alt: ({ alt }: IconProps) => alt
})`
vertical-align: middle;
`
@ravecat you can just use:
import { ReactComponent as WhateverNameYouWant } from 'common/assets/down-arrow.svg';
and you get a nice inlined SVG.
How about having svg components pure/memoized by default?
At the moment to achieve the same I would have to do something like this
import React, { PureComponent, memo } from 'react'
import { ReactComponent as _GraphIcon } from '../../assets/icons/icon-graph.svg'
const GraphIcon = memo(_GraphIcon)
wouldn't it be better if this was done by default?
@khaledosman Please create a new issue.
It seems that
export { ReactComponent as MyIcon } from './icon.svg'
produces undefined
when exported as
import { MyIcon } from 'icons/index.js'
@AlpacaGoesCrazy don't think you can do that, it's not a real import. It's using a Webpack plugin to transform the statement. You'll have to import { ReactComponent as BlahBlah } from './blah.blah'
and then export the result.
@philraj thank you, just did that and it works
import { ReactComponent as MyIcon } from './icon.svg'
export {
MyIcon
}
however it would be nice to use direct exports for svg
This has been fixed but hasn't been released yet: https://github.com/facebook/create-react-app/pull/5573
Most helpful comment
This video convinced me that the way we should be using SVG is inline. This table, showing the features and benefits of implementing SVG using the three available techniques, cinched it for me:
An SVG is different from all other assets (image, sound, video, etc) in that isn't a binary blob, and so can be more easily manipulated in our app. An SVG has more in common with a stateless react component than a JPEG.
You can still do this:
Then again, I suppose I haven't thought through the scenario of using an SVG asset in CSS…
😬