290

With ES6, I can import several exports from a file like this:

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

However, I like the organization of having one module per file. I end up with imports like this:

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

I would love to be able to do this:

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

or something similar, with the understood convention that each file contains one default export, and each module is named the same as its file.

Is this possible?

Sebastian Simon
  • 14,320
  • 6
  • 42
  • 61
Joe Frambach
  • 25,568
  • 9
  • 65
  • 95
  • 1
    This is possible. Please see module documentation for babel https://babeljs.io/docs/learn-es2015/ ... guoted "import {sum, pi} from "lib/math";". The accepted answer is no more valid. Please update it. – Eduard Jacko Aug 04 '15 at 23:04
  • 8
    @kresli I don't think you understand the question. In the docs, `lib/math` is a file containing multiple exports. In my question, `lib/math/` is a directory containing several files, each containing one export. – Joe Frambach Aug 07 '15 at 00:26
  • 2
    ok, I see. In that case Bergi is correct. Sorry – Eduard Jacko Aug 07 '15 at 22:26

13 Answers13

259

I don't think this is possible, but afaik the resolution of module names is up to module loaders so there might a loader implementation that does support this.

Until then, you could use an intermediate "module file" at lib/things/index.js that just contains

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

and it would allow you to do

import {ThingA, ThingB, ThingC} from 'lib/things';
Fareed Alnamrouti
  • 26,439
  • 4
  • 77
  • 71
Bergi
  • 513,640
  • 108
  • 821
  • 1,164
  • 6
    Thanks for the help. I was able to get this working with `index.js` looking like: `import ThingA from 'things/ThingA'; export {ThingA as ThingA}; import ThingB from 'things/ThingB'; export {ThingB as ThingB};`. Other incantations in `index.js` wouldn't budge. – Joe Frambach Apr 18 '15 at 21:02
  • 2
    Hm, [`export * from`](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-exports-static-semantics-exportentries) should work. Have you tried `…from './ThingA'` or `export ThingA from …`? What module loader are you using? – Bergi Apr 18 '15 at 21:11
  • Or is it because you use default exports instead of exporting a `ThingA` from `ThingA`? – Bergi Apr 18 '15 at 21:14
  • I am using default exports in each lib/things/ThingX.js file. That seems to be the convention. I am using webpack on iojs 1.7.1 – Joe Frambach Apr 18 '15 at 21:22
  • 7
    Yes, your original answer did work if each ThingA.js, ThingB.js, each exported named exports. Spot on. – Joe Frambach Apr 18 '15 at 21:30
  • Does anyone know if there is an already existing utility to generate an index.js file? – majorBummer Sep 27 '15 at 21:33
  • @majorBummer - you can probably build this using GULP, or as an NPM tool. – vsync Oct 26 '15 at 12:39
  • 1
    Do you have to specify the index file or can you specify just the folder and index.js will be loaded instead? – Zorgatone May 11 '16 at 10:06
  • 1
    @Zorgatone: That depends on the module loader you are using, but yes usually the folder path will be enough. – Bergi May 11 '16 at 10:49
  • I mean. In the standard modules. Will that work in browsers when they will support modules, or would them fail to resolve that? – Zorgatone May 11 '16 at 10:50
  • @Zorgatone: There is no standard for browser modules yet. – Bergi May 11 '16 at 10:54
  • Aren't ES6 modules part of the ES6 standard? – Zorgatone May 11 '16 at 10:55
  • @Zorgatone: Only the declaration syntax, not how they are loaded or how they are identified by name. – Bergi May 11 '16 at 10:56
  • 1
    if you have `export * from 'ThingA'; export * from 'ThingB';`, How can you import the default export of `ThingA` and `ThingB` from the `lib/things/index.js` file – Olivier Boissé Oct 21 '19 at 18:47
  • 1
    @OlivierBoissé If they're default exports, [you need to explicitly give them a name](https://stackoverflow.com/a/34072770/1048572) - star exports don't include `default`, which would collide between the `ThingA` and `ThingB` modules. If you have both named and default exports, you need to use both syntaxes. – Bergi Oct 21 '19 at 19:02
  • Is there a direct benefit for using this approach besides readability? – Rodrigo Mata Dec 27 '19 at 17:58
  • @RodrigoMata What alternative approach are you comparing against? – Bergi Dec 28 '19 at 11:14
  • @Bergi I mean, between having or not this intermediate module file. IMO it will help if e.g. I want to rename some specific `export` or if I need to import a lot of dependencies at once, but I would like to know if there are additional benefits from it – Rodrigo Mata Dec 28 '19 at 18:41
  • 1
    @RodrigoMata No, it really just provides this grouping (and defined evaluation order, but that's rarely needed). – Bergi Dec 28 '19 at 19:03
136

Just a variation on the theme already provided in the answer, but how about this:

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

Then to consume all the things elsewhere,

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

Or to consume just some of things,

import {ThingA,ThingB} from './things'
Jed Richards
  • 11,346
  • 2
  • 21
  • 33
83

The current answers suggest a workaround but it's bugged me why this doesn't exist, so I've created a babel plugin which does this.

Install it using:

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

then add it to your .babelrc with:

{
    "plugins": ["wildcard"]
}

see the repo for detailed install info


This allows you to do this:

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

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

again, the repo contains further information on what exactly it does, but doing it this way avoids creating index.js files and also happens at compile-time to avoid doing readdirs at runtime.

Also with a newer version you can do exactly like your example:

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

works the same as the above.

Downgoat
  • 11,422
  • 5
  • 37
  • 64
  • 6
    Warning, I'm having severe issues with this plugin. Problems probably come from its internal caching, you will be pulling your hair out, when your code will be perfect, but your script won't work properly because you added file to `./lib/things;` and it is not being picked up. The problems it causes are ridiculous. I just witnessed situation, when changing file with `import *` makes babel to pick up added file, but changing it back, brings problem back, like it reuses cache from before the change. Use with caution. – Łukasz Zaroda Sep 23 '17 at 12:42
  • @ŁukaszZaroda babel has an internal cache at `~/.babel.json` which causes this weird behavior. Also if you are using like a watcher or a hot reloader you have to save the new file so it’ll be recompiled with the new directory listing – Downgoat Sep 23 '17 at 14:18
  • @Downgoat so how to overcome this except for deleting babel's cache? And btw. I don't think your comment is correct. I have babel's caching disabled and had such a huge problems with that plugin. Totally not recommend it – SOReader Oct 20 '17 at 14:28
  • 1
    Btw to anyone having further issues, add `bpwc clear-cache` because webpack and other build processes will still silently cache – Downgoat Jun 08 '18 at 16:44
  • This is a great idea but I wasn't able to get it to work either. Possibly a conflict with my flowtyped code, I'm not sure, but I was getting `ReferenceError: Foo is not defined' no matter how I structured the imports. – jlewkovich Jun 15 '19 at 18:51
  • @Downgoat Where do I add "bpwc clear-cache"? Thanks – Raine Revere Dec 13 '19 at 21:46
  • Works fine for me. babel-loader: ^8.0.6 – Darin Cardin Mar 02 '20 at 14:55
23

Great gugly muglys! This was harder than it needed to be.

Export one flat default

This is a great opportunity to use spread (... in { ...Matters, ...Contacts } below:

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

Then, to run babel compiled code from the command line (from project 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' }

Export one tree-like default

If you'd prefer to not overwrite properties, change:

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

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

And the output will be:

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

Export multiple named exports w/ no default

If you're dedicated to DRY, the syntax on the imports changes as well:

// imports/collections/index.js

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

This creates 2 named exports w/ no default export. Then change:

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

console.log(Matters, Contacts);

And the output:

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

Import all named exports

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

Notice the destructuring import { Matters, Contacts } from './collections'; in the previous example.

$ 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 practice

Given these source files:

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

Creating a /myLib/index.js to bundle up all the files defeats the purpose of import/export. It would be easier to make everything global in the first place, than to make everything global via import/export via index.js "wrapper files".

If you want a particular file, import thingA from './myLib/thingA'; in your own projects.

Creating a "wrapper file" with exports for the module only makes sense if you're packaging for npm or on a multi-year multi-team project.

Made it this far? See the docs for more details.

Also, yay for Stackoverflow finally supporting three `s as code fence markup.

Michael Cole
  • 13,473
  • 4
  • 66
  • 81
20

You now can use async import():

import fs = require('fs');

and then:

fs.readdir('./someDir', (err, files) => {
 files.forEach(file => {
  const module = import('./' + file).then(m =>
    m.callSomeMethod();
  );
  // or const module = await import('file')
  });
});
Christian Fritz
  • 15,980
  • 3
  • 38
  • 58
mr_squall
  • 1,556
  • 16
  • 17
  • 3
    Dynamic imports are nice like that. They sure didn't exist when the question was asked. Thanks for the answer. – Joe Frambach Mar 07 '19 at 18:19
  • I'm having trouble figuring out where this would go. Would this be found in the `index.js` file to load all files in the directory? Instead of `import('file')` can you also do `export * from 'file'`? A little help? Thanks! – Joshua Pinter May 28 '21 at 02:46
  • This can be in some bootstrap method where you want to register all routes from controller-files for example. – mr_squall May 28 '21 at 03:35
10

Similar to the accepted question but it allows you to scale without the need of adding a new module to the index file each time you create one:

./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
  • 923
  • 1
  • 7
  • 19
3

I've used them a few times (in particular for building massive objects splitting the data over many files (e.g. AST nodes)), in order to build them I made a tiny script (which I've just added to npm so everyone else can use it).

Usage (currently you'll need to use babel to use the export file):

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

Generates a file containing:

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

Then you can just consume the file:

import * as myCoolModule from "my-cool-module.js"
myCoolModule.foo()
Jamesernator
  • 648
  • 7
  • 12
  • Not working correctly in windows, generates path as a windows path ( `\\` instead of `/`) also as an improvment you may want to allow two options like `--filename` && `--dest` to allow customizing where the created file should be stored and under wich name. Also doesn't work with filenames containing `.` (like `user.model.js`) – Yuri Scarbaci Jan 16 '19 at 11:01
2

Just an other approach to @Bergi's answer

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

export default {
 ThingA,
 ThingB,
 ThingC
}

Uses

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

You can use require as well:

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

Then use your moduleHolder with dynamically loaded controllers:

  loadModules(DIR) 
  for (const controller of moduleHolder) {
    controller(app, db)
  }
mr_squall
  • 1,556
  • 16
  • 17
1

If you are using webpack. This imports files automatically and exports as api namespace.

So no need to update on every file addition.

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;

For Typescript users;

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
  • 3,591
  • 1
  • 24
  • 34
0

This is not exactly what you asked for but, with this method I can Iterate throught componentsList in my other files and use function such as componentsList.map(...) which I find pretty usefull !

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
  • 550
  • 3
  • 22
0

I was able to take from user atilkan's approach and modify it a bit:

For Typescript users;

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

}));
-10

if you don't export default in A, B, C but just export {} then it's possible to do so

// 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
  • 2,642
  • 2
  • 14
  • 25
  • 2
    This is not valid javascript (there's no quotes around `./thing`) and even if there were, it would not work. (I tried it, and it didn't work.) – John Apr 24 '17 at 22:11