Можно ли импортировать модули из всех файлов в каталоге, используя подстановочный знак?


256

С ES6 я могу импортировать несколько экспортов из файла, подобного этому:

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

Тем не менее, мне нравится организация одного модуля на файл. Я получаю импорт, как это:

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

Я хотел бы быть в состоянии сделать это:

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

или что-то подобное, с понятным соглашением, что каждый файл содержит один экспорт по умолчанию, и каждый модуль называется так же, как его файл.

Это возможно?


Это возможно. Пожалуйста, смотрите документацию модуля для babel babeljs.io/docs/learn-es2015 ... guoted "import {sum, pi} из" lib / math ";". Принятый ответ больше не действителен. Пожалуйста, обновите это.
Эдуард Джако

6
@ kresli Я не думаю, что вы понимаете вопрос. В документации, lib/mathэто файл, содержащий несколько экспортов. В моем вопросе lib/math/это каталог, содержащий несколько файлов, каждый из которых содержит один экспорт.
Frambot

2
да я вижу. В этом случае Берги прав. Извините
Эдуард Джако,

Ответы:


231

Я не думаю, что это возможно, но на самом деле разрешение имен модулей зависит от загрузчиков модулей, так что может быть реализация загрузчика, которая поддерживает это.

До тех пор вы можете использовать промежуточный «файл модуля», lib/things/index.jsкоторый просто содержит

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

и это позволит вам сделать

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

6
Спасибо за помощь. Я был в состоянии получить эту работу с index.jsпохожим: import ThingA from 'things/ThingA'; export {ThingA as ThingA}; import ThingB from 'things/ThingB'; export {ThingB as ThingB};. Другие заклинания index.jsне сдвинулись с места.
Frambot

2
Хм, export * fromдолжно работать. Вы пробовали …from './ThingA'или export ThingA from …? Какой модуль загрузчик вы используете?
Берги

7
Да, ваш первоначальный ответ сработал, если каждый ThingA.js, ThingB.js экспортировал именованные экспорты. Пятно на.
Frambot

1
Вам нужно указать индексный файл или вы можете указать только папку, и вместо него будет загружен index.js?
Зоргатоне

1
@Zorgatone: Это зависит от того, какой загрузчик модулей вы используете, но, как правило, пути к папке будет достаточно.
Берги

128

Просто вариация на тему уже указана в ответе, но как на счет этого:

В Thing,

export default function ThingA () {}

В things/index.js,

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

Затем потреблять все вещи в другом месте,

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

Или потреблять только некоторые вещи,

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

Хотите взглянуть на ответ @ wolfbiter? Не уверен, почему он утверждает, что скобки не работают.
Берги

@ Берги Да, согласен, я не думаю, что волкодавец действительно ES6. Может быть, он использует старую версию Babel или какой-то другой транспортер?
Джед Ричардс

Как это происходит? Импорт каталога не разрешается index.jsдля меня. Я использую SystemJs + Babel
jasonszhao

2
Не могли бы вы просто напечатать export ThingA from './ThingA'вместоexport {default as ThingA} from './ThingA'
Петр Пеллер

1
это использует три встряхивания? если я импортирую {ThingA} из './things', будут ли также ThingB и ThingC добавлены в комплект?
Джорджио

75

Текущие ответы предлагают обходной путь, но меня беспокоит, почему этого не существует, поэтому я создал babelплагин, который делает это.

Установите его, используя:

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

затем добавьте его к себе .babelrcс помощью:

{
    "plugins": ["wildcard"]
}

см. репо для подробной информации об установке


Это позволяет вам сделать это:

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

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

Опять же, репозиторий содержит дополнительную информацию о том, что именно он делает, но, делая это таким образом, избегает создания index.jsфайлов, а также происходит во время компиляции, чтобы избежатьreaddir s во время выполнения.

Также с более новой версией вы можете сделать так же, как ваш пример:

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

работает так же, как и выше.


3
Предупреждение, у меня серьезные проблемы с этим плагином. Проблемы, вероятно, связаны с его внутренним кэшированием, вы будете убирать волосы, когда ваш код будет идеальным, но ваш скрипт не будет работать должным образом, потому что вы добавили файл, ./lib/things;а он не был выбран. Проблемы, которые это вызывает, смешны. Я только что был свидетелем ситуации, когда изменение файла с помощью import *babel заставляет забрать добавленный файл, но изменение его обратно возвращает проблему, как если бы она повторно использовала кэш до изменения. Используйте с осторожностью.
Лукаш Зарода

@ ŁukaszZaroda babel имеет внутренний кеш, ~/.babel.jsonкоторый вызывает такое странное поведение. Кроме того, если вы используете как наблюдатель или горячий перегрузчик, вы должны сохранить новый файл, чтобы он был перекомпилирован с новым списком каталогов
Downgoat

@ Downgoat, как это преодолеть, кроме удаления кеша babel? И кстати. Я не думаю, что ваш комментарий правильный. У меня отключено кэширование babel, и у меня были огромные проблемы с этим плагином. Совершенно не рекомендую
SOReader

1
Кстати, для тех, у кого есть дальнейшие проблемы, добавьте, bpwc clear-cacheпотому что webpack и другие процессы сборки будут все еще молча кэшироваться
Downgoat

Это отличная идея, но я не смог заставить ее работать. Возможно, конфликт с моим кодированным потоком кода, я не уверен, но я получал `ReferenceError: Foo не определен 'независимо от того, как я структурировал импорт.
jlewkovich

13

Великие гугли магглы! Это было сложнее, чем нужно было.

Экспортировать одну квартиру по умолчанию

Это отличная возможность использовать распространение ( ...в { ...Matters, ...Contacts }ниже:

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

Затем, чтобы запустить скомпилированный код babel из командной строки (из проекта root /):

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

Экспорт одного древовидного по умолчанию

Если вы не хотите перезаписывать свойства, измените:

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

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

И вывод будет:

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

Экспорт нескольких именованных экспортов без по умолчанию

Если вы посвящены DRY , синтаксис в импорте также меняется:

// imports/collections/index.js

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

Это создает 2 именованных экспорта без экспорта по умолчанию. Затем измените:

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

console.log(Matters, Contacts);

И вывод:

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

Импортировать все именованные экспорты

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

Обратите внимание на разрушение import { Matters, Contacts } from './collections'; в предыдущем примере.

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

На практике

Учитывая эти исходные файлы:

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

Создание /myLib/index.jsдля объединения всех файлов побеждает цель импорта / экспорта. Во-первых, было бы проще сделать все глобальным, чем сделать все глобальным с помощью импорта / экспорта через index.js «файлы-оболочки».

Если вы хотите определенный файл, import thingA from './myLib/thingA'; в ваших собственных проектах.

Создание «файла-обертки» с экспортом для модуля имеет смысл только в том случае, если вы собираете пакет для npm или для многолетнего многопользовательского проекта.

Сделал это так далеко? Смотрите документы для более подробной информации.

Кроме того, yay для Stackoverflow, наконец, поддерживает три в качестве разметки кода.


10

Вы можете использовать асинхронный импорт ():

import fs = require('fs');

а потом:

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

2
Динамический импорт так хорош. Они точно не существовали, когда был задан вопрос. Спасибо за ответ.
Frambot

6

Аналогичен принятому вопросу, но позволяет масштабировать без необходимости добавления нового модуля в индексный файл каждый раз, когда вы его создаете:

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

Это не работает для меня при попытке импортировать в качестве псевдонима./example.js
tsujp

у меня тоже не работает (webpack 4.41, babel 7.7)
Эдвин Джоассарт

3

Я использовал их несколько раз (в частности, для создания массивных объектов, разделяющих данные по многим файлам (например, узлы AST)), чтобы создать их, я создал крошечный скрипт (который я только что добавил в npm, чтобы все остальные можете использовать это).

Использование (в настоящее время вам нужно использовать babel для использования файла экспорта):

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

Создает файл, содержащий:

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

Тогда вы можете просто использовать файл:

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

Не работает правильно в Windows, генерирует путь как путь к Windows ( \` instead of / ) also as an improvment you may want to allow two options like --filename` &&, --destчтобы разрешить настройку, где должен быть сохранен созданный файл и под каким именем. Также не работает с именами файлов, содержащими .(как user.model.js)
Юрий Скарбачи

2

Просто другой подход к ответу @ Берги

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

export default {
 ThingA,
 ThingB,
 ThingC
}

Пользы

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

Это не сработает. Я только попробовал это в приложении реакции, и это возвратилось export '...' was not found in '.....
Хамид Майели

1

Вы также можете использовать 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)
  }
}

Затем используйте свой moduleHolder с динамически загружаемыми контроллерами:

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

0

Это не совсем то, что вы просили, но с помощью этого метода я могу выполнять итерации componentsListв других моих файлах и использовать такие функции, componentsList.map(...)которые я считаю довольно полезными!

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

Если вы используете веб-пакет. Это импортирует файлы автоматически и экспортирует как API пространство имен .

Так что не нужно обновляться при каждом добавлении файла.

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;

Для пользователей 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

Я смог взять у пользователя подход atilkan и немного его изменить:

Для пользователей 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

если вы не экспортируете значения по умолчанию в A, B, C, а просто экспортируете {}, то это можно сделать

// 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
Это недопустимый JavaScript (здесь нет кавычек ./thing), и даже если бы он был, он не работал бы. (Я попробовал это, и это не сработало.)
Джон
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.