Dynamically import images from a directory using webpack
Asked Answered
M

6

166

So here's my current workflow for importing images and icons in webpack via ES6:

import cat from './images/cat1.jpg'
import cat2 from './images/cat2.svg'
import doggy from './images/doggy.png'
import turtle from './images/turtle.png'

<img src={doggy} />

This gets messy quick. Here's what I want:

import * from './images'

<img src={doggy} />
<img src={turtle} />

I feel like there must be some way to dynamically import all files from a specific directory as their name sans extension, and then use those files as needed.

Anyone seen this done, or have any thoughts on the best way to go about it?


UPDATE:

Using the selected answer, I was able to do this:

function importAll(r) {
  let images = {};
  r.keys().map((item, index) => { images[item.replace('./', '')] = r(item); });
  return images;
}

const images = importAll(require.context('./images', false, /\.(png|jpe?g|svg)$/));

<img src={images['doggy.png']} />
Motif answered 8/2, 2017 at 16:17 Comment(2)
I just like to point out that .map kind of expects a return value. In your case, one would use a good ol' forEach instead.Desorb
@BramVanroy or just make it a one-liner and return r.keys.().map(...) directly...Claresta
F
172

I feel like there must be some way to dynamically import all files from a specific directory as their name sans extension, and then use those files as needed.

Not in ES6. The whole point of import and export is that dependencies can be determined statically, i.e. without executing code.

But since you are using webpack, have a look at require.context . You should be able to do the following:

function importAll(r) {
  return r.keys().map(r);
}

const images = importAll(require.context('./', false, /\.(png|jpe?g|svg)$/));
Folse answered 8/2, 2017 at 16:45 Comment(8)
Interesting... So, I am currently using 'file-loader' in my webpack config to move all those files to a single location in my apps public dir. That isn't happening here. How do loaders work with require.context?Motif
"That isn't happening here" You mean the files don't appear in the output folder? Do you still get the path to them with the above code though? I don't think there needs to be done anything special to support this...Folse
Not sure why, buy my loader wasn't being run and I was getting the original path. The loader is working fine now and the correct path is being provided! Awesome. Thanks for the introducing into require.context :D!Motif
What to use if I have create-react-app(cra)? incra importAll returned nothing.Gosse
This works for me, but how would you write the same thing in TypeScript? What would be the correct types for it?Lipkin
I was able to get this to work in a typescript environment by just marking the require.context() call as // @ts-ignore . It worked fine in my Create-react-app environment, and also with webpack. However, I observe that this is not really runtime importing--rather, at runtime, it will allow you to pull in any files that matched the specification in your source directories. It will not just pick up any matching file in the desired directory at runtime!Hypercatalectic
If anybody is wondering how to use the returned array, then every item in the return array would have an object with default as a key, which can be directly used as <img src={item.default} />Muoimuon
@MaximilianLindsey You can look at the return signature of require.context() which I found to be __WebpackModuleApi.RequireContext, so I took that and passed it as the arg type in importAll(r: __WebpackModuleApi.RequireContext) { ... } and TS stopped complaining.Sullage
S
15

It's easy. You can use require (a static method, import is just for dynamic files) inside the render. Like the example below:

render() {
    const {
      someProp,
    } = this.props

    const graphImage = require('./graph-' + anyVariable + '.png')
    const tableImage = require('./table-' + anyVariable2 + '.png')

    return (
    <img src={graphImage}/>
    )
}
Situs answered 8/2, 2017 at 16:22 Comment(3)
I think more needs to be done to make this work with webpack.Folse
Can you paste your webpack config file here?Situs
I would not recommend using global requires like this - see eslint.org/docs/rules/global-requireMunch
B
13

A functional approach to solve this problem:

const importAll = require =>
  require.keys().reduce((acc, next) => {
    acc[next.replace("./", "")] = require(next);
    return acc;
  }, {});

const images = importAll(
  require.context("./image", false, /\.(png|jpe?g|svg)$/)
);
Balsamic answered 27/10, 2018 at 19:37 Comment(0)
C
12

I have directory of png country flags named like au.png, nl.png etc. So I have:

-svg-country-flags
 --png100px
   ---au.png
   ---au.png
 --index.js
 --CountryFlagByCode.js

index.js

const context = require.context('./png100px', true, /.png$/);

const obj = {};
context.keys().forEach((key) => {
  const countryCode = key.split('./').pop() // remove the first 2 characters
    .substring(0, key.length - 6); // remove the file extension
  obj[countryCode] = context(key);
});

export default obj;

I read a file like this:

CountryFlagByCode.js

import React from 'react';
import countryFlags from './index';

const CountryFlagByCode = (countryCode) => {
    return (
        <div>
          <img src={countryFlags[countryCode.toLowerCase()]} alt="country_flag" />
        </div>
      );
    };

export default CountryFlagByCode;
Cocytus answered 15/1, 2018 at 14:54 Comment(1)
On Nextjs 13 to avoid the warning - "Only plain objects can be passed to Client Components from Server Components. Module objects are not supported.", you need to use context(key).default instead of context(key).Scandent
I
7

Here is a functional component I made in Reactjs to simply show all the images inside the media folder in my project (same level as component) using the webpack docs and some of the answers here.

import React from 'react';

const cache = {};

function importAll(r) {
    r.keys().forEach((key) => (cache[key] = r(key)));
}
// Note from the docs -> Warning: The arguments passed to require.context must be literals!
importAll(require.context("./media", false, /\.(png|jpe?g|svg)$/));

const images = Object.entries(cache).map(module => module[1].default);


const MediaPage = () => {
    return (
        <>
            <p>Media Page..</p>

            {images.map(image => (
                <img style={{width: 100}} src={image} />
            ))}
        </>
    );
}

export default MediaPage;

rendered images from media directory

With the way that I loaded the images the filenames are lost when mapping from the cache object but if you need them you can just use the cache object directly instead as the keys are the filename.

// example with styles just for clarity
return (
    <>
        <p>Media Page..</p>

        {Object.entries(cache).map(module => {
            const image = module[1].default;
            const name = module[0].replace("./","");
            return (
                <div style={{float: 'left', padding: 10, margin: 10, border: '2px solid white' }}>
                    <img style={{width: 100, margin: 'auto', display: 'block'}} src={image} />
                    <p>{name}</p>
                </div>
            )
        })}
    </>
);

images with filenames

Indreetloire answered 29/4, 2021 at 23:56 Comment(3)
require is not defined showing this errorCutlass
for me also not workingSpatterdash
src={//localhost:3000/images/${name}} worked for meSpatterdash
G
6

UPDATE It seems like I didnt quite understand the question. @Felix got it right so check his answer. The following code will work in a Nodejs environment only.

Add an index.js file in the images folder

const testFolder = './';
const fs = require('fs');
const path = require('path')

const allowedExts = [
  '.png' // add any extensions you need
]

const modules = {};

const files = fs.readdirSync(testFolder);

if (files && files.length) {
  files
    .filter(file => allowedExts.indexOf(path.extname(file)) > -1)
    .forEach(file => exports[path.basename(file, path.extname(file))] = require(`./${file}`));
}

module.exports = modules;

This will allow you to import everything from another file and Wepback will parse it and load the required files.

Grose answered 8/2, 2017 at 16:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.