È possibile importare moduli da tutti i file in una directory, usando un carattere jolly?


256

Con ES6, posso importare diverse esportazioni da un file come questo:

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

Tuttavia, mi piace l'organizzazione di avere un modulo per file. Finisco con importazioni come questa:

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

Mi piacerebbe poter fare questo:

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

o qualcosa di simile, con la convenzione intesa che ogni file contiene un'esportazione predefinita e che ogni modulo è chiamato come il suo file.

È possibile?


Questo è possibile. Consultare la documentazione del modulo per babel babeljs.io/docs/learn-es2015 ... guoted "import {sum, pi} da" lib / math ";". La risposta accettata non è più valida. Per favore aggiornalo.
Eduard Jacko,

6
@kresli Non credo che tu capisca la domanda. Nei documenti, lib/mathè un file contenente più esportazioni. Nella mia domanda, lib/math/è una directory contenente diversi file, ognuno contenente un'esportazione.
Frambot,

2
va bene, ho capito. In quel caso Bergi ha ragione. Siamo spiacenti
Eduard Jacko il

Risposte:


231

Non credo sia possibile, ma la risoluzione dei nomi dei moduli dipende dai caricatori di moduli, quindi potrebbe esserci un'implementazione del caricatore che supporta questo.

Fino ad allora, è possibile utilizzare un "file modulo" intermedio lib/things/index.jsche contiene solo

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

e ti permetterebbe di farlo

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

6
Grazie per l'aiuto. Sono stato in grado di ottenere questo lavoro con index.jsguardando come: import ThingA from 'things/ThingA'; export {ThingA as ThingA}; import ThingB from 'things/ThingB'; export {ThingB as ThingB};. Altri incantesimi index.jsnon si muoverebbero.
Frambot,

2
Hm, export * fromdovrebbe funzionare. Hai provato …from './ThingA'o export ThingA from …? Quale modulo loader stai usando?
Bergi,

7
Sì, la tua risposta originale ha funzionato se ogni ThingA.js, ThingB.js, ciascuno ha esportato esportazioni denominate. Spot on.
Frambot,

1
Devi specificare il file indice o puoi specificare solo la cartella e verrà invece caricato index.js?
Zorgatone,

1
@Zorgatone: dipende dal caricatore del modulo che stai utilizzando, ma di solito il percorso della cartella sarà sufficiente.
Bergi,

128

Solo una variazione sul tema già fornito nella risposta, ma che ne dici di questo:

In un 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'

Quindi per consumare tutte le cose altrove,

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

O per consumare solo alcune cose,

import {ThingA,ThingB} from './things'

Vuoi dare un'occhiata alla risposta di @ wolfbiter? Non sono sicuro del motivo per cui afferma che la parentesi non funziona.
Bergi,

@Bergi Sì, d'accordo, non credo che il wolfbiter sia valido ES6. Forse sta usando una vecchia versione di Babel o qualche altro transpiler?
Jed Richards,

Come si traspone questo? L'importazione di una directory non risolve index.jsper me. Sto usando SystemJs + Babel
jasonszhao il

2
Non puoi semplicemente digitare export ThingA from './ThingA'invece diexport {default as ThingA} from './ThingA'
Petr Peller

1
questo sfrutta tre agitazioni? se imposto {ThingA} da './things' verranno aggiunti al bundle anche ThingB e ThingC?
Giorgio,

75

Le risposte attuali suggeriscono una soluzione alternativa, ma mi ha infastidito perché questo non esiste, quindi ho creato un babelplug-in che lo fa.

Installalo usando:

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

quindi aggiungilo al tuo .babelrccon:

{
    "plugins": ["wildcard"]
}

consultare il repository per informazioni dettagliate sull'installazione


Questo ti permette di fare questo:

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

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

ancora una volta, il repository contiene ulteriori informazioni su ciò che fa esattamente, ma farlo in questo modo evita la creazione di index.jsfile e si verifica anche in fase di compilazione per evitare di fare readdirs in fase di esecuzione.

Inoltre con una versione più recente puoi fare esattamente come il tuo esempio:

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

funziona come sopra.


3
Attenzione, sto riscontrando gravi problemi con questo plugin. I problemi probabilmente derivano dalla sua cache interna, ti toglierai i capelli, quando il tuo codice sarà perfetto, ma lo script non funzionerà correttamente perché hai aggiunto il file ./lib/things;e non viene raccolto. I problemi che causa sono ridicoli. Ho appena assistito a una situazione, quando cambiando file con import *rende babel per raccogliere il file aggiunto, ma cambiandolo indietro, si risolve il problema, come se riutilizzasse la cache prima della modifica. Usare con cautela.
Łukasz Zaroda,

@ ŁukaszZaroda babel ha una cache interna in ~/.babel.jsoncui provoca questo strano comportamento. Inoltre, se si utilizza come un watcher o un hot ricaricatore, è necessario salvare il nuovo file in modo che venga ricompilato con il nuovo elenco di directory
Downgoat,

@Downgoat quindi come superare questo, tranne per eliminare la cache di Babel? E a proposito. Non penso che il tuo commento sia corretto. Ho disabilitato la memorizzazione nella cache di Babel e ho avuto problemi enormi con quel plugin. Totalmente non lo consiglio
SOReader

1
A proposito di chiunque abbia ulteriori problemi, aggiungi bpwc clear-cacheperché il webpack e altri processi di compilazione verranno comunque memorizzati nella cache silenziosa
Downgoat

Questa è un'ottima idea ma non sono riuscito a farlo funzionare neanche. Probabilmente un conflitto con il mio codice flowtyped, non sono sicuro, ma stavo ottenendo "ReferenceError: Foo non è definito", non importa come ho strutturato le importazioni.
jlewkovich,

13

Fantastici bocconcini! Questo è stato più difficile di quanto fosse necessario.

Esporta un valore predefinito piatto

Questa è una grande opportunità per usare spread ( ...in { ...Matters, ...Contacts }seguito:

// imports/collections/Matters.js
export default {           // default export
  hello: 'World',
  something: 'important',
};
// imports/collections/Contacts.js
export default {           // default export
  hello: 'Moon',
  email: 'hello@example.com',
};
// 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);

Quindi, per eseguire il codice compilato babel dalla riga di comando (dalla radice del progetto /):

$ 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: 'hello@example.com' }

Esporta un predefinito ad albero

Se preferisci non sovrascrivere le proprietà, modifica:

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

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

E l'output sarà:

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

Esporta più esportazioni con nome senza impostazione predefinita

Se sei dedicato a DRY , cambia anche la sintassi sulle importazioni:

// imports/collections/index.js

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

Ciò crea 2 esportazioni denominate senza esportazione predefinita. Quindi cambia:

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

console.log(Matters, Contacts);

E l'output:

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

Importa tutte le esportazioni denominate

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

Si noti la destrutturazione import { Matters, Contacts } from './collections'; nell'esempio precedente.

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

In pratica

Dati questi file sorgente:

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

La creazione di un /myLib/index.jsraggruppamento di tutti i file vanifica lo scopo dell'importazione / esportazione. Sarebbe più semplice rendere tutto globale in primo luogo, piuttosto che rendere tutto globale tramite import / export tramite "file wrapper" index.js.

Se vuoi un file particolare, import thingA from './myLib/thingA';nei tuoi progetti.

La creazione di un "file wrapper" con esportazioni per il modulo ha senso solo se stai confezionando per npm o su un progetto pluriannuale pluriannuale.

Sei arrivato così lontano? Vedi i documenti per maggiori dettagli.

Inoltre, yay per Stackoverflow supporta infine tre `s come markup di code fence.


10

Puoi usare async import ():

import fs = require('fs');

e poi:

fs.readdir('./someDir', (err, files) => {
 files.forEach(file => {
  const module = import('./' + file).then(m =>
    m.callSomeMethod();
  );
  // or const module = await import('file')
  });
});

2
Le importazioni dinamiche sono belle così. Di sicuro non esistevano quando è stata posta la domanda. Grazie per la risposta.
Frambot

6

Simile alla domanda accettata, ma consente di ridimensionare senza la necessità di aggiungere un nuovo modulo al file indice ogni volta che ne crei uno:

./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'

Questo non funziona per me quando tento di importare come alias in./example.js
tsujp

non funziona neanche per me (webpack 4.41, babel 7.7)
Edwin Joassart

3

Li ho usati alcune volte (in particolare per costruire oggetti di grandi dimensioni che dividono i dati su molti file (ad esempio nodi AST)), al fine di costruirli ho creato un piccolo script (che ho appena aggiunto a npm in modo che tutti gli altri può usarlo).

Utilizzo (al momento dovrai usare babel per usare il file di esportazione):

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

Genera un file contenente:

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

Quindi puoi semplicemente consumare il file:

import * as myCoolModule from "my-cool-module.js"
myCoolModule.foo()

Non funziona correttamente in Windows, genera il percorso come percorso di Windows ( \` instead of / ) also as an improvment you may want to allow two options like --filename` && --destper consentire la personalizzazione di dove il file creato deve essere archiviato e con il cui nome. Inoltre non funziona con nomi di file contenenti .(come user.model.js)
Yuri Scarbaci

2

Solo un altro approccio alla risposta di @ Bergi

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

export default {
 ThingA,
 ThingB,
 ThingC
}

usi

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

Non funzionerà L'ho appena provato in un'app di reazione ed è tornato export '...' was not found in '.....
Hamid Mayeli,

1

Puoi usare anche richiedi:

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)
  }
}

Quindi utilizzare il moduloHolder con controller caricati dinamicamente:

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

0

Questo non è esattamente quello che mi hai chiesto, ma con questo metodo posso scorrere i componentsListmiei altri file e utilizzare funzioni come quelle componentsList.map(...)che trovo piuttosto utili!

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;

0

Se stai usando il webpack. Ciò importa i file automaticamente ed esporta come spazio dei nomi api .

Quindi non è necessario aggiornare ogni aggiunta di file.

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;

Per gli utenti Typescript;

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

0

Sono stato in grado di prendere l'approccio dell'utente atilkan e modificarlo un po ':

Per gli utenti Typescript;

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

}));

-9

se non esporti le impostazioni predefinite in A, B, C ma esporti solo {}, è possibile farlo

// 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()

1
Questo non è javascript valido (non ci sono citazioni in giro ./thing) e anche se ci fossero, non funzionerebbe. (L'ho provato e non ha funzionato.)
John,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.