Skip to main content
illustration of random abstract shapes

JavaScript Module Systems Unraveled: Which One to Use and When

Hello there, fellow coders! Today, we’re venturing into the fascinating world of JavaScript module systems. If you’ve ever found yourself scratching your head when faced with terms like ESM, CommonJS, AMD, or UMD, or wondered about the purpose of .mjs and .cjs file extensions, then this one’s for you. We’ll break down the nuts and bolts of these module systems, and by the end of this article, you’ll have a solid understanding of each one’s benefits and when to use them. Let’s dive in.

So, What Are JavaScript Modules Anyway?

Before we delve into the different types of module systems, let’s cover what modules actually are. A module is essentially a discrete piece of code, stored in a file, that encapsulates related functions, variables, and objects. The idea is to separate out code into manageable and reusable chunks. This way, each module serves a specific function and can be imported into your code as needed.

ESM: ECMAScript Modules

ESM, or ECMAScript Modules, is the latest standard for working with modules in JavaScript. As of ES6 (ES2015), JavaScript natively supports ESM. In a nutshell, ESM introduces an official, standardized module syntax accepted by all modern browsers and JavaScript environments.

Here’s an example of ESM in action:

// lib.js
export function add(a, b) {
  return a + b;
}

// main.js
import { add } from "./lib.js";

console.log(add(2, 2)); // prints 4

Notice the export and import keywords? That’s ESM for you.

CommonJS

CommonJS is the module system that Node.js has been using long before ESM came into the picture. It’s synchronous by nature, meaning it waits for each operation to complete before moving on to the next, which works well on a server but less so in a browser environment. Here’s how it looks:

// lib.js
exports.add = function (a, b) {
  return a + b;
};

// main.js
var lib = require("./lib");

console.log(lib.add(2, 2)); // prints 4

In CommonJS, we use require to import modules and exports to export them.

AMD: Asynchronous Module Definition

As the name suggests, AMD, or Asynchronous Module Definition, supports asynchronous module loading, making it suitable for browser environments. However, it’s a bit more verbose than ESM or CommonJS.

// lib.js
define(function () {
  return {
    add: function (a, b) {
      return a + b;
    },
  };
});

// main.js
require(["lib"], function (lib) {
  console.log(lib.add(2, 2)); // prints 4
});

AMD uses define to define modules and require to load them.

UMD: Universal Module Definition

UMD, or Universal Module Definition, is a compatibility layer that allows JavaScript modules to run in both client-side and server-side environments. It’s a blend of CommonJS and AMD.

// lib.js
(function (root, factory) {
  if (typeof define === "function" && define.amd) {
    define([], factory);
  } else if (typeof exports === "object") {
    module.exports = factory();
  } else {
    root.returnExports = factory();
  }
})(this, function () {
  return {
    add: function (a, b) {
      return a + b;
    },
  };
});

UMD uses a combination of define and exports to handle modules.

.mjs and .cjs Extensions

You might have noticed .mjs and .cjs file extensions while browsing JavaScript projects. These are specifically used to denote which module system the file uses. .mjs stands for “module JavaScript” and is used with ESM, while .cjs stands for “CommonJS” and is used with the CommonJS module system.

Browser vs. Node.js: Module Systems

The environment in which your code runs greatly influences the module system you should use.

In the browser, ESM is the way to go, thanks to its native support and asynchronous nature, which enables efficient code loading in a browser environment.

In contrast, Node.js traditionally used CommonJS due to its simplicity and synchronous nature, which aligns with server-side operations. However, as of Node.js v14, there’s stable support for ESM, meaning you can use ESM syntax in Node.js as well.

Wrapping Up

And there you have it, folks—a whirlwind tour of JavaScript’s various module systems! To choose the right one for your project, consider the environment and the specific needs of your application. And remember, no one module system is superior to all others—they each have their place and serve their purpose.

Keep exploring, keep coding, and as always, stay curious. Till next time!

Stay in touch

Don't miss out on new posts or project updates. Hit me up on X (Twitter) for updates, queries, or some good ol' tech talk.

Follow @zkMake
Zubin Khavarian's Profile Photo Written by Zubin Khavarian