Search code examples
javascriptnode.jsesmodules

How to write ES module having both default and named export?


I want to make my ES module importable in both following styles:

import * as mylib from "./mylib.mjs";
import mylib from "./mylib.mjs";

Then I wrote mylib.mjs in following style:

export function foo(){
}
export function bar(){
}
export function baz(){
}
// ... and many export functions...(1)
export default {foo, bar, baz, /* ... and many function names...(2) */};

But I always have to worry about some names missing in export default(2). If a new export functions(1) is added, I have to add it also in function names(2). I often forget this.

In CommonJS, using exports makes this smarter. Is there any variable that is equivalent to exports in ES module?

exports.foo=function () {
};
exports.bar=function () {
};
exports.baz=function () {
};
exports.default=exports;// No need to change here when function export is added.

Solution

  • This is not something I would advise doing as I feel barrel files are an anti-pattern, and it is better to have one way for loading your modules, but something you could do to get close to what you want is to organize your project like this:

    .
    └── src/
        ├── mylib/
        │   └── mod.mjs
        ├── mylib.mjs
        └── consumer.mjs
    

    mylib/mod.mjs

    export function foo(){
    }
    export function bar(){
    }
    export function baz(){
    }
    

    mylib.mjs

    export * from './mylib/mod.mjs'
    export * as default from './mylib/mod.mjs'
    

    consumer.mjs

    import * as mylibNs from './mylib.mjs'
    import mylib from './mylib.mjs'
    

    Requires an extra file to manage the the exports, but you won't have to update the exported names in multiple places.

    Of course if you're ok with circular references in your modules, you can have just one file like this:

    mylib.mjs

    export function foo() {
    }
    export function bar() {
    }
    export function baz() {
    }
    
    export * as default from './mylib.mjs'
    

    It would be consumed the same way as before, but the default property would be a reference back to the module itself, i.e. mylib.default === mylib.

    This was tested using Node.js v20.12.0.