A front end developer's guide to CommonJS modules

Orde Saunders' avatarUpdated: Published: by Orde Saunders

CommonJS modules are the standard way of writing modules for node so if you're coming from that background they won't need explaining. However, when you are coming from a front end developer's background where you are used to writing code that runs in the browser I found they need a bit more explanation.

Background

For some time I have been using the basic JavaScript module pattern to organise my code:

window.DECADE_CITY = (function(module) {

  // Module code

  return module;
}(window.DECADE_CITY || {}));

Using an Immediately Invoked Function Expression (IFFE) provides a safe wrapper round code and only introduces one variable to the global scope. This pattern, and variations of it, have been in use by front end JavaScript developers for many years so most will be familiar with it.

Whilst this is fine for small to medium projects it has limits on how far it will scale, particularly when you want to re-use code between projects and need to resolve dependencies.

Browserify has a lot of potential for larger projects with its ability to use npm and Git repositories to manage dependences but I found that, coming from a front end perspective, the documentation suffered from the "how to draw an owl" problem.

How to draw an owl: Lessons in becoming an expert in a couple of easy steps

One of the best introductory guides I found to Browserify is &yet's npm, Browserify & Modules which gives a good run down of what you need to know to get started. However, there was one sentence in particular that made me realise what I had been missing in the "how to draw an owl" explanations:

Npm and commonjs modules are how almost all backend node.js developers write and manage JavaScript code.

Whilst I'm familiar with npm, not being a back end node developer I'm not familiar with CommonJS modules. I can only assume that, as they are how back end developers work, none of the explanations I had encountered had explained CommonJS in a way that I could easily relate to as a front end developer used to the traditional module pattern.

Having familiarised myself with CommonJS modules they aren't inherently difficult but they are conceptually different to the traditional module pattern that I'm used to.

CommonJS modules

The most important thing to grasp about CommonJS modules is that the code you are writing is not what will turn up in the browser. They will go through an additional build step before they are ready for the browser.

The next thing that you need to know is that a CommonJS module is a single file that contains JavaScript. With systems based on the traditional module pattern you can have more than one module contained within a single file (even though you probably wouldn't). However, with CommonJS a file on the file system is a module.

Module scope

CommonJS introduces a new scope: module scope. Each module (and thus file) has its own scope. Coming from a front end perspective it can look like each module is putting code into the global scope but remember there is a build step before the code gets to the browser. If we're using Browserify to do the compilation it will wrap each module (file) in a closure which will fully encapsulate the module.

Whilst we don't have a global scope per se, there are two items that are implicitly present and these are what makes the module system work: module and require1.

Whilst these implicit items might seem like magic it's not too different from a browser environment. Even though we don't normally think about it, in the browser we are used to items being implicitly present, window and document being the most obvious.

module

As the code in the module will be wrapped in a closure we need a way to expose code to make our module useful. This is achieved by the module object. It contains a number of properties about the module but the only one you need to worry about for now is the exports property. Whatever you assign to module.exports is what will be exposed when your module is included.

Shown below is an example module, this is the entire contents of the file:

Filename: my-module.js

function plus(foo, bar) {
  return foo + bar;
}

module.exports = plus;

In this case we are attaching a function but you could attach variables, objects or constructors - whatever you want to expose as the external interface to this module. We'll see how these are accessed next.

require

Remember that each file is a module and it has its own scope. As a result we need a method of introducing code from other modules into that scope. This is where the built in require function comes in, it's the counterpart to module.exports.

require() takes one argument - a string representing the ID of a module2 - and it returns the module.exports of that module. By assigning it to a variable you can now access the external interface of that module.

Filename: my-other-module.js

var baz = require('my-module');

// The `module.exports` of 'my-module' is now assigned to `baz`:
baz(1, 2); // => 3.

// Nothing from the 'my-module' scope is available:
plus(1, 2); // => ReferenceError: plus is not defined

In this case we've assigned plus to the export of my-module and then used require() to reassign it to the variable baz within the module scope of my-other-module.

In reality you would assign it to a better named variable than baz but for the purposes of this illustration I wanted to use distinct names show how module.exports and require() interact to contain the module scope of each file.

Summary

The key points to remember when dealing with CommonJS modules are:

  • Each file is a module, each module is a file.
  • Each module has its own scope.
  • A module exposes its code to other modules via module.exports.
  • Code exposed by other modules is introduced into the module's scope with require().

Whilst this is an oversimplification, I found it helped me to think of modules as being wrapped with the following IIFE which is much closer to the module pattern that is traditionally used in front end development:

(function(require, module) {

  // Module code

  return module;
}(require, { 'exports': {} }));

Footnotes

  1. For historical reasons there is also exports but that just confuses matters even further so I'm skipping over it.^
  2. How you reference the ID of the module you want to require will depend on how your project is structured so I'm not going to cover it here.^