20
votes

In the past I've used the revealing module pattern.

function myModule() {
  function foo() ...
  function bar() ...

  return { 
    foo: foo, 
    bar: bar
  };
}

With ES6, this was improved with object shorthand.

function myModule() {
  function foo() ...
  function bar() ...

  return { foo, bar };
}

Now with built-in module syntax, I'm struggling to find the preferred pattern that is most similar to the above.

Option #1 named exports

// export file
function foo() ...
function bar() ...

export { foo, bar };

// import file
import { foo, bar } from './export-file';

foo();
bar();

Option #2 default export/import with destructuring

// export file
function foo() ...
function bar() ...

export default { foo, bar };

// import file
import baz from './export-file';

const { foo, bar } = baz;

foo();
bar();

Option #3 default export/import with name spacing

// export file
function foo() ...
function bar() ...

export default { foo, bar };

//import file
import baz from './export-file';

baz.foo();
baz.bar();

I like Option #1 with the named exports for the simplicity it offers in the "destructuring" import syntax.

import { foo, bar } from './export-file';

I also want to continue to make the module's exported API explicitly defined at the bottom of the exporting file in the export object.

export { foo, bar };
// OR
export default { foo, bar };

I read all the time that default exports are preferred, and so I've been trying to find a preferred pattern that includes default exports, but I'm reluctant to adopt as it just seems more verbose with few advantages (other than the need for name-spacing, or the inclusion of both named and default exports in some cases).

Is there an idiomatic pattern for the revealing module pattern with ES6 module syntax?

1
I personally prefer option #2, as it feels like it closely matches RMP.evolutionxbox
You could also just directly export the function definitions: export function foo() { .... This question is somewhat opinion based, and here's mine: don't default export "namespace objects", since one can do import * as baz from './export-file' for that effect anyway.Ilja Everilä
I'm sorry I missed the "I also want to continue to make the module's exported API explicitly defined at the bottom", but still In my opinion the default should provide something fundamental such as TheOneClassToRuleThemAll and possible utilities etc. belong as plain exports, so a user of the module can easily either destructure in the import statement, or do the star import.Ilja Everilä
thanks @IljaEverilä. i'm starting to see that expressed elsewhere as well. "Default exports only are favoured when there is only a single major value to export (e.g. a class)". This sentiment expressed by you and others, has me leaning toward option #1 for now.sfletche
"I read all the time" - where? That claim needs to be debunked. Everywhere.Bergi

1 Answers

14
votes

I read all the time that default exports are preferred

No, they are not. They are simpler and have shorter syntax, and might be used more often (since there are more small, single-export modules), but they are not generally preferred.

Use the right tool for the job. You already know the advantages you want.

Is there an idiomatic pattern for the revealing module pattern with ES6 module syntax?

Yes, Option #1. When you have multiple things to export, always use named exports.

You get both explicit aliasing and tree shaking from the picky syntax

import { foo, bar } from './export-file';

as well as namespaces

import * as baz from './export-file';