Javascript - CJS and ES Modules
A quick guide to CJS and ES Modular systems in JS.
Modules in Javascript
A modular design pattern is a great way to organise code into reusable blocks of logic. If done right, it can keep code clean and easier to manage especially in bigger projects that many people could be working on.
For a long time this was only available by using libraries such as RequireJS, otherwise it meant that if you were wanting to write some sort of program in JavaScript you would have to do it in one file. As you can imagine, this is not a great experience if you are putting together a more complex peice of software.
Having a way to store your code in more than one file and being able to reference it only where it needs to be used, reduces the cognitive overload that comes with trying to manage one exceptionally large file.
In the rest of this article I will be looking at CommonJS and EcmaScript Modules. However, there are a few more that you might want to have a look at as well AMD, UMD and IIFE.
CommonJS | CJS
The CommonJS modular system was brought to the web by Kevin Dangoor, an enginneer working at Mozilla during that time. This ended up becoming the standard until 2015 when the ES Module system came out.
However, ESM was not fully supported across all major browsers until around 2020 so uptake of the system was slow.
In CommonJS the require method is used to bring in modules. Modules can be “required” any where in the file.
const module = require('myModule');The syntax to export a module using CommonJS looks like:
module.exports = {
... // your code to export ...};
One thing to keep in mind when using CJS is that it loads modules syncronously. Any code that comes after a require statement will only proceed to run once the module has been loaded.
This is an important point to note, especially if you are bringing in large modules.
If you are looking to enhance the performance of your application then make sure you require only what you need from a module rather than loading the whole thing. Destructuring is a great way to help manage this.
const { usefulMethod1, usefulMethod2 } = require('myModule');The great thing about this system is that a lot of packages and libraries have been written using it and most frameworks and packages these days will also supply a CJS build in their dists so compatibility is still strong and probably will be for a while. However, if you are starting a project it would be advisable to use ESM. If that project is a library or some sort of package that is to be consumed in some way, it would also be a good idea to provide a CJS version in the build.
EcmaScript Modules | ESM
The ES Module system has been around for while now and is seeing more of an uptake out there due to modern browsers adding native support for it. As this is the case, ESM has now replaced CJS as the standard module system.
Get Dean Thomson’s stories in your inbox
Join Medium for free to get updates from this writer.
We have been able to use ESM in Node since around v8 or so with the use of the --experimental-modules flag. I feel that this would have mainly been used by people who wanted to try it out and those early adopters out there.
To import a module into a file you would use the following syntax:
import module from 'myModule';// and if the module does not have a default exportimport * as module from 'myModule';
To export an ESM module:
const myModule = { ... };export default myModule;// or, quite simplyexport default { ... };
The ESM syntax can, arguably, provide better readability as you keep all of your imports at the top of the file and not potentially “peppered” through the file. This is provided that your coding style allows for that type of behaviour.
Another advantage to ES Modules is that they load asyncronously. This means that the each file will not block the loading of the other while importing. However, it is good to remember that while the loading is asyncronous the execution of the code will only occur once all imports have been loaded. This ensures any dependencies required by the imports are available upon execution.
Enabling ESM in a project is as easy as setting the type parameter in the package.json file to module . Any javascript file you create in your project will be treated as an ES Module. If you happen to still need the CJS syntax you can rename the extension of these files from .js to .cjs . A reason you might need to do this will be if you are moving an existing project over to the ESM syntax and there is some functionality that you either are not able to update or it is an old lib you rely on for some reason or you are just strapped for time.
If you do not want to (or are unable) to add the type to the package.json but still want to use ESM you can rename your file extension to .mjs to achieve the same result.
Conclusion
In this article I touched on ESM and CJS module formats. However, there are a few more out there in the wild. AMD or the Asyncronous Module Definition is, as its name suggests, a module format that allows for asyncronous loading of modules and dependencies. Another is the Universal Module Definition or UMD, which provides a way to support both, CJS and AMD formats.
At the end of the day choosing which to support is up to you and how you want your project to be consumed.
That, being said, ESM is the standard and is being adopted by projects of all sizes out there. Providing, at the very least, a CJS version of your project together with ESM would be recommended for the time being.
Sources
For more detailed information have a look at some of the links below:
https://nodejs.org/api
https://dev.to/iggredible/what-the-heck-are-cjs-amd-umd-and-esm-ikm
https://blog.logrocket.com/es-modules-in-node-today/#commonjsmodulesystem
https://medium.com/@lisa.berteau.smith/commonjs-and-the-history-of-javascript-modularity-63d8518f103e
https://reflectoring.io/nodejs-modules-imports/
https://addyosmani.com/writing-modular-js/






