É possível importar módulos de todos os arquivos em um diretório, usando um curinga?

256

Com o ES6, posso importar várias exportações de um arquivo como este:

import {ThingA, ThingB, ThingC} from 'lib/things';

No entanto, gosto da organização de ter um módulo por arquivo. Acabo com importações como esta:

import ThingA from 'lib/things/ThingA';
import ThingB from 'lib/things/ThingB';
import ThingC from 'lib/things/ThingC';

Eu adoraria poder fazer isso:

import {ThingA, ThingB, ThingC} from 'lib/things/*';

ou algo semelhante, com a convenção entendida de que cada arquivo contém uma exportação padrão e que cada módulo tem o mesmo nome que seu arquivo.

Isso é possível?

Frambot
fonte
Isso é possível. Por favor, consulte a documentação do módulo para babel babeljs.io/docs/learn-es2015 ... guoted "import {sum, pi} de" lib / math ";". A resposta aceita não é mais válida. Atualize-o.
Eduard Jacko
6
@ kresli Acho que você não entende a pergunta. Nos documentos, lib/mathé um arquivo que contém várias exportações. Na minha pergunta, lib/math/é um diretório que contém vários arquivos, cada um contendo uma exportação.
Frambot 7/08/2015
2
OK eu vejo. Nesse caso, Bergi está correto. Desculpe
Eduard Jacko

Respostas:

231

Eu não acho que isso seja possível, mas após a resolução dos nomes dos módulos depende dos carregadores de módulos, pode haver uma implementação do carregador que suporte isso.

Até então, você poderia usar um "arquivo de módulo" intermediário lib/things/index.jsque continha apenas

export * from 'ThingA';
export * from 'ThingB';
export * from 'ThingC';

e isso permitiria que você fizesse

import {ThingA, ThingB, ThingC} from 'lib/things';
Bergi
fonte
6
Obrigado pela ajuda. Eu era capaz de começar este trabalho com index.jsolhar como: import ThingA from 'things/ThingA'; export {ThingA as ThingA}; import ThingB from 'things/ThingB'; export {ThingB as ThingB};. Outros encantamentos index.jsnão mudariam.
Frambot
2
Hum, export * fromdeve funcionar. Você já tentou …from './ThingA'ou export ThingA from …? Qual carregador de módulo você está usando?
Bergi 18/04/2015
7
Sim, sua resposta original funcionou se cada ThingA.js, ThingB.js, cada um exportasse exportações nomeadas. Spot on.
Frambot
1
Você precisa especificar o arquivo de índice ou pode especificar apenas a pasta e o index.js será carregado?
Zorgatone 11/11
1
@ Zorgatone: Isso depende do carregador de módulos que você está usando, mas sim, geralmente o caminho da pasta será suficiente.
Bergi 11/05/19
128

Apenas uma variação do tema já fornecida na resposta, mas e quanto a isso:

Em um Thing,

export default function ThingA () {}

In things/index.js,

export {default as ThingA} from './ThingA'
export {default as ThingB} from './ThingB'
export {default as ThingC} from './ThingC'

Então, para consumir todas as coisas em outro lugar,

import * as things from './things'
things.ThingA()

Ou para consumir apenas algumas coisas,

import {ThingA,ThingB} from './things'
Jed Richards
fonte
Quer dar uma olhada na resposta de @ wolfbiter? Não sei por que ele afirma que os parênteses não funcionam.
Bergi 04/08/2015
@ Bergi Sim, concordo, eu não acho que Wolfbiter é válido ES6. Talvez ele esteja usando uma versão antiga do Babel ou algum outro transpiler?
Jed Richards
Como isso está sendo transpilado? A importação de um diretório não resolve index.jspara mim. Estou usando SystemJs + Babel
jasonszhao
2
Você não pode simplesmente digitar export ThingA from './ThingA'em vez deexport {default as ThingA} from './ThingA'
Petr Peller
1
isso tira vantagem de três tremores? se eu importar {ThingA} de './things', o ThingB e o ThingC também serão adicionados ao pacote?
Giorgio
75

As respostas atuais sugerem uma solução alternativa, mas me incomodou por que isso não existe, então eu criei um babelplugin que faz isso.

Instale-o usando:

npm i --save-dev babel-plugin-wildcard

adicione-o ao seu .babelrccom:

{
    "plugins": ["wildcard"]
}

consulte o repositório para obter informações detalhadas sobre a instalação


Isso permite que você faça isso:

import * as Things from './lib/things';

// Do whatever you want with these :D
Things.ThingA;
Things.ThingB;
Things.ThingC;

novamente, o repositório contém mais informações sobre o que exatamente ele faz, mas evita a criação de index.jsarquivos e também acontece no tempo de compilação para evitar fazer readdirs em tempo de execução.

Também com uma versão mais recente, você pode fazer exatamente como no seu exemplo:

 import { ThingsA, ThingsB, ThingsC } from './lib/things/*';

funciona da mesma maneira que acima.

Downgoat
fonte
3
Aviso, estou tendo problemas graves com este plug-in. Os problemas provavelmente vêm do cache interno; você arranca os cabelos, quando o código fica perfeito, mas o script não funciona corretamente porque você adicionou o arquivo ./lib/things;e não está sendo escolhido. Os problemas que causa são ridículos. Acabei de testemunhar a situação, ao alterar o arquivo com o import *make babel para pegar o arquivo adicionado, mas alterá-lo de volta traz o problema de volta, como se reutilizasse o cache antes da alteração. Use com cuidado.
Łukasz Zaroda 23/09/19
@ ŁukaszZaroda babel tem um cache interno no ~/.babel.jsonqual causa esse comportamento estranho. Além disso, se você estiver usando como um observador ou um reloader quente você tem que salvar o novo arquivo por isso vai ser recompilados com a nova listagem de diretório
Downgoat
@Downgoat, então, como superar isso, exceto para excluir o cache de babel? E aliás. Não acho que seu comentário esteja correto. Eu tenho o cache do babel desativado e tive um problema enorme com esse plugin. Totalmente não recomendo
SOReader 20/10
1
Btw para qualquer um que tem mais problemas, adicione bpwc clear-cacheporque o cache Webpack e outros processos de compilação será ainda silenciosamente
Downgoat
Essa é uma ótima ideia, mas também não consegui fazê-la funcionar. Possivelmente um conflito com o meu código do tipo de fluxo, não tenho certeza, mas estava recebendo `ReferenceError: Foo não está definido ', não importa como estruturei as importações.
jlewkovich
13

Great gugly muglys! Isso foi mais difícil do que precisava ser.

Exportar um padrão simples

Esta é uma grande oportunidade de usar propagação ( ...em { ...Matters, ...Contacts }abaixo:

// imports/collections/Matters.js
export default {           // default export
  hello: 'World',
  something: 'important',
};
// imports/collections/Contacts.js
export default {           // default export
  hello: 'Moon',
  email: '[email protected]',
};
// imports/collections/index.js
import Matters from './Matters';      // import default export as var 'Matters'
import Contacts from './Contacts';

export default {  // default export
  ...Matters,     // spread Matters, overwriting previous properties
  ...Contacts,    // spread Contacts, overwriting previosu properties
};
// imports/test.js
import collections from './collections';  // import default export as 'collections'

console.log(collections);

Em seguida, para executar o código compilado babel na linha de comando (na raiz do projeto /):

$ npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/node 
(trimmed)

$ npx babel-node --presets @babel/preset-env imports/test.js 
{ hello: 'Moon',
  something: 'important',
  email: '[email protected]' }

Exportar um padrão semelhante a uma árvore

Se você preferir não substituir as propriedades, altere:

// imports/collections/index.js
import Matters from './Matters';     // import default as 'Matters'
import Contacts from './Contacts';

export default {   // export default
  Matters,
  Contacts,
};

E a saída será:

$ npx babel-node --presets @babel/preset-env imports/test.js
{ Matters: { hello: 'World', something: 'important' },
  Contacts: { hello: 'Moon', email: '[email protected]' } }

Exportar várias exportações nomeadas sem padrão

Se você é dedicado ao DRY , a sintaxe das importações também muda:

// imports/collections/index.js

// export default as named export 'Matters'
export { default as Matters } from './Matters';  
export { default as Contacts } from './Contacts'; 

Isso cria 2 exportações nomeadas sem exportação padrão. Então mude:

// imports/test.js
import { Matters, Contacts } from './collections';

console.log(Matters, Contacts);

E a saída:

$ npx babel-node --presets @babel/preset-env imports/test.js
{ hello: 'World', something: 'important' } { hello: 'Moon', email: '[email protected]' }

Importar todas as exportações nomeadas

// imports/collections/index.js

// export default as named export 'Matters'
export { default as Matters } from './Matters';
export { default as Contacts } from './Contacts';
// imports/test.js

// Import all named exports as 'collections'
import * as collections from './collections';

console.log(collections);  // interesting output
console.log(collections.Matters, collections.Contacts);

Observe a desestruturação import { Matters, Contacts } from './collections'; no exemplo anterior.

$ npx babel-node --presets @babel/preset-env imports/test.js
{ Matters: [Getter], Contacts: [Getter] }
{ hello: 'World', something: 'important' } { hello: 'Moon', email: '[email protected]' }

Na prática

Dados esses arquivos de origem:

/myLib/thingA.js
/myLib/thingB.js
/myLib/thingC.js

Criar um /myLib/index.jspara agrupar todos os arquivos anula o propósito de importação / exportação. Seria mais fácil tornar tudo global em primeiro lugar do que tornar tudo global via importação / exportação via "arquivos de wrapper" index.js.

Se você deseja um arquivo específico, import thingA from './myLib/thingA';em seus próprios projetos.

Criar um "arquivo wrapper" com exportações para o módulo só faz sentido se você estiver empacotando para npm ou em um projeto de várias equipes com vários anos.

Chegou tão longe? Veja os documentos para mais detalhes.

Além disso, sim, para o Stackoverflow finalmente suportando três `s como marcação de cerca de código.

Michael Cole
fonte
10

Você pode usar async import ():

import fs = require('fs');

e depois:

fs.readdir('./someDir', (err, files) => {
 files.forEach(file => {
  const module = import('./' + file).then(m =>
    m.callSomeMethod();
  );
  // or const module = await import('file')
  });
});
mr_squall
fonte
2
As importações dinâmicas são boas assim. Eles com certeza não existiam quando a pergunta foi feita. Obrigado pela resposta.
Frambot 7/03/19
6

Semelhante à pergunta aceita, mas permite escalar sem a necessidade de adicionar um novo módulo ao arquivo de índice cada vez que você cria um:

./modules/moduleA.js

export const example = 'example';
export const anotherExample = 'anotherExample';

./modules/index.js

// require all modules on the path and with the pattern defined
const req = require.context('./', true, /.js$/);

const modules = req.keys().map(req);

// export all modules
module.exports = modules;

./example.js

import { example, anotherExample } from './modules'
Nicolas
fonte
Isso não funciona para mim quando a tentativa de importação como um alias em./example.js
tsujp
o doens't não funciona para mim também (webpack 4.41, babel 7.7)
Edwin Joassart
3

Eu os usei algumas vezes (em particular para criar objetos maciços que dividem os dados em muitos arquivos (por exemplo, nós AST)). Para construí-los, criei um pequeno script (que acabei de adicionar ao npm para que todos os outros pode usá-lo).

Uso (atualmente você precisará usar o babel para usar o arquivo de exportação):

$ npm install -g folder-module
$ folder-module my-cool-module/

Gera um arquivo contendo:

export {default as foo} from "./module/foo.js"
export {default as default} from "./module/default.js"
export {default as bar} from "./module/bar.js"
...etc

Então você pode apenas consumir o arquivo:

import * as myCoolModule from "my-cool-module.js"
myCoolModule.foo()
Jamesernator
fonte
Não está funcionando corretamente no Windows, gera o caminho como um caminho do Windows ( \` instead of / ) also as an improvment you may want to allow two options like --filename` && --destpara permitir a personalização de onde o arquivo criado deve ser armazenado e com o nome. Também não funciona com nomes de arquivos contendo .(como user.model.js)
Yuri Scarbaci
2

Apenas uma outra abordagem para a resposta de @ Bergi

// lib/things/index.js
import ThingA from './ThingA';
import ThingB from './ThingB';
import ThingC from './ThingC';

export default {
 ThingA,
 ThingB,
 ThingC
}

Usos

import {ThingA, ThingB, ThingC} from './lib/things';
Ashok Vishwakarma
fonte
Isso não vai funcionar. Eu apenas tentei em um aplicativo de reação e ele retornou export '...' was not found in '.....
Hamid Mayeli 14/10/19
1

Você também pode usar o exigir:

const moduleHolder = []

function loadModules(path) {
  let stat = fs.lstatSync(path)
  if (stat.isDirectory()) {
    // we have a directory: do a tree walk
    const files = fs.readdirSync(path)
    let f,
      l = files.length
    for (var i = 0; i < l; i++) {
      f = pathModule.join(path, files[i])
      loadModules(f)
    }
  } else {
    // we have a file: load it
    var controller = require(path)
    moduleHolder.push(controller)
  }
}

Em seguida, use o moduleHolder com controladores carregados dinamicamente:

  loadModules(DIR) 
  for (const controller of moduleHolder) {
    controller(app, db)
  }
mr_squall
fonte
0

Não é exatamente isso que você pediu, mas, com esse método, eu posso iterar componentsListnos meus outros arquivos e usar funções como as componentsList.map(...)que eu acho bastante úteis!

import StepOne from './StepOne';
import StepTwo from './StepTwo';
import StepThree from './StepThree';
import StepFour from './StepFour';
import StepFive from './StepFive';
import StepSix from './StepSix';
import StepSeven from './StepSeven';
import StepEight from './StepEight';

const componentsList= () => [
  { component: StepOne(), key: 'step1' },
  { component: StepTwo(), key: 'step2' },
  { component: StepThree(), key: 'step3' },
  { component: StepFour(), key: 'step4' },
  { component: StepFive(), key: 'step5' },
  { component: StepSix(), key: 'step6' },
  { component: StepSeven(), key: 'step7' },
  { component: StepEight(), key: 'step8' }
];

export default componentsList;
FlyingZipper
fonte
0

Se você estiver usando o webpack. Isso importa arquivos automaticamente e exporta como API namespace da .

Portanto, não é necessário atualizar a cada adição de arquivo.

import camelCase from "lodash-es";
const requireModule = require.context("./", false, /\.js$/); // 
const api = {};

requireModule.keys().forEach(fileName => {
  if (fileName === "./index.js") return;
  const moduleName = camelCase(fileName.replace(/(\.\/|\.js)/g, ""));
  api[moduleName] = {
    ...requireModule(fileName).default
  };
});

export default api;

Para usuários do Typecript;

import { camelCase } from "lodash-es"
const requireModule = require.context("./folderName", false, /\.ts$/)

interface LooseObject {
  [key: string]: any
}

const api: LooseObject = {}

requireModule.keys().forEach(fileName => {
  if (fileName === "./index.ts") return
  const moduleName = camelCase(fileName.replace(/(\.\/|\.ts)/g, ""))
  api[moduleName] = {
    ...requireModule(fileName).default,
  }
})

export default api
atilkan
fonte
0

Consegui tirar da abordagem do usuário atilkan e modificá-la um pouco:

Para usuários do Typecript;

require.context('@/folder/with/modules', false, /\.ts$/).keys().forEach((fileName => {
    import('@/folder/with/modules' + fileName).then((mod) => {
            (window as any)[fileName] = mod[fileName];
            const module = new (window as any)[fileName]();

            // use module
});

}));
Justin Icenhour
fonte
-9

se você não exportar o padrão em A, B, C, mas apenas exportar {}, é possível fazer isso

// things/A.js
export function A() {}

// things/B.js
export function B() {}

// things/C.js
export function C() {}

// foo.js
import * as Foo from ./thing
Foo.A()
Foo.B()
Foo.C()
hjl
fonte
1
Esse javascript não é válido (não há aspas ./thing) e, mesmo que houvesse, não funcionaria. (Eu tentei, e não funcionou.)
John