Est-il possible d'importer des modules à partir de tous les fichiers d'un répertoire à l'aide d'un caractère générique?

256

Avec ES6, je peux importer plusieurs exportations à partir d'un fichier comme celui-ci:

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

Cependant, j'aime l'organisation d'avoir un module par fichier. Je me retrouve avec des importations comme celle-ci:

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

J'adorerais pouvoir faire ça:

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

ou quelque chose de similaire, avec la convention bien connue que chaque fichier contient une exportation par défaut et que chaque module est nommé de la même manière que son fichier.

Est-ce possible?

Frambot
la source
C'est possible. Veuillez consulter la documentation du module pour babel babeljs.io/docs/learn-es2015 ... guoté "import {sum, pi} de" lib / math ";". La réponse acceptée n'est plus valable. Veuillez le mettre à jour.
Eduard Jacko
6
@kresli Je ne pense pas que vous compreniez la question. Dans les documents, se lib/mathtrouve un fichier contenant plusieurs exportations. Dans ma question, lib/math/est un répertoire contenant plusieurs fichiers, chacun contenant une exportation.
Frambot
2
OK je vois. Dans ce cas, Bergi a raison. Désolé
Eduard Jacko

Réponses:

231

Je ne pense pas que cela soit possible, mais afaik la résolution des noms de modules dépend des chargeurs de modules, il pourrait donc y avoir une implémentation de chargeur qui prend en charge cela.

Jusque-là, vous pouvez utiliser un "fichier module" intermédiaire lib/things/index.jsqui contient juste

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

et cela vous permettrait de faire

import {ThingA, ThingB, ThingC} from 'lib/things';
Bergi
la source
6
Merci pour l'aide. Je suis en mesure d'obtenir ce travail avec la index.jsrecherche comme: import ThingA from 'things/ThingA'; export {ThingA as ThingA}; import ThingB from 'things/ThingB'; export {ThingB as ThingB};. D'autres incantations index.jsne bougeraient pas.
Frambot
2
Hm, ça export * fromdevrait marcher. Avez-vous essayé …from './ThingA'ou export ThingA from …? Quel chargeur de module utilisez-vous?
Bergi
7
Oui, votre réponse d'origine fonctionnait si chaque ThingA.js, ThingB.js, chacun exportait des exportations nommées. Repérez.
Frambot du
1
Devez-vous spécifier le fichier d'index ou pouvez-vous spécifier uniquement le dossier et index.js sera chargé à la place?
Zorgatone
1
@Zorgatone: Cela dépend du chargeur de module que vous utilisez, mais oui, généralement, le chemin du dossier sera suffisant.
Bergi
128

Juste une variation sur le thème déjà fourni dans la réponse, mais qu'en est-il:

Dans un Thing,

export default function ThingA () {}

Dans things/index.js,

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

Ensuite, pour consommer toutes les choses ailleurs,

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

Ou pour consommer juste certaines choses,

import {ThingA,ThingB} from './things'
Jed Richards
la source
Vous voulez voir la réponse de @ wolfbiter? Je ne sais pas pourquoi il prétend que les parenthèses ne fonctionnent pas.
Bergi
@Bergi Ouais d'accord, je ne pense pas que wolfbiter soit valide ES6. Peut-être qu'il utilise une ancienne version de Babel, ou un autre transpilateur?
Jed Richards
Comment cela se traduit-il? L'importation d'un répertoire ne se résout pas index.jspour moi. J'utilise SystemJs + Babel
jasonszhao
2
Ne pouvez-vous pas simplement taper export ThingA from './ThingA'au lieu deexport {default as ThingA} from './ThingA'
Petr Peller
1
est-ce que cela profite de trois secousses? si j'importe {ThingA} de './things', ThingB et ThingC seront-ils également ajoutés au bundle?
Giorgio
75

Les réponses actuelles suggèrent une solution de contournement, mais cela me dérange pourquoi cela n'existe pas, j'ai donc créé un babelplugin qui fait cela.

Installez-le en utilisant:

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

puis ajoutez-le à votre .babelrcavec:

{
    "plugins": ["wildcard"]
}

voir le repo pour des informations d'installation détaillées


Cela vous permet de faire ceci:

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

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

encore une fois, le dépôt contient des informations supplémentaires sur ce qu'il fait exactement, mais le faire de cette façon évite de créer des index.jsfichiers et se produit également au moment de la compilation pour éviter de faire des readdirs au moment de l'exécution.

De plus, avec une version plus récente, vous pouvez faire exactement comme votre exemple:

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

fonctionne de la même manière que ci-dessus.

Downgoat
la source
3
Attention, j'ai de graves problèmes avec ce plugin. Les problèmes viennent probablement de sa mise en cache interne, vous vous arracherez les cheveux, lorsque votre code sera parfait, mais votre script ne fonctionnera pas correctement car vous avez ajouté un fichier ./lib/things;et il n'est pas récupéré. Les problèmes qu'elle provoque sont ridicules. Je viens d'être témoin d'une situation, lorsque le changement de fichier avec import *fait babel pour récupérer le fichier ajouté, mais le changer en arrière, ramène le problème, comme s'il réutilise le cache d'avant le changement. Utiliser avec précaution.
Łukasz Zaroda
@ ŁukaszZaroda babel possède un cache interne à l'origine de ~/.babel.jsonce comportement étrange. De plus, si vous utilisez comme un observateur ou un rechargeur à chaud, vous devez enregistrer le nouveau fichier afin qu'il soit recompilé avec la nouvelle liste de répertoires
Downgoat
@Downgoat alors comment surmonter cela sauf pour supprimer le cache de babel? Et btw. Je ne pense pas que votre commentaire soit correct. J'ai la mise en cache de babel désactivée et j'ai eu d'énormes problèmes avec ce plugin.
Je
1
Btw à toute personne ayant d'autres problèmes, ajoutez bpwc clear-cachecar le webpack et les autres processus de construction seront toujours mis en cache silencieusement
Downgoat
C'est une excellente idée mais je n'ai pas réussi non plus à la faire fonctionner. Peut-être un conflit avec mon code de type flux, je ne suis pas sûr, mais je recevais `ReferenceError: Foo n'est pas défini ', peu importe comment j'ai structuré les importations.
jlewkovich
13

Grands gugly muglys! C'était plus difficile que nécessaire.

Exporter un plat par défaut

Ceci est une excellente occasion d'utiliser la propagation ( ...en - { ...Matters, ...Contacts }dessous:

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

Ensuite, pour exécuter le code compilé babel à partir de la ligne de commande (à partir de la racine du projet /):

$ 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]' }

Exporter une arborescence par défaut

Si vous préférez ne pas écraser les propriétés, modifiez:

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

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

Et la sortie sera:

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

Exporter plusieurs exportations nommées sans défaut

Si vous êtes dédié à DRY , la syntaxe des importations change également:

// imports/collections/index.js

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

Cela crée 2 exportations nommées sans exportation par défaut. Puis changez:

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

console.log(Matters, Contacts);

Et la sortie:

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

Importer toutes les exportations nommées

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

Remarquez la déstructuration import { Matters, Contacts } from './collections'; dans l'exemple précédent.

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

En pratique

Compte tenu de ces fichiers source:

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

Créer un /myLib/index.jspour regrouper tous les fichiers va à l'encontre du but de l'importation / exportation. Il serait plus facile de tout rendre global en premier lieu, que de tout rendre global via l'importation / exportation via les "fichiers wrapper" index.js.

Si vous voulez un fichier particulier, import thingA from './myLib/thingA'; dans vos propres projets.

La création d'un "fichier wrapper" avec des exportations pour le module n'a de sens que si vous empaquetez pour npm ou sur un projet pluriannuel pluriannuel.

Tu es arrivé jusqu'ici? Voir les documents pour plus de détails.

Aussi, oui pour Stackoverflow supportant finalement trois `comme code de clôture.

Michael Cole
la source
10

Vous pouvez utiliser async import ():

import fs = require('fs');

puis:

fs.readdir('./someDir', (err, files) => {
 files.forEach(file => {
  const module = import('./' + file).then(m =>
    m.callSomeMethod();
  );
  // or const module = await import('file')
  });
});
mr_squall
la source
2
Les importations dynamiques sont agréables comme ça. Ils n'existaient certainement pas lorsque la question a été posée. Merci d'avoir répondu.
Frambot
6

Similaire à la question acceptée, mais elle vous permet de vous adapter sans avoir à ajouter un nouveau module au fichier d'index chaque fois que vous en créez un:

./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
la source
Cela ne fonctionne pas pour moi lorsque j'essaie d'importer en tant ./example.js
qu'alias
ne fonctionne pas pour moi non plus (webpack 4.41, babel 7.7)
Edwin Joassart
3

Je les ai utilisées plusieurs fois (en particulier pour construire des objets massifs en divisant les données sur de nombreux fichiers (par exemple les nœuds AST)), afin de les construire, j'ai fait un petit script (que je viens d'ajouter à npm pour tout le monde) peut l'utiliser).

Utilisation (actuellement, vous devrez utiliser babel pour utiliser le fichier d'exportation):

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

Génère un fichier contenant:

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

Ensuite, vous pouvez simplement consommer le fichier:

import * as myCoolModule from "my-cool-module.js"
myCoolModule.foo()
Jamesernator
la source
Ne fonctionne pas correctement dans Windows, génère un chemin comme chemin Windows ( \` instead of / ) also as an improvment you may want to allow two options like --filename` && --destpour permettre de personnaliser où le fichier créé doit être stocké et sous quel nom. Ne fonctionne pas non plus avec les noms de fichiers contenant .(comme user.model.js)
Yuri Scarbaci
2

Juste une autre approche de la réponse de @ Bergi

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

export default {
 ThingA,
 ThingB,
 ThingC
}

Les usages

import {ThingA, ThingB, ThingC} from './lib/things';
Ashok Vishwakarma
la source
Ça ne marchera pas. Je viens de l'essayer dans une application React et elle est revenue export '...' was not found in '.....
Hamid Mayeli
1

Vous pouvez également utiliser require:

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

Utilisez ensuite votre moduleHolder avec des contrôleurs chargés dynamiquement:

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

Ce n'est pas exactement ce que vous avez demandé mais, avec cette méthode, je peux parcourir componentsListmes autres fichiers et utiliser des fonctions comme celles componentsList.map(...)que je trouve assez utiles!

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
la source
0

Si vous utilisez webpack. Cela importe automatiquement les fichiers et les exporte en tant qu'espace de noms api .

Donc, pas besoin de mettre à jour chaque ajout de fichier.

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;

Pour les utilisateurs de Typographie;

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
la source
0

J'ai pu reprendre l'approche de l'utilisateur atilkan et la modifier un peu:

Pour les utilisateurs de Typographie;

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
la source
-9

si vous n'exportez pas par défaut en A, B, C mais exportez simplement {} alors il est possible de le faire

// 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
la source
1
Ce n'est pas du javascript valide (il n'y a pas de guillemets ./thing) et même s'il y en avait, cela ne fonctionnerait pas. (Je l'ai essayé, et cela n'a pas fonctionné.)
John