Importe imagens dinamicamente de um diretório usando webpack

99

Então, aqui está meu fluxo de trabalho atual para importar imagens e ícones no 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} />

Isso fica bagunçado rápido. Aqui está o que eu quero:

import * from './images'

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

Eu sinto que deve haver alguma maneira de importar dinamicamente todos os arquivos de um diretório específico como seu nome sem extensão e, em seguida, usar esses arquivos conforme necessário.

Alguém viu isso ser feito ou tem alguma opinião sobre a melhor maneira de fazer isso?


ATUALIZAR:

Usando a resposta selecionada, consegui fazer o seguinte:

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']} />
klinore
fonte
7
Eu só gosto de apontar que .mapespera um valor de retorno. No seu caso, seria usado um bom e velho em forEachvez disso.
Bram Vanroy
1
@BramVanroy ou apenas torná-lo um one-liner e retornar r.keys.().map(...)diretamente ...
LinusGeffarth

Respostas:

120

Eu sinto que deve haver alguma maneira de importar dinamicamente todos os arquivos de um diretório específico como seu nome sem extensão e, em seguida, usar esses arquivos conforme necessário.

Não em ES6. O ponto principal de importe exporté que as dependências podem ser determinadas estaticamente , ou seja, sem executar código.

Mas como você está usando o webpack, dê uma olhada em require.context. Você deve ser capaz de fazer o seguinte:

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

const images = importAll(require.context('./', false, /\.(png|jpe?g|svg)$/));
Felix Kling
fonte
Interessante ... Então, atualmente estou usando 'file-loader' na configuração do meu webpack para mover todos esses arquivos para um único local no diretório público de meus aplicativos. Isso não está acontecendo aqui. Como os carregadores funcionam com require.context?
klinore
1
"Isso não está acontecendo aqui" Quer dizer que os arquivos não aparecem na pasta de saída? Você ainda consegue o caminho para eles com o código acima? Não acho que seja necessário fazer nada de especial para apoiar isso ...
Felix Kling
2
Não sei por que, comprei que meu carregador não estava funcionando e eu estava obtendo o caminho original. O carregador está funcionando bem agora e o caminho correto está sendo fornecido! Impressionante. Obrigado por apresentar o require.context: D!
klinore de
1
O que usar se eu tiver o aplicativo create-react-app (cra)? em cra importAllnada retornou.
giorgim
2
Isso funciona para mim, mas como você escreveria a mesma coisa no TypeScript? Quais seriam os tipos corretos para isso?
Maximilian Lindsey
10

É fácil. Você pode usar require(um método estático, a importação é apenas para arquivos dinâmicos) dentro do render. Como o exemplo abaixo:

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

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

    return (
    <img src={graphImage}/>
    )
}
Robsonsjre
fonte
1
Acho que mais precisa ser feito para que isso funcione com o webpack.
Felix Kling
Você pode colar o arquivo de configuração do webpack aqui?
Robsonsjre
3
Isso é glorioso. Obrigado!
poweratom
1
Eu não recomendaria o uso de requisitos globais como este - consulte eslint.org/docs/rules/global-require
markyph
7

Eu tenho um diretório de bandeiras de países png nomeados como au.png, nl.png etc. Então eu tenho:

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

Eu li um arquivo como este:

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;
Tudor Morar
fonte
5

Uma abordagem funcional para resolver este problema:

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)$/)
);
Patrick Santos
fonte
Obrigado! Funciona perfeitamente!
Zakalwe
4

ATUALIZAÇÃO Parece que não entendi bem a pergunta. @Felix acertou, então verifique sua resposta. O código a seguir funcionará apenas em um ambiente Nodejs.

Adicione um index.jsarquivo na imagespasta

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;

Isso permitirá que você importe tudo de outro arquivo e o Wepback irá analisá-lo e carregar os arquivos necessários.

kbariotis
fonte