70

I'm using ECMAScript6 modules. What is the correct way to export/import multiple methods from a module from the options below?

Single class of static methods:

//------ myClass.js ------

export default class myClass {

  static myMethod1() {
    console.log('foo'); 
  }

  static myMethod2(args...) {
    console.log('bar'); 
  }  

}

//------ app.js ------

import myClass from 'myClass';
myClass.myMethod1();    //foo

Multiple exported methods:

//------ myMethods.js ------

export function myMethod1() {
    console.log('foo');
}

export function myMethod2() {
    console.log('bar');
}

//------ app.js ------
import {myMethod1, myMethod2} from 'myMethods';
myMethod1()    //foo;


//OR
import * as myMethods from 'myMethods';
myMethods.myMethod1()    //foo;

1) Exporting: A class of just static methods feels like a bit of a 'code smell' but similarly exporting everything individually does feel a bit verbose. Is it simply developer preference or are there performance implications here?

2) Importing: '* as' syntax is my preferred method as it allows you to use the dot notation (referencing both the module AND the method) aiding code readability. Does this have performance implications though when I may only be using 1 of the methods?

umpljazz
  • 1,182
  • 4
  • 11
  • 21
  • With `import {myMethod1, myMethod2} from 'myMethods';` the imported methods do not have to be dereferenced as a variable would. Though having said that I'm not sure if the dot notation is actually treated as a variable. – user5321531 Apr 27 '15 at 12:01
  • See also [Export an object containing function, or just export multiple functions in ES6](https://stackoverflow.com/q/34714826/1048572) – Bergi May 29 '21 at 18:00

2 Answers2

63

A class of just static methods feels like a bit of a 'code smell'

Yes indeed. You don't need a class structure here! Just export a normal "module" object:

//------ myMethods.js ------

export default {
  myMethod1() {
    console.log('foo'); 
  },
  myMethod2(args...) {
    console.log('bar'); 
  }  
};

I do recommend your second approach with multiple exports, though.

exporting everything individually does feel a bit verbose

Well, you don't need any wrapper structure, so I'd say it's less boilerplate. You just have to explicitly tag everything that you want to be exported, which is not a bad thing.

* as syntax is my preferred method as it allows you to use the dot notation (referencing both the module AND the method) aiding code readability.

That's very much personal preference, and does depend on the type of code you are writing. Sometimes conciseness is superior, but the ability to explicitly reference the module can be helpful as well. Notice that namespace imports using * as and default-imported objects are very similar here, though only named exports allow you to directly reference them via import {myMethod1, myMethod2}. So better leave the choice to those that import your module.

Does this have any performance implications?

Not much. Current ES6 implementations are not yet aiming for performance optimisations anyway.

In general, static identifiers are easier to resolve and optimise than property accesses[1], multiple named exports and partial imports could theoretically make JIT faster, and of course smaller files need less time to load if unused exports are removed during bundling. See here for details. There hardly will be noticeable performance differences, you should use what is better maintainable.

[1]: module namespaces (import * as ns) are static as well, even if ns.… looks like a dynamic property access

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I would use a class if exporting a class for the purpose of instantiating instances of the class. A plain `{ ... }` otherwise is all that is needed. – user5321531 Apr 27 '15 at 19:22
  • 1
    @user5321531: Yes, but a class with only static methods needs no instantiations :-) – Bergi Apr 27 '15 at 20:34
  • It would be nice to be able to do export default * (* being an object containing all named exports), but I guess that it goes against the "there should not be more than one way to do it" philosophy – Rivenfall Mar 27 '19 at 16:30
  • @Rivenfall You can [import yourself](https://stackoverflow.com/a/40242291/1048572) like `import * as namespace from './myself';`, then `export { namespace as default }`. However I would recommend to avoid that, just let the user of the library import the namespace. – Bergi Mar 27 '19 at 19:07
  • `import * as ` leads to many names for the same thing. one dev will import * as foo from 'foo' import * as myfoo from 'foo' import * as somethingcreative from 'foo' – Steven T. Cramer Aug 31 '19 at 04:19
  • 2
    @StevenT.Cramer Well you can always do `import { default as somethingcreative } from 'foo'`/`import somethingcreative from 'foo'`, named exports don't change anything about that. The convention is of course to import the "foo" module as `foo`. If your devs don't want to stick to that convention, it's their problem (or they have a good reason for it). – Bergi Aug 31 '19 at 16:55
  • A drawback of using exported functions directly is that they can't be stubbed in your tests (e.g. with Sinon). – chkpnt Sep 28 '22 at 08:47
  • 1
    @chkpnt Many test libraries support mocking ES modules (e.g. [testdouble](https://github.com/testdouble/testdouble.js/blob/main/docs/7-replacing-dependencies.md#how-module-replacement-works-for-es-modules-using-import), [esmock](https://www.npmjs.com/package/esmock), [jest](https://jestjs.io/docs/manual-mocks)) – Bergi Sep 28 '22 at 12:27
  • @Bergi: Good to know! As Sinon doesn't use a loader, it can only mock properties within an exported object. Apparently I need to take a closer look at the other frameworks. – chkpnt Sep 28 '22 at 13:15
6

TLDR; Use multiple exported methods and explicit import.

@Bergi is right about not needing a class with static fields, only an object in your first case. However, this option is discouraged by Axel Rauschmayer:

Note that default-exporting objects is usually an anti-pattern (if you want to export the properties). You lose some ES6 module benefits (tree-shaking and faster access to imports).

Devs at Airbnb recommend named exports and explicit wildcrad import, see this thread: https://github.com/airbnb/javascript/issues/710#issuecomment-297840604

thisismydesign
  • 21,553
  • 9
  • 123
  • 126
  • 1
    In many cases readability is more important, than tree-shaking – Michael Freidgeim Sep 05 '21 at 02:02
  • 1
    @MichaelFreidgeim There are many ways to improve readability that don't involve breaking reasonable conventions. Not breaking them also saves a lot of mental overhead, unless your favorite pastime is thinking about the cost-benefit of tree shaking. :) – thisismydesign Sep 05 '21 at 19:26