JavaScript Module Definitions

Javi Carrasco
Oct 16, 2019 · 4 min read
Image for post
Image for post
Photo by Rick Mason on Unsplash

A JavaScript module may be seen as a lego piece, it has some tubes on the bottom and some studs at the top and this simple contract makes it easy to connect some modules together. Inside the module there may be anything, and the internals of the module don’t affect the internals of other modules.

This contract that makes modules connectable to others, is called the module definition. Currently we have lots of different module definitions (IIFE, commonJS, ESM, AMD, UMD, esnext…) so we need to understand them specially if we want to publish our code.

Prehistory

When JavaScript appeared on browsers it was added with <script> tags and every function or variable was global. That was easy for smaller projects but made it difficult to reuse code created by different teams because some functions or variables could be unintentionally shared or overridden between different libraries.

IIFE

There was nothing like a standard module definition so some smart developers started to use IIFE (Immediately Invoked Function Expressions)

(function () {})();

Because the scope of a function body is local, the variables and functions inside that function were not global anymore.

When the code had to be available to other parts of the application we were still forced to add at least one global variable. Something like :

window.myLibrary = {…} 

But at least it was intentional and more controlled.

CommonJS

In 2009 some developers started to use JS in the server, and a real module system was needed. So CJS modules were born

var path = require(‘path’);module.exports = …module.exports.NAMED = …

A .js file is a module by default, so no functions or variables are global. They can be exposed to other modules with module.exports and modules can use other modules with require().

CommonJS is still the standard module definition for node applications, but browsers don’t support it.

To use CJS on the browser the bundler concept appeared. Browserify was a command line tool that analysed the require() in .js files and concatenated all the dependent modules into a single .js file.

That helped web developers to use modules. The bundled js had additional advantages: for example it was faster because less files had to be downloaded in the browser.

AMD

Asynchronous Module Definition was a different approach

define(name, [dependencies], function(dependencies) {})

A module is defined with a call to a define function were the module specifies its dependencies. When the module loader had all the dependencies loaded then it started executing the module.

RequireJS was the loader that implemented AMD. It was a browser library and had some advantages like it didn’t require a CLI, cause everything was done in the browser.

UMD

This explosion of modules caused additional work for library developers, so UMD was invented. Universal Module Definition is basically an if statement that makes the module available as a CJS module, as an AMD module or as an IIFE global depending on who is using the library.

ESM

But none of these module definitions were an EcmaScript standard so in 2015 ES6 came with ESM (EcmaScript Module)

import { format } from “date-utils”;export …export default …

It has one main advantage, you can import a single function from a library, so the bundler can remove the unneeded code.

System.register

ES modules are supported by most modern browsers now. SystemJS is a module loader that runs in the browser and supports additional features like transpilation or loading not standard modules. It has its own module definition System.register and a set of additional related tools, including a bundler.

System.register([dependencies], function(export) {  export(‘parseDate’, function () { … });});

Bundlers vs Module Loaders in the Browser

Currently there are 2 approaches of using modules in web applications:

  • To use a bundler (Browserify, Webpack, Rollup, Parcel…)
  • To use a browser loader (RequireJS, SystemJS or simple browser native ESM).

Modern browsers can use ESM natively but no version of IE supports it.

Modern bundlers can read most module definitions and can also generate them.

Some initiatives like unpkg or pika are pushing forward the use of ESM directly in the browser. Also a web packaging standard is being defined.

Today using a bundler produces the best performance, but browsers (together with HTTP/2) are evolving to load ESM faster and better. So it may be different in a near future.

Conclusion

The reason because there are so many module definitions is historical, now that we have ESM there should be no reason for more module definitions to appear (please).

NodeJS supports CJS natively and starting on Node 12 it also supports ESM with an experimental flag.

If you are writing a JavaScript library right now you should support:

  • CJS if the code can be run in node
  • ESM, always
  • AMD and global IIFE for supporting older applications without a bundler

JavaScript In Plain English

New JavaScript + Web Development articles every day.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store