Search code examples
javascriptclosuresamdcommonjs

Trying to understand what this JS function does and what its parameters are, can someone explain?


I'm reading through some code from a library I recently downloaded to do matrix math for WebGL. However, I'm having a hard time wrapping my head around what this function does. This comes from the glMatrix.js library.

(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
  (global = global || self, factory(global.glMatrix = {}));
}(this, function (exports)

What is the factory parameter/function and where does exports come from? Can someone walk me step-by-step through what this function does?

EDIT: This is only the first part of the code which is why the opening bracket never closes.


Solution

  • This is a design pattern called UMD, it is a more advanced version of the IIFE design pattern. They both wrap your code, creating a new private scope. The main diferrence between the two is that UMD is more abstract and will also work in node.js and amd rather than just browsers.

    The first and last line you are showing in your question are basically the IIFE part (minus the end since you have cropped it). You are invoking the iife and passing the global aka window object and the factory function which you can only see the function (exports) part in your code.

    The following part checks if the environment you are using is node, amd or regular JSso that they will define the module in the each environmest requires i.e. node just needs you to set the exports object, amd needs you to use the define function and in vanilla JS you just add the object to the window or global object.

    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
    typeof define === 'function' && define.amd ? define(['exports'], factory) :
    (global = global || self, factory(global.glMatrix = {}));
    

    In node and amd you don't need to name your export since you just need to require that file i.e. const glMatrix = require("./common.js");, but in JS you need to retrieve from the global object which is why it's only one that requires to be named i.e. factory(global.glMatrix = {}). That line adds the glMatrix property to the global object (originally as an empty object) and then passes it as a parameter to your factory function, which attaches all the functions, values and classes you should be able to access from outside the scope.

    The implementation of the UMD pattern may vary from library to library. For example here is a way you could do it without passing any parameters to the IIFE

    (function() {
      var global = this;
    
      typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
        typeof define === 'function' && define.amd ? define(['exports'], factory) :
        (global = global || self, factory(global.foo = {}));
    
      function factory(exports) {
        exports.bar = function() {
          console.log("Hello World!");
        };
      }
    })();
    
    foo.bar();

    In node you can just create a new file and export whatever you want. Node will then associate those exports with the file rather than a property (which is what happens in browser JS). So for example a file named foo.js with the following contents:

    function bar() {
        console.log("Hello World!");
    }
    
    exports.bar = bar;
    

    Can be accessed from another file like so:

    const foo = require("foo.js");
    foo.bar();
    

    Or you could directly access the properties using Destructuring:

    const { bar } = require("foo.js");
    bar();