Node.JS: CommonJS vs. ES Modules

Node.JS: CommonJS vs. ES Modules

When first getting into Node.JS and going through tutorials and documentation I noticed there was different import methods available. Most tutorials are a few years old at this point and most always use the require() but as I kept investigating it seemed like newer Node.JS apps were using import{}.

So what exactly is the difference?

Quick Overview

Let me start by saying what these two module systems are doing. Both systems are attempting to standardize the module ecosystem in JavaScript outside of the browser(example: servers).

CommonJS came first, it was released in 2009 and it is very widely used today. It's easy to recognize when CommonJS is being used. You will see require() for importing modules and exports for, well, exporting modules.

Here is a little example. First we require() our fs module, parse some JSON data from a local file and save it to the users object and lastly we export the users object so we can use it in another module.

const fs = require('fs');

const users = JSON.parse(fs.readFileSync(`${__dirname}/_data/users.json`, 'utf-8'));

module.exports = users;

When ECMAScript 2015 was released it included the import function and has quickly became the standard.

The same module as above but using ES modules.

import * as fs from 'fs';

export let users = JSON.parse(fs.readFileSync(`${__dirname}/_data/users.json`, 'utf-8'));

So far, not too much difference.

Differences between the two module systems

Dynamic and Static

A major difference is how the interpreter loads the modules. CommonJS is dynamic in nature while ES modules are static. The interpreter is creating a dependency graph, with CommonJS, code can start to be executed while the dependency graph is explored while ES Modules builds the dependency graph before any code can be executed. This ES feature helps with problems stemming from circular dependencies.

CommonJS lets you use require() at runtime, in other words, an if/else statement can be used to import a module depending on the conditions. It should be noted that it is possible to use dynamic imports within ES modules using promises.

Strict Mode

ES modules run in strict mode 'use strict';. This cannot be changed or turned off. Strict mode has it's benefits by getting rid of silent errors that can be difficult to debug and can make it easier for the compiler to optimize the code and essentially it will run faster.

On the other hand, CommonJS has to be explicitly told to run in strict mode. This will be a little friendlier on some of these silent errors that JavaScript allows us to get away with when not in strict mode.

Some References to the file system have changed

I bring this up because when working server side there is usually lots of local file system read and writes. I felt a little lost when switching to ES syntax.

CommonJS has some built references to help developers get locations of files and directories. In CommonJS we can use __filename and __dirname to help us build paths to local files.

ES modules has a similar reference but it does require us to build the paths a little further. We can use import.meta.url to get the current path to the module in question. From there, we can build our own function(s) to create our paths.

Using ES Modules

CommonJS is the core module system shipped with Node.JS. Unless we make some changes to our package.json file any .js file will be interpreted as a CommonJS module.

If we want to use ES modules as the standard within our project we can make a quick edit to the package.json file.

Open your project's package.json file and add one field:

"type": "module"

Now all .js files will be interpreted as ES Modules.

.cjs AND .mjs

One really cool thing I found was that if we don't make the changes in our package.json file to "type": "module" and mostly use CommonJS but we have one module that uses ES modules we just have to name our module file with a .mjs file extension. When Node sees this file extension it knows that we are using ES modules syntax and it will interpret it properly.

On the flip side, if we did make the change to our package.json file we can use the file extension .cjs and Node will know to treat this file as a CommonJS module.

Conclusion

As you can see there are some small differences between the two module systems. The standard has changed from CommonJS to ES Modules and we have explored a few ways of working with both. Most modules within the node package modules(NPM) have both options, CommonJS and ES Modules.

I still mostly use CommonJS practices but this is because it is what I am use to. As time goes on I am slowly getting use to using ES Modules and I am fully onboard with their standards. If you're trying to decide what approach to use I would suggest using the ES Modules as this is the industry norm, at least now you won't be surprised when you come across the .mjs/.cjs file extensions.