41

All my experience with exporting/importing modules has come in ES6 using export and import, where you can do something like this to have a single module export a default function as well as separate named functions.

// module.js
export default mainFunction
export { namedFunction }

// main.js
import mainFunction from 'functions'
mainFunction()

import { namedFunction } from 'function'
namedFunction()

However I can't figure out how to do this with ES5 style imports using module.exports and require. As far as I understand, I can export either a single default:

// module.js
module.exports = function mainFunction() {}

// main.js
const mainFunction = require('module.js')

Or I can create named exports:

// module.js
module.exports = {
  namedFunction: function() {}
}

// main.js
const namedFunction = require('module.js').namedFunction

But I can't do both. I thought I could maybe name one of the exports "default" like this, but it doesn't work

// module.js
module.exports = {
  default: function() {},
  namedFunction: function() {}
}

// main.js
const mainFunction = require('module.js') // does not work
const mainFunction = require('module.js').default // works, but not what I want
const namedFunction = require('module.js').namedFunction

How can I accomplish this dual default/named export with ES5?

Ryan Giglio
  • 1,085
  • 1
  • 14
  • 26
  • Exports in ES6 (compiled with babel) export default to a property named `default` and set a property `_esModule`. When importing the default module, it will import `default` iff `_esModule` exists. Otherwise, it imports the whole module as the default. This behavior has caused some [confusion](https://github.com/babel/babel/issues/2212). – Garrett Motzner Jan 04 '19 at 23:27

3 Answers3

72

You want to assign the value of module.exports to be your default function, and then put all the named exports as properties on that function.

const defaultFunction = () => { console.log('default!'); };
const namedFunction1 = () => { console.log('1!'); };
const namedFunction2 = () => { console.log('2!'); };

const myModule = module.exports = defaultFunction;
myModule.namedFunction1 = namedFunction1;
myModule.namedFunction2 = namedFunction2;

Let's say that was in myModule.js. Then you can do this:

const myModule = require('./myModule');
myModule(); // Prints: 'default!'
myModule.namedFunction1(); // Prints: '1!'
Trott
  • 66,479
  • 23
  • 173
  • 212
  • 10
    I'm assuming there is a reason, but why not just assign directly to module.exports? I tried it and it appears to work fine: `module.exports = defaultFunction; module.exports.namedFunction1 = namedFunction1;` – Ada Richards Dec 30 '19 at 08:41
  • 2
    @arichards If that's easier/better for you, then that should work. If I'm reasonably sure the function will be called `myModule` where it is used, then I like to call it `myModule` where it is defined as well. But that's just my preference. – Trott Dec 30 '19 at 15:38
7

Just re-assign exports to module.exports

Like:

//name.js

const NameType = {
  foo: "bar",
};

function Name(name) {
  this.name = name;
}

module.exports = Name; // assign default export to Name
exports = module.exports; // re-assign exports to point it to the updated location.

exports.NameType = NameType; // now you can use named export as usual

In another file you can import like:

const Name = require("./name");
const { NameType } = require("./name");

This works because by default module = { exports: {} } and exports = module.exports

Reference: Difference between "module.exports" and "exports" in the CommonJs Module System

KJ Sudarshan
  • 2,694
  • 1
  • 29
  • 22
  • 1
    Hmm.... I am confused... how is `module.exports = Name; exports = module.exports; exports.NameType = NameType` different than just `module.exports = Name; Name.NameType = NameType;` (i.e. the accepted answer)? My understanding is that *assigning* a value to exports doesn't affect the import (that's explained in your ref link), but since you're assigning it to `Name` you *do* end up adding `.NameType = NameType` to `Name`, even if the change to `exports` doesn't make it out of the module. Am I missing something? – Jason C Jun 15 '22 at 01:14
  • I guess I don't understand what "the updated location" is in your comments... what is the "updated location"? Isn't `exports` already assigned to `module.exports` by default? – Jason C Jun 15 '22 at 01:16
  • @JasonC It's important to re-assign since `module.exports = Name;` makes it now point to a different object ( Name in this case ). But `exports` still points to what `module.exports` was pointing to before. Hence the re-assignment. – KJ Sudarshan Jul 10 '23 at 06:38
  • Also, the re-assignment is only done to keep the API for named and default exports similar to how one normally uses them. Otherwise. `module.exports.NameType` also works for the use case intended here. But we prefer using `exports.NameType` over `module.exports.NameType`. – KJ Sudarshan Jul 10 '23 at 06:57
0

If you don't want to repeat the named exports, you can always do something like

module.exports = defaultFunction;
module.exports = Object.assign(module.exports, {
    namedFunction1,
    namedFunction2,
});

Then you can require as normal;

const myModule = require('./myModule');
myModule();
myModule.namedFunction1();
Shannon Hochkins
  • 11,763
  • 15
  • 62
  • 95