Czy można importować moduły ze wszystkich plików w katalogu, używając symbolu wieloznacznego?

256

Za pomocą ES6 mogę zaimportować kilka eksportów z pliku takiego jak ten:

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

Jednak podoba mi się organizacja posiadania jednego modułu na plik. Skończyło się na takich importach:

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

Chciałbym móc to zrobić:

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

lub coś podobnego, przy założeniu, że każdy plik zawiera jeden domyślny eksport, a każdy moduł ma taką samą nazwę jak jego plik.

czy to możliwe?

Frambot
źródło
To jest możliwe. Proszę zapoznać się z dokumentacją modułu dla babel babeljs.io/docs/learn-es2015 ... oznaczone „import {sum, pi} z„ lib / math ”;”. Przyjęta odpowiedź nie jest już ważna. Zaktualizuj to.
Eduard Jacko,
6
@kresli Nie sądzę, że rozumiesz pytanie. W dokumentach lib/mathznajduje się plik zawierający wiele eksportów. W moim pytaniu lib/math/jest katalog zawierający kilka plików, z których każdy zawiera jeden eksport.
Frambot,
2
dobra, widzę. W takim przypadku Bergi ma rację. Przepraszamy
Eduard Jacko,

Odpowiedzi:

230

Nie sądzę, że jest to możliwe, ale afaik rozdzielczość nazw modułów zależy od modułów ładujących, więc może istnieć implementacja modułu ładującego, która to obsługuje.

Do tego czasu można użyć pośredniego „pliku modułu”, lib/things/index.jsktóry zawiera tylko

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

i pozwoliłoby ci to zrobić

import {ThingA, ThingB, ThingC} from 'lib/things';
Bergi
źródło
6
Dzięki za pomoc. Udało mi się uzyskać tej pracy z index.jswyglądające jak: import ThingA from 'things/ThingA'; export {ThingA as ThingA}; import ThingB from 'things/ThingB'; export {ThingB as ThingB};. Inne zaklęcia index.jsnie drgnęły.
Frambot,
2
Hm, export * frompowinno działać. Próbowałeś …from './ThingA'lub export ThingA from …? Jakiego modułu ładującego używasz?
Bergi,
7
Tak, oryginalna odpowiedź działała, jeśli każdy ThingA.js, ThingB.js, każdy eksportował nazwany eksport. Spot on.
Frambot,
1
Czy musisz podać plik indeksu, czy możesz podać tylko folder, a zamiast tego zostanie załadowany plik index.js?
Zorgatone
1
@Zorgatone: To zależy od modułu ładującego, którego używasz, ale tak, zwykle ścieżka do folderu będzie wystarczająca.
Bergi,
128

Tylko wariacja na temat już podana w odpowiedzi, ale co z tym:

W sposób Thing,

export default function ThingA () {}

w things/index.js,

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

Następnie, aby skonsumować wszystkie rzeczy gdzie indziej,

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

Lub konsumować tylko niektóre rzeczy,

import {ThingA,ThingB} from './things'
Jed Richards
źródło
Chcesz spojrzeć na odpowiedź @ wolfbiter? Nie jestem pewien, dlaczego twierdzi, że nawiasy nie działają.
Bergi,
@Bergi Tak, zgadzam się, nie sądzę, że wolfbiter jest ważny ES6. Może używa starej wersji Babel lub innego transpilatora?
Jed Richards,
Jak to się transponuje? Importowanie katalogu nie jest index.jsdla mnie rozwiązaniem . Używam SystemJs + Babel
jasonszhao
2
Nie możesz po prostu pisać export ThingA from './ThingA'zamiastexport {default as ThingA} from './ThingA'
Petr Peller,
1
czy wykorzystuje to trzy drżenie? jeśli zaimportuję {ThingA} z „./things”, czy ThingB i ThingC zostaną dodane do pakietu?
Giorgio
75

Obecne odpowiedzi sugerują obejście tego problemu, ale denerwuje mnie, dlaczego tak nie jest, dlatego stworzyłem babelwtyczkę, która to robi.

Zainstaluj za pomocą:

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

następnie dodaj go do .babelrc:

{
    "plugins": ["wildcard"]
}

zobacz repozytorium, aby uzyskać szczegółowe informacje na temat instalacji


To pozwala ci to zrobić:

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

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

ponownie, repozytorium zawiera dalsze informacje na temat tego, co dokładnie robi, ale robienie tego w ten sposób pozwala uniknąć tworzenia index.jsplików, a także dzieje się w czasie kompilacji, aby uniknąć robienia readdirw czasie wykonywania.

Również dzięki nowszej wersji możesz zrobić dokładnie tak, jak twój przykład:

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

działa tak samo jak powyżej.

Downgoat
źródło
3
Ostrzeżenie, mam poważne problemy z tą wtyczką. Problemy prawdopodobnie wynikają z wewnętrznego buforowania, będziesz wyciągał włosy, gdy kod będzie idealny, ale twój skrypt nie będzie działał poprawnie, ponieważ dodałeś plik ./lib/things;i nie jest on pobierany. Problemy, które powoduje, są absurdalne. Właśnie byłem świadkiem sytuacji, gdy zmiana pliku z import *powoduje, że babel pobiera dodany plik, ale zmiana go z powrotem, przywraca problem, tak jakby ponownie używał pamięci podręcznej sprzed zmiany. Używaj ostrożnie.
Łukasz Zaroda
@ ŁukaszZaroda Babel ma wewnętrzny bufor, ~/.babel.jsonktóry powoduje takie dziwne zachowanie. Również jeśli używasz jak obserwator lub gorący przeładowywacz, musisz zapisać nowy plik, aby został ponownie skompilowany z nową listą katalogów
Downgoat
@Downgoat, więc jak to przezwyciężyć, z wyjątkiem usuwania pamięci podręcznej babel? I btw. Nie sądzę, aby twój komentarz był poprawny. Mam wyłączone buforowanie Babel i miałem tak ogromne problemy z tą wtyczką. Całkowicie nie polecam
SOReader
1
Przy okazji wszystkim, którzy mają dalsze problemy, dodaj, bpwc clear-cacheponieważ
pakiet WWW
To świetny pomysł, ale też nie udało mi się go uruchomić. Prawdopodobnie konflikt z moim kodem przepływającym, nie jestem pewien, ale otrzymywałem komunikat „ReferenceError: Foo nie jest zdefiniowany” bez względu na to, jak zorganizowałem import.
jlewkovich
13

Świetne gugly muglys! To było trudniejsze niż trzeba.

Eksportuj jedno domyślne mieszkanie

Jest to świetna okazja, aby korzystać z rozprzestrzeniania ( ...w { ...Matters, ...Contacts }poniżej:

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

Następnie, aby uruchomić skompilowany kod Babel z wiersza poleceń (z katalogu głównego projektu /):

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

Wyeksportuj jedno domyślne drzewo

Jeśli nie chcesz zastępować właściwości, zmień:

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

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

Wyjście będzie:

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

Eksportuj wiele nazwanych eksportów bez domyślnego

Jeśli poświęcasz się DRY , zmienia się również składnia importu:

// imports/collections/index.js

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

Spowoduje to utworzenie 2 nazwanych eksportów bez domyślnego eksportu. Następnie zmień:

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

console.log(Matters, Contacts);

A wynik:

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

Zaimportuj wszystkie nazwane eksporty

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

Zwróć uwagę na destrukcję import { Matters, Contacts } from './collections'; w poprzednim przykładzie.

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

W praktyce

Biorąc pod uwagę te pliki źródłowe:

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

Tworząc /myLib/index.js pakietu wszystkich plików przeczy celowi importu / eksportu. Łatwiej byłoby uczynić wszystko globalnym w pierwszej kolejności, niż uczynić wszystko globalnym poprzez import / eksport za pomocą „plików otokowych” index.js.

Jeśli chcesz konkretnego pliku, import thingA from './myLib/thingA'; we własnych projektach.

Utworzenie „pliku opakowania” z eksportami dla modułu ma sens tylko wtedy, gdy pakujesz dla npm lub w wieloletnim projekcie wielozadaniowym.

Dotarłeś tak daleko? Więcej informacji można znaleźć w dokumentacji .

Poza tym Stackoverflow w końcu wspiera trzy `s jako znaczniki ogrodzenia kodu.

Michael Cole
źródło
10

Możesz użyć importu asynchronicznego ():

import fs = require('fs');

i wtedy:

fs.readdir('./someDir', (err, files) => {
 files.forEach(file => {
  const module = import('./' + file).then(m =>
    m.callSomeMethod();
  );
  // or const module = await import('file')
  });
});
mr_squall
źródło
2
Dynamiczne importowanie jest bardzo miłe. Z pewnością nie istniały, kiedy zadano pytanie. Dziękuję za odpowiedź.
Frambot
6

Podobne do zaakceptowanego pytania, ale umożliwia skalowanie bez potrzeby dodawania nowego modułu do pliku indeksu za każdym razem, gdy go tworzysz:

./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
źródło
To nie działa dla mnie, gdy ./example.js
próbuję
dla mnie też nie działa (pakiet 4.41, babel 7.7)
Edwin Joassart
3

Użyłem ich kilka razy (w szczególności do budowania masywnych obiektów dzielących dane na wiele plików (np. Węzłów AST)), aby je zbudować, stworzyłem mały skrypt (który właśnie dodałem do npm, więc wszyscy inni można go użyć).

Użycie (obecnie musisz użyć Babel, aby użyć pliku eksportu):

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

Generuje plik zawierający:

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

Następnie możesz po prostu użyć pliku:

import * as myCoolModule from "my-cool-module.js"
myCoolModule.foo()
Jamesernator
źródło
Nie działa poprawnie w systemie Windows, generuje ścieżkę jako ścieżkę systemu Windows ( \` instead of / ) also as an improvment you may want to allow two options like --nazwa_pliku &&, --destaby umożliwić dostosowanie miejsca, w którym utworzony plik powinien być przechowywany i pod jaką nazwą. Nie działa również z nazwami plików zawierającymi .(jak user.model.js)
Yuri Scarbaci
2

To tylko inne podejście do odpowiedzi @ Bergi

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

export default {
 ThingA,
 ThingB,
 ThingC
}

Używa

import {ThingA, ThingB, ThingC} from './lib/things';
Ashok Vishwakarma
źródło
To nie zadziała. Właśnie wypróbowałem to w aplikacji reagującej i wróciło export '...' was not found in '.....
Hamid Mayeli
1

Możesz użyć również wymagają:

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

Następnie użyj modułu moduleHolder z dynamicznie ładowanymi kontrolerami:

  loadModules(DIR) 
  for (const controller of moduleHolder) {
    controller(app, db)
  }
mr_squall
źródło
0

Nie jest to dokładnie to, o co prosiłeś, ale dzięki tej metodzie mogę iterować componentsListpo innych plikach i korzystać z funkcji, componentsList.map(...)które są dla mnie bardzo przydatne!

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
źródło
0

Jeśli używasz webpacka. To automatycznie importuje pliki i eksportuje jako przestrzeń nazw interfejsu API .

Więc nie trzeba aktualizować przy każdym dodaniu pliku.

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;

Dla użytkowników maszynopisu;

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
źródło
0

Byłem w stanie wziąć z podejścia użytkownika atilkan i nieco go zmodyfikować:

Dla użytkowników maszynopisu;

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
źródło
-9

jeśli nie eksportujesz domyślnie w A, B, C, ale po prostu eksportujesz {}, możesz to zrobić

// 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
źródło
1
To nie jest prawidłowy skrypt javascript (wokół nie ma cytatów ./thing), a nawet gdyby tak było, nie działałoby. (Próbowałem, ale to nie zadziałało)
John