JavaScript Module Systems

Even an old topic as it is today, it is still worth revisiting…

Photo by Fabian Blank on Unsplash

If you use JavaScript across the fullstack, you must had one time, if not all the time, run into some errors relating to the module system, either it’s CommonJS or ESM or even AMD. This blog gives you a run down of all the different module systems. Note that discussion of the history of module system is not in focus today.

This is the first and foremost system before anything else. It leverages that JavaScript scopes work at the function level so the pattern uses function to encapsulate private objects.

In the example below, public objects are exposed in the return statement. All other declarations are private.

// quote Addy Osmani’s JavaScript Design Patternsvar myRevealingModule = (function () {
var privateVar = "Ben Cherry",
publicVar = "Hey there!";
function privateFunction() {
console.log( "Name:" + privateVar );
}
function publicSetName( strName ) {
privateVar = strName;
}
function publicGetName() {
privateFunction();
}
// Reveal public pointers to
// private functions and properties
return {
setName: publicSetName,
greeting: publicVar,
getName: publicGetName
};
})();
myRevealingModule.setName( "Paul Kinlan" );

This is one of the first systems created. It uses keywords require and exports. It is developed for server development and these are synchronous.ie., the files are loaded one by one in order inside the file.

//------ fileA.js ------
var a = require('fileB');
a.func(2)
//------ fileB.js ------
exports.func = (r) => r * r;

For NodeJS, it is very much similar except that the exports object. NodeJS uses module.exports as the object while CommonJS uses just the exports variable itself.

//------ fileA.js ------
var a = require('fileB');
a.func(2)
//------ fileB.js ------
func = (r) => r * r;
module.exports = func

In both CommonJS and NodeJS, require is a function to import symbols from another module to the current one. exports is a special object that exposes anything inside as a public element.

CommonJS/NodeJS is unfortunately not suitable for browser environment, which requires a loader or transpiler. Luckily, there is webpack and browserify available to parse CommonJS.

AMD was splitted from CommonJS to address asynchronous module loading. This is the system used by RequireJS and that is working client-side.

define(['module1', ',module2'], function(module1, module2) {
return function () {};
});

ES6 makes it possible to import and export modules in both synchronous and asynchronous way.

//------ lib.js ------
export const sqrt = Math.sqrt;
export function square(x) {
return x * x;
}
export function diag(x, y) {
return sqrt(square(x) + square(y));
}
//------ main.js ------
import { square, diag } from 'lib';
console.log(square(11)); // 121
console.log(diag(4, 3)); // 5

Here the export statement makes the exported objects public, which makes static analyzing possible by building a dependency graph while bundling the file. This makes it possible for build tools like Webpack to optimise the code and perform tricks like tree-shaking.

However, as ESM is not supported universally in all browsers so transpiling is required through tools like Babel. Also, dynamically imported modules (dynamic import()) is not yet fully supported.

That’s so much of it!

Happy Reading!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store