How to import an entire folder of SVG images (or how to load them dynamically) into a React Web App?
If using React, I strongly suspect you are also using Webpack. You can use require.context
instead of es6 import
and Webpack will resolve it for you when building.
require.context ( folder, recurse, pattern )
- folder - String - Path to folder to begin scanning for files.
- recurse - Boolean - Whether to recursively scan the folder.
- pattern - RegExp - Matching pattern describing which files to include.
The first line of each example ...
const reqSvgs = require.context ( './images', true, /\.svg$/ )
... creates a Require Context mapping all the *.svg
file paths in the images
folder to an import. This gives us a specialized Require Function named reqSvgs
with some attached properties.
One of the properties of reqSvgs
is a keys
method, which returns a list of all the valid filepaths.
const allSvgFilepaths = reqSvgs.keys ()
We can pass one of those filepaths into reqSvgs
to get an imported image.
const imagePath = allSvgFilePaths[0]
const image = reqSvgs ( imagePath )
This api is constraining and unintuitive for this use case, so I suggest converting the collection to a more common JavaScript data structure to make it easier to work with.
Every image will be imported during the conversion. Take care, as this could be a foot-gun. But it provides a reasonably simple mechanism for copying multiple files to the build folder which might never be explicitly referenced by the rest of your source code.
Here are 3 example conversions that might be useful.
Array
Create an array of the imported files.
const reqSvgs = require.context ( './images', true, /\.svg$/ )
const paths = reqSvgs.keys ()
const svgs = paths.map( path => reqSvgs ( path ) )
Array of Objects
Create an array of objects, with each object being { path, file }
for one image.
const reqSvgs = require.context ( './images', true, /\.svg$/ )
const svgs = reqSvgs
.keys ()
.map ( path => ({ path, file: reqSvgs ( path ) }) )
Plain Object
Create an object where each path is a key to its matching file.
const reqSvgs = require.context ('./images', true, /\.svg$/ )
const svgs = reqSvgs
.keys ()
.reduce ( ( images, path ) => {
images[path] = reqSvgs ( path )
return images
}, {} )
SurviveJS gives a more generalized example of require.context
here SurviveJS Webpack Dynamic Loading.
Stumbled onto this issue - I initially had the "Accepted answer", but i caused http request for each and every svg, which triggered a rate limit. So I ended up with a combination the accepted answer and what @karthik proposed - using a loader in the request.context
As of CRA 2.0 @svgr is included to import svg's as react components.
const reqSvgs = require.context('!@svgr/webpack!flag-icon-css/flags/4x3', true, /\.svg$/)
So here we combine an svg loader and require.context
const flagMap = reqSvgs.keys().reduce((images, path) => {
const key = path.substring(path.lastIndexOf('/') + 1, path.lastIndexOf('.'))
images[key] = reqSvgs(path).default
return images
}, {})
Then we map all these into a json object so we can use key lookup
To render svg's in jsx:
const Flag = flagMap['dk']
return (
<Flag />
)
And happy days, svgs included in bundle and no individual http requests
Instead of multiple SVG files you can use the single SVG sprite.
SVG sprite can be generated from a directory of SVG files using svg-sprite-generator:
svg-sprite-generate -d images -o images/sprite.svg
Then use it like this:
import React from 'react';
import { NavLink } from 'react-router-dom';
import sprite from './images/sprite.svg';
export default (props) => (
<NavLink className="hex" activeClassName="active" to={'/hex/' + props.itemName}>
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30">
<use xlinkHref={`${sprite}#${props.itemName}`} />
</svg>
</NavLink>
)