JS module system

When we say an application is modular, we generally mean it's composed of a set of highly decoupled, distinct pieces of functionality stored in modules.

However, unlike other traditional programming languages, for a long time, JavaScript didn't provide developers with the means to import such modules of code in a clean, organized manner.

But, this didn't stop the community and they created impressive work-arounds. Following are the most used module loading patterns used by the developers:

  • Scripts are code fragments that browsers run in global scope. They are precursors of modules.
  • CommonJS modules are a module format that is mainly used on servers (e.g., via Node.js).
  • AMD is a module format that is mainly used in browsers.
  • ECMAScript modules are JavaScript’s built-in module format. It supersedes all previous formats.

CommonJS modules#

CommonJS is a project that aims to define a series of specifications to help in the development of server-side JavaScript applications. The CommonJS module proposal specifies a simple API for declaring modules server-side and unlike AMD attempts to cover a broader set of concerns such as IO, filesystem, promises and more.

There are essentially two elements to interact with the module system: require and exports

  • require is a function that can be used to import symbols from another module to the current scope
  • exports is a special object: anything put in it will get exported
var lib = require('package/lib');

// some behaviour for our module
function foo(){
    lib.log('hello world!');
}

// export (expose) foo to other modules
exports.foo = foo;

Implementations

Since it was made primarily for the server, it is well implemented in NodeJs (Node.js modules have a few features that go beyond CommonJS). For the client there are currently two popular options: webpack and browserify which help bundle these modules.

Asynchronous Module Definition (AMD)#

AMD was born as CommonJS wasn’t suited for the browsers early on. The main difference between AMD and CommonJS lies in its support for asynchronous module loading.

//Calling define with a dependency array and a factory function
define(['dep1', 'dep2'], function (dep1, dep2) {

    //Define the module value by returning a value.
    return function () {};
});

// Or:
define(function (require) {
    var dep1 = require('dep1'),
    dep2 = require('dep2');

    return function () {};
});

Implementations

Currently the most popular implementations of AMD are require.js and Dojo.

ECMAScript 6 modules#

Fortunately, the ECMA team behind the standardization of JavaScript decided to standardize modules. We finally have a standard to define modules in Javascript which compatible with both synchronous and asynchronous modes of operation.

  • On lines of require and define, ES6 modules have an import directive which can be used to bring in modules into namespace.
  • export helps to explicitly make elements public. A module can have 2 types of exports
    • named exports which can be several per module
    • default export primary export of module

Importing examples

// Default exports and named exports
import theDefault, { named1, named2 } from 'src/mylib';
import theDefault from 'src/mylib';
import { named1, named2 } from 'src/mylib';

// Renaming: import named1 as myNamed1
import { named1 as myNamed1, named2 } from 'src/mylib';

// Importing the module as an object
// (with one property per named export)
import * as mylib from 'src/mylib';

// Only load the module, don’t import anything
import 'src/mylib';

Exporting examples

// export inline
export var myVar1 = ...;
export let myVar2 = ...;
export const MY_CONST = ...;

export default 123;

// define everyting and export at the last
export { MY_CONST, myFunc };

// export things under different names:
export { MY_CONST as THE_CONST, myFunc as theFunc };

ReExporting examples Re-exporting means adding another module’s exports to those of the current module.

// add all of the other module’s exports:
export * from 'src/other_module';

// be more selective
export { foo, bar } from 'src/other_module';

// Export other_module’s foo as myFoo
export { foo as myFoo, bar } from 'src/other_module';

// Re-export other_module's default export
export { default } from 'src/other_module';

The above exampled are taken from the wonderful 2ality post

Module loading techniques summary#

Runs onLoadedFilename ext.
Scriptbrowsersasync.js
CommonJS moduleserverssync.js .cjs
AMD modulebrowsersasync.js
ECMAScript modulebrowsers and serversasync.js .mjs

Further References#


Loading comments...