Dawid Kedzierski's Newsletter

Subscribe
Archives
October 23, 2022

What’s going on with those CJS, AMD, UMD and ESM?

Nowadays, it’s hard to imagine a world without JavaScript being modern and multi-platform language, but some more experienced developers surely remember time of jQuery domination, of ES5 standard or even earlier, and the language designated for “just websites”. Starting from 2009 and Node.js, later AngularJS (2010), React (2013), Angular (2016), and many more have been introduced, making JavaScript extremely popular on almost all the platforms.

Together with JavaScript popularity, also size and complexity of web applications increased drastically. Unfortunately, JavaScript wasn’t providing these days any built-in module functionality, and so community had to figure out the solution. Thanks to that, CommonJS standard has been introduced, as the primary way of packaging for Node.js:

// CJS imports module synchronously, gives copy of importing object,
// requires transpiler and bundler in order to work in browsers

const circle = require('./circle.js');
console.log(`The area of a circle of radius 4 is ${circle.area(4)}`);

// https://nodejs.org/docs/latest/api/modules.html#modules-commonjs-modules
// circle.js file

const { PI } = Math;

exports.area = (r) => PI * r ** 2;

// https://nodejs.org/docs/latest/api/modules.html#modules-commonjs-modules

Unfortunately, designed for Node.js, it didn’t work well for web browsers, mostly because of loading modules synchronously (and requiring transpilers and bundlers). Thus, AMD format has been introduced:

// AMD imports modules asynchronously

define((require) => {
    const circle = require('./circle');

    console.log(`The area of a circle of radius 4 is ${circle.area(4)}`);
});
// circle.js file

define(() => {
    return {
        area: (r) => {
            return PI * r ** 2;
        }
    };
});

It resolved the issue by loading modules asynchronously, also introducing better debugging experience, cross-domain usage improvements, or ability to declare multiple modules within the same file.

Then, UMD format came out. It stands for Universal Module Definition, which is pretty much self-explanatory, isn’t it? To simplify, it can be used by both loaders - CommonJS and AMD (and others), on the servers as well as in the browsers.

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(["jquery", "underscore"], factory);
    } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') {
        module.exports = factory(require("jquery"), require("underscore"));
    } else {
        root.Requester = factory(root.$, root._);
    }
}(this, function ($, _) {
    // ...
}));

To simplify again, it’s just a wrapper which checks the available loader at runtime. Firstly, it looks for AMD, then for CommonJS, and if both aren’t available, it tries to import and export modules from and to global object.

Finally, in 2015 the ESM has been released to the JavaScript as the native module functionality.

import { area } from './circle.js';
// or import * as circle from './circle.js';

console.log(`The area of a circle of radius 4 is ${area(4)}`);
// or console.log(`The area of a circle of radius 4 is ${circle.area(4)}`);
// circle.js file

export function area(r) {
    return PI * r ** 2;
}

Since it’s built-in to the JavaScript itself, it is becoming a global standard. Most of the crucial JavaScript-based projects are being migrated, like Node.js which supports both - the original CommonJS, and the ESM - so you can easily write import { readFileSync } from 'fs'; for all the native packages. Having said that, still many popular projects have not been migrated yet, like React. As you can see here it only provides CJS (so CommonJS) and UMD formats.

Don't miss what's next. Subscribe to Dawid Kedzierski's Newsletter:
Powered by Buttondown, the easiest way to start and grow your newsletter.