Search code examples
javascriptnode.jsmodulerequire

Why we need to pass module.exports as a parameter since we are already passing module as a parameter?


I have been going through some online tutorials on Node.js. What I understood is that, on using require(./file-path) function, the node gets the content of that file and wraps inside an immediately invoking function

(function(exports, require, module, __filename, __dirname) {
  // content 
}())

I understood the difference between exports and module.exports. That's all I can see in the internet on searching the above question. But what my question is , why we need to pass module.exports and module to the wrapping IIFE? We could have passed module alone and then get module.exports from it. Is there any advantage on doing like this? Normally when we pass an object to a function , we don't have to pass object.property additionally.


Solution

  • The answer is: historical reasons.

    You're right, we could have only module and exports would not be needed but it's still there for backwards compatibility.

    It used to be a time when the module wrapper was changed in pretty much every patch release.

    In Node 0.1.11 the module wrapper was:

    var wrapper = "function (__filename) { "+
                  "  var onLoad; "+
                  "  var onExit; "+
                  "  var exports = this; "+
                  content+
                  "\n"+
                  "  this.__onLoad = onLoad;\n"+
                  "  this.__onExit = onExit;\n"+
                  "};\n";
    

    See: https://github.com/nodejs/node/blob/v0.1.11/src/node.js#L167#L177

    As you can see the exports was the same as the this that the wrapper function was called with. You couldn't swap it with a new object and you couldn't even add some reserved keys to it - e.g. you couldn't safely export a property named __onExit.

    Then in 0.1.12 it was:

    var wrapper = "function (__filename, exports) { " + content + "\n};";
    

    See: https://github.com/nodejs/node/blob/v0.1.12/src/node.js#L243-L245

    Here the exports was an object supplied as one of the arguments but you couldn't swap it with a new object, you could only add or remove properties from the object that you got.

    Then the 0.1.13 was the first to have this, i.e. require and include:

    var wrapper = "function (__filename, exports, require, include) { " + content + "\n};";
    

    See: https://github.com/nodejs/node/blob/v0.1.13/src/node.js#L225-L227

    Then 0.1.14 was the first to have __module (with underscores) in the wrapper (and that dropped include):

    var wrapper = "var __wrap__ = function (__module, __filename, exports, require) { " 
                + content 
                + "\n}; __wrap__;";
    

    See: https://github.com/nodejs/node/blob/v0.1.14/src/node.js#L280-L284

    And the 0.1.16 was the first to have a module argument (with no underscores) in the wrapper:

    var wrapper = "var __wrap__ = function (exports, require, module, __filename) { " 
                + content 
                + "\n}; __wrap__;";
    

    See: https://github.com/nodejs/node/blob/v0.1.16/src/node.js#L444-L448

    It's been changed many times after that but this is the time that the module got introduced making the exports not necessary any more but still a useful shortcut, allowing you to use:

    exports.a = 1;
    exports.b = 2;
    exports.c = 3;
    

    instead of:

    module.exports.a = 1;
    module.exports.b = 2;
    module.exports.c = 3;
    

    though in practice if there was no exports then one would usually write:

    const exports = module.exports;
    exports.a = 1;
    exports.b = 2;
    exports.c = 3;
    

    or more likely:

    module.exports = {
      a: 1,
      b: 2,
      c: 3,
    };
    

    or, to have some checks in static analysis tools:

    const a = 1;
    const b = 2;
    const c = 3;
    module.exports = { a, b, c };
    

    There are many ways to do it, it's a pretty flexible mechanism.