È 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.