Is there any reason to define module.exports using an IIFE?
Asked Answered
H

3

22

My team doesn't have any experienced JS developers, but we are writing a library in Node and got a suggestion from a real JS developer that "We should make the js more modular - not to pollute the global namespace and to make it more readable to new-comers", and told us to do the following:

module.exports = (function(){
      return {
         nameToExpose: functionToExpose
         ...
    };
})();

rather than

module.exports.nameToExpose = functionToExpose;

What's the point of this, if any? The latter does not make any local declarations that would be scoped by the IIFE, and even if it did, they would be local to the module file and not global to the whole program that require()s it.

Some Googling and poking about this site does not turn up any answers on this particular question, though there are many other explanations of IIFEs that I have read (and which are summarized in the above comment). Some testing certainly reveals that the latter does not actually put functionToExpose in the global namespace, though its original name is recorded in the function type itself.

Holiday answered 8/9, 2015 at 17:10 Comment(4)
He must have gave you an advice for javascript code in the browser. An IIFE in that case does prevent global namespace pollution.Mickimickie
That's what my teammate thought, but the particular code in question was part of an application specifically intended to be run with Node. There is no browser front-end.Holiday
Go find another "real JS developer" who actually understands node and what a module is and a global namespace is. If this is more readable I'll eat my hat.Gerius
The only good reason I can think of is if you're developing a library that needs to run in the server but can also run in browser then you should need to wrap it with IIFE for obvious reasons, otherwise, if it's strictly a NodeJS module and it can't or shouldn't run in browser then you shouldn't do it.Globoid
H
19

Pretty much no difference. The whole idea of Node.js, using require, having modules, etc., is specifically to separate concerns. I'd say (cautiously) that if you're doing it right, you shouldn't be needing to worry about "polluting" any sort of global scope. Anything within module.exports lives in that module.

When you're dealing with front-end stuff, that's when the global scope becomes something of a concern, because if a function or whatever isn't scoped (i.e., in an IIFE, or other function block), it has access to the global window object, and everything else has access to that function.

a real JS developer

Calling someone that is a red flag.

not to pollute the global namespace and to make it more readable to new-comers

If you're modularizing your code correctly, that shouldn't be a concern. There's a time and a place for IIFEs, but I see no reason why wrapping everything in an IIFE, which is already inside of a module, would somehow magically make the code "more modular" or any more readable to "new comers" than by simply using Node.js like it was designed:

module.exports = function() { ... } // whatever

and even if it did, they would be local to the module file and not global to the whole program that require()s it.

You are correct. I'd take whatever he's saying with a grain of salt. Maybe he knows of some specific use-cases where his approach has been helpful to him in the past, so I'd ask him specifically about that to see what he says. Other than that, I feel like you're on the right track.

Hibernal answered 8/9, 2015 at 17:18 Comment(0)
B
13

The reason to maybe sometimes do this is because if you don't, then any variables you need for the module.exports object have to be scoped to the entire file.

Consider these two ways.

  1. Without IIFE.

    var foo = 'la' + 'la';  // some computed value
    
    //
    // ... lots of program code here ...
    //
    
    module.exports = {
        foo : foo,
    };
    
  2. With IIFE.

    //
    // ... lots of program code here ...
    //
    
    module.exports = (function () {
        var foo = 'la' + 'la';  // some computed value
        return {
            foo : foo
        }
    }());
    

In the first example, two problems arise.

  1. Your variables (like foo) are created quite far away from where they are used to export a value from the module. This can reduce clarity. Sure, you can declare a variable after the program code, but it still has the same scope (and vars are hoisted). Plus, general best practice is to declare all your variables up front, and not doing so is a tradeoff to consider.
  2. The program code can mess around with your variables, intentionally or accidentally, which complicates things and is undesirable unless you need that (sometimes you do).

The second example eliminates these problems by having a private scope for that area of the file. You can still use variables that are scoped to the entire file, but in cases where you don't need that, you can have variables that are easier to read and understand.

Often times we program for humans, not machines. This is an example of optimizing for the former.

Update:

In modern versions of JavaScript, const and let are probably better solutions to the problems this pattern aims to solve. With them, you can define variables in a way that will throw errors if you make the same mistakes the IIFE is trying to protect you from.

//
// ... lots of program code here ...
//

const foo = 'la' + 'la';  // some computed value

module.exports = {
    foo : foo,
};

In the above example, if the program code uses foo, it will crash with a ReferenceError, because of the Temporal Dead Zone, as opposed to receiving undefined as a var would. This is great because now you must explicitly move the declaration of foo to an earlier place in the code if its use was intentional, or otherwise fix the code.

Backgammon answered 8/9, 2015 at 18:37 Comment(2)
Great answer! That said, arguably, if your files are so large that you're gonna end up polluting variables/your variables are getting left waaay up at the top of your file, you probably want to think about decoupling into a separate fileEnugu
With ES modules and proper constants now commonplace in modern JavaScript code, I'd say the IIFE pattern has been effectively deprecated by the community. Its use may make sense in some edge cases, such as transpilers and certain recursive functions, but in general, there's no need for it these days.Backgammon
B
-1

It's a good idea. There's a lot about javascript that seems to push the envelope of how 'modern programming' is done. Maybe one of those is the modular system of CommonJS. But the evolution in javascript has always been along the lines of the classical object oriented paradigms of software development. It most likely always will be.

Javascript may do things differently, but it doesn't do different things... if that makes sense.

It's always a good idea to protect scope and objects from excessive mutations.

https://medium.com/backticks-tildes/the-s-o-l-i-d-principles-in-pictures-b34ce2f1e898

Bidle answered 18/1, 2021 at 4:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.