JSModules.io - Understanding JavaScript Modules

JavaScript-logoThe next version of JavaScript comes with a module system heavily inspired by Node.js modules.

Here’s how it works.

Creating a JavaScript Module

We’re going to build a simple asap module, which lets you schedule some work to happen as soon as possible, but asynchronously. In Node.js, you can do this via process.nextTick, but there are different approaches that work in various browsers. We’re going to build a module that works everywhere (A production-quality version of this library would be more detailed, but this will do for our purposes.) .

We start by creating a new file for this module. We’ll call it asap.js. The module provides a single primary function, which JavaScript calls the default export. You export a default value from a module by using export default.

var asap;
var isNode = typeof process !== "undefined" &&
             {}.toString.call(process) === "[object process]";

if (isNode) {
  asap = process.nextTick;
} else if (typeof setImmediate !== "undefined") {
  asap = setImmediate;
} else {
  asap = setTimeout;
}

export default asap;
export var later = isNode ? process.setImmediate : asap;

Importing Module

To import asap in another module, we use the import syntax:

import asap from "asap";

asap(function() {
  console.log("hello async world!");
});

This syntax takes the default function exported from asap and stores it in the variable asap, which we can then use to call it.

Named exports

Sometimes modules need multiple exports, which their consumers can refer to individually by name.

For example, jQuery has a single primary export, (the jQuery function), and several additional named exports (ajaxgetJSONanimate, etc.). In Node.js, the mkdirp module has a single default export, which recursively creates a directory, and a named export called sync, which does the same thing, but synchronously.

In our case, in addition to the default export, the asap module might wish to provide a later function, which schedules a function to run later, after other network or UI work has a chance to occur.

Our module looks the same as before, except we add a new export declaration.

var asap;
var isNode = typeof process !== "undefined" &&
             {}.toString.call(process) === "[object process]";

if (isNode) {
  asap = process.nextTick;
} else if (typeof setImmediate !== "undefined") {
  asap = setImmediate;
} else {
  asap = setTimeout;
}

export default asap;
export var later = isNode ? process.setImmediate : asap;

Named imports

Now that we’ve exported later, we can import it in another module.

import { later } from "asap";

later(function() {
  console.log("Running after other network events");
});

For the curious, you can import both the default export and a number of named exports in the same import:

import asap, { later } from "asap";

And that’s all there is to it!

Conveniences

Renaming named imports

Sometimes when importing a named export, you want to give it its own local name.

import { unlink as rm } from "fs";

rm(filename, function(err) { /* check errors */ });

Importing into a namespace

It can be convenient to import all of a module’s named exports into a single local namespace.

import * as fs from "fs";

fs.unlink(filename, function(err) { /* check errors */ });

Shorter named exports

You can make any declaration in JavaScript (like var or function) a named export by prefixing it with export.

// exports this function as "requestAnimationFrame"
export function requestAnimationFrame() {
  // cross-browser requestAnimationFrame
}

// exports document.location as "location"
export var location = document.location; 

This also works with new declarations, like class and let.

// exports this class as "File"
export class File() { /* implementation */ }

// exports "0.6.3" as "VERSION"
export let VERSION = "0.6.3";

These names are also available in the module’s local scope, so you can use them in other functions.

Grouping named exports

You can export any number of local variables together.
export { getJSON, postJSON, animate };

function getJSON() {
  // implementation
}

function postJSON() {
  // implementation
}

function animate() {
  // implementation
}
You can put the grouped export declaration anywhere in the file, so you can define your imports and exports next to each other at the top of your modules if you wish.

Features

JavaScript modules have a number of nice features that make important use cases smooth and seamless, including refactoring and tooling.

  • Support late bound cycles between modules for both the default export and named exports. It just works.
  • Separate the names that exist on the default export (and their prototype chains) and other named exports, avoiding collisions.
  • Make it easy to determine exactly what you are importing by just looking at the syntax. That improves error messages, but also makes it easier to build tools like browserify and JSHint that work reliably without caveats.

What are Javascript Modules?

A Javascript Module is a set of Javascript codes that encloses implementation details and reveals a public API in order to be easily loaded and used by other pieces of Javascript.

Javascript modules also save developers the efforts and time needed for writing the same code over and over again. They save the code for future use.

Different Types of JS Modules Systems

CommonJS

CommonJS module specification is the standard used in Node.js for working with modules. It defines the exact manner Javascript modules should be organized and used.

CommonJS modules allow you to use Javascript modules within the current scope without having to pollute the global scope. This, therefore, keeps the code organized & reduces the chance of naming collisions.

Async Module Definition (AMD)

Asynchronous module definition (AMD) is a specification for Javascript which defines an Application programming interface (API) that defines code module and their dependencies and loads them asynchronously if desired.

On the plus side, AMD implementation improves website performance since it loads smaller Javascript files, and then only when they are needed. It also helps in achieving fewer page errors as it allows developers to define certain codes that must load before a module becomes executed so that the module will not try using code which is currently not available.

In addition, AMD implementations allow developers to encapsulate code in smaller, more logically-organized files, just like Java, and other programming languages.

The AMD specification is implemented by Dojo Toolkit, RequireJS, and ScriptManJS.

Universal Module Definition (UMD)

Universal Module Definition is a Combination of CommonJs + AMD. It can be used for both AMD/CommonJs environments

UMD primarily creates a way of using either AMD or CommonJs, while it also supports the global variable definition. Due to this, UMD modules are capable of working everywhere, be it in the client, on the server or elsewhere.

The UMD pattern usually strives to be compatible with most of the popular script loaders such as RequireJS, and the likes.

ECMAScript Harmony (ES6)

ECMAScript Harmony is a standardized scripting language implemented by Javascript, ActionScript, and some other programming languages.

Part of the reasons to choose Es6 is that it is simple to use, it is compatible with a browser and non-browser environment, it is very fast in compiling, it operates smoothly with existing Javascript modules like AMD, CommonJs, and Node.js, etc.