;

How to export a JavaScript module

Few days ago I wrote a tweet that surprised many developers not fully aware on how CommonJS modules work. The same tweet also gave birth to some usual discussion about anti-patterns, confusion, what why and how …

When people talk about isomorphic, or better “universal JS”, they usually refer to code that runs on both node.js and the browser. There are at least 7 others major engines in the server-side and micro-controller scenario, and these might or might not have a CommonJS built-in modules system:

Ultimately, we have old and modern browsers, where there’s no CommonJS support but, in most updated cases, an explicit export based syntax which is not compatible with CommonJS.

Have you ever wondered what all these JavaScript engines have in common since about ever?

The top level this context

If an engine doesn’t implement CommonJS module and exports, the only way to attach a property, a method, or an utility, is using this.





method = function () {  };



this.method = function () {  };

If you think using this inside a module is confusing, think again ‘cause in JavaScript there shouldn’t ever be confusion about the execution context. In a module system there are only 3 possible cases for the this reference:

The good news about ES2015 modules, is that we could bring CommonJS in there with ease, finally making our modules truly portable cross environments. The counter bad news is that it’s not possible, without a transpiler, to bring ES2015 modules to CommonJS based environments.

As of today, CommonJS is the most de-facto standard when it comes to modules and modules related tools.

As explained in the Duktape documentation, whenever we require a module its content will be executed within a closure in the following way:

var exports = {};
var module = {
  exports: exports, 
  id: 'package/lib'
};

F.call(exports,     
       require,     
       exports,     
       module);     

It’s that simple: whenever we need to export a property, a method, an utility, or a class, we can either use this or simply the exports object. In some developer opinion, using exports somehow implicitly flags the module as CommonJS compatible, and I kinda agree the explicit intent works quite well and is very welcome.



exports.PI = 3.14;
exports.abs = function (n) {
  return (n | 0) * (n <  0 ? -1 : 1);
};
exports.max = function () {
  
};

The only reason we might need to access the exports property trough the module reference is actually to fully overwrite the export, allowing us to export directly a function or any other object.


module.exports = {
  PI: 3.14,
  abs: function (n) {
    return (n | 0) * (n <  0 ? -1 : 1);
  },
  max: function () {
    
  }
};

If your habit is to pass through the module in order to export anything like in module.exports.foo = "bar", you might reconsider your verbosity and do directly exports.foo = "bar" instead, which is the exact equivalent action.

Bear in mind, if your habit is to overwrite the default exports object you still need to do that through the module.exports = {...}; way ‘cause reassigning the exports = {} won’t actually work.

There is another de-facto standard in most common CLI oriented JS environments, the load(fileName) function. Not so difficult to imagine, the load function simply find synchronously a file and execute its content in a top level global context.

It’s basically the equivalent of a <script src="module.js"></script> on the web, there’s no guard about global scope/context pollution, so whatever is there, will be somehow “exported”.




var module = "module.js";


this.method = function () {
  return 'Hi, I am the load("' + module + '") result';
};





load('module.js');

module;   
method(); 

Accordingly, if we’d like to use load in order to bring in modules, we might consider writing them in the following way:



(function (exports) {'use strict';

  
  var module = "module.js";

  
  exports.method = function () {
    return 'Hi, I am the load("' + module + '") result';
  };
}(this));

It is not by accident that I’ve called the inline invoked function parameter exports because that module will be automatically compatible with any CommonJS based environment too.

When developers like JS simplicity but are not fully familiar with the JavaScript world, things like GJS happen. Please don’t get me wrong, I’ve written my ArchLinux and GNOME based operating system updater via GJS and I think it’s an amazing project, however, the fact the API uses Python naming convention, instead of a JS one, and the fact you apparently need to throw errors in order to know in which file you are running, make this environment quite hostile for regular client/server JS developers.

On top of that, there is a NON-CommonJS like module system which basically creates a Proxy object per each imported module so that you don’t accidentally pollute the global context, you actually pollute the module itself with undesired exports.

Every demo I’ve seen, even directly from the source, apparently doesn’t care about undesired exports per module, and there’s not a single word about that in the related documentation. I’m kinda new to GJS so I don’t feel that confident about filing bugs, but the TL;DR version of its weird behavior is the following.




var module = "module.js";


this.method = function () {
  return 'Hi, I am the imports.' + module + ' result';
};



imports.searchPath.push('.');

print(imports.module.method());

print(imports.module.module);

The problem is that every GJS module imports Gtk and other modules, so that every module accidentally exports every single constant or variable defined within them. While this is practically not such huge issue, unless you are creating a module introspection library that will be inevitably full of false-positives, I think is a very dirty approach to modules in general.

Playing around with GJS code and modules, I’ve found this approach way cleaner and superior than every module I’ve read so far.


const {
  methodA,
  propertyB,
  methodC
} = (function () {

  
  var Gtk = imports.gi.Gtk;

  
  return {
    methodA: function () { Gtk.init(null, 0); },
    propertyB: 12345,
    methodC: function () { Gtk.main(); }
  };
}());

No accidental module pollution, a clean way to group the export, a closure to import or do whatever we need in there. Anyway, this is a very specific GJS issue I hope they will solve or put a word in the documentation soon.

Alternatively, the same approach used with Nashorn and others would also work quite well, granting compatibility with CommonJS.


(function (exports) {'use strict';

  
  var module = "module.js";

  
  exports.method = function () {
    return 'Hi, I am the load("' + module + '") result';
  };
}(this));

As we can see, once again using this to export wins in terms of portability.

Having to deal with all these different ways to import/export modules is a real mess.

Having an incompatible ES2015 specification that doesn’t work in engines already based on CommonJS surely doesn’t help neither.

I personally wish “ES.future“ will bring in CommonJS too, beside current export syntax, so that every environment can finally converge without breaking current modules ecosystem.

Meanwhile, we can at least normalize the CommonJS approach pretty much everywhere. How? Using this to export at least one and one only utility: the require one! Please note this is a simplified script for this post purpose only.


(function (context) {'use strict';

  
  
  if ('require' in context) return;

  var
    
    cache = Object.create(null),

    
    executeAndCache = function (file) {
      var
        
        
        exports = {},
        
        
        
        module = {exports: exports, id: file}
      ;
      
      
      Function(     
        'require',  
        'exports',  
        'module',   
        read(file)  
                    
                    
                    
                    
      ).call(
        exports,    
        require,    
        exports,    
        module      
      );
      
      
      
      
      
      
      
      
      cache[file] = module.exports;
      
      
      return cache[file];
    },

    
    read =  (context.java &&      
            function (file) {
              return new java.lang.String(
                java.nio.file.Files.readAllBytes(
                  java.nio.file.Paths.get(file)
                )
              );
            }) ||
            context.readFile ||   
            context.read     ||   
            (typeof imports === 'object' &&
            function(file){       
              return imports.gi.GLib.file_get_contents(file)[1];
            }) ||
            function (file) {     
              var xhr = new XMLHttpRequest;
              xhr.open('GET', file, false);
              xhr.send(null);
              return xhr.responseText;
            }

    
    
  ;

  
  context.require = require;

  function require(file) {
    if (file.slice(-3) !== '.js') file += '.js';
    
    
    
    
    return cache[file] || executeAndCache(file);
  }

  
  
  

}(this)); 
          

All we need to do in non-CommonJS environments is to import or load such file at the very beginning.


this.method = function () {
  return 'Hi, I am require("' + module.id + '").method()';
};




load('require.js');
require('./module').method();





imports.searchPath.push('.');
var require = imports.require.require;

print(require('./module').method());





require('./module').method()

Good, we can now use any npm module that is based on JS and not node.js specific tasks!

While we all dream about one/universal JS, there are many differences between a trusted environment, the server, and a non trusted one, which is usually the browser. In the latter case, the network status is also a major concern, and blocking while loading, as opposite of bundling all modules, doesn’t seem like a good idea, unless done in an intra-net environment or to quickly prototype some project.

Accordingly, the only reasonable way to load modules in a CommonJS way on browsers would be running main applications within an async function or, generally speaking, a generator.


var cache = Object.create(null);
function loadInCache(file) {
  return (cache[file] = new Promise(function (res, rej) {
    var xhr = new XMLHttpRequest;
    xhr.open('GET', file, true);
    xhr.send(null);
    xhr.onerror = rej;
    xhr.onload = function () {
      var
        exports = {},
        module = {exports: exports, id: file}
      ;
      try {
        Function(
          'require', 'exports', 'module',
          xhr.responseText
        ).call(
          exports,
          require, exports, module
        );
        res(module.exports);
      } catch(o_O) { rej(o_O); }
    };
  }));
}


export function require(file) {
  if (file.slice(-3) !== '.js') file += '.js';
  return cache[file] || loadInCache(file);
};


export default require;

At this point, everything we need to import in a non blocking way should be in place. The only missing bit is a generator handler like the classic async one, as shown and described in this page.


import * from 'async';

import * from 'require';

async(function *() {
  var module = yield require('./module');
  alert(module.method());
});




async(function *() {
  let [
    moduleA,
    moduleB,
    moduleC
  ] = yield Promise.all([
    require('./moduleA'),
    require('./moduleB'),
    require('./moduleC')
  ]);
});

We have now the ability to use npm modules even through most updated browsers and in a non blocking way … cool uh?

As we’ve seen, the most de-facto cross environment way to export a library is through the this reference, but having a common way to define modules through an exports reference in every environment is definitively a better option and we should probably go for it.

The npm registry in this case becomes virtually the source for any sort of JavaScript based project, and we could start publishing even Desktop based Applications and UIs via GJS and Gtk3 based JavaScript modules … and that would be absolutely marvelous!