Providing both default and named exports in JavaScript

15 Jul 2023 in Tech

I always wondered how some NPM packages provided a default export in addition to named exports. Something like this:

js
// Provide the most common use case
const validator = require("my-validator");
// Then you can use validator() or one of the components such as validateEmail()
// Validate everything in one
const isValid = validator(payload);
// Or validate specific things
const isValidEmail = validator.validateEmail(payload.email);

Then one day I was using nock to write some tests and noticed that they do the same thing:

js
const nock = require("nock");
nock.disableNetConnect();
const scope = nock("http://www.example.com")
.get("/resource")
.reply(200, "path matched");

So, I went digging in the code and found that it can be accomplished by setting module.exports to be a function, then using Object.assign to attach new method to the function prototype:

js
// my-validator/main.js
const validateEmail = require('./validators/email');
const validatePhoneNumber = require('./validators/phone');
const validateAddress,= = require('./validators/address');
module.exports = function(payload){
// Do validation
}
Object.assign(module.exports, {
validateEmail,
validatePhoneNumber,
validateAddress,
});

Now that I’ve seen it in action it makes total sense. Using named exports allows you to export useful functions from your package without people needing to require specific files (which you may choose to move in the future). Object.assign expands your public API, providing additional value to consumers but also additional maintenance burden to you.

Now you know how to expose multiple functions from a single package as part of your public API. Now, you just need to decide what you want to expose as your public API 😀.