Node.js official docs are excellent at explaining the two different types of module systems. CommonJS and ESM.
There is a very good chapter in Node.js Design Patterns, Chapter 2: The Module System (2020) that describes where CommonJS came from. It emerges from the need to provide a module system for JavaScript in browserless environments simply because one did not exist, other than the AMD and UMD initiatives, and to not rely on urls and <script> tags for resources. It was successful so became popular.
We now have ECMAScript Modules thanks to the ES2015 official proposal and it attempts to align the management of modules in both server-side and browser environments.
The main points to help you decide are:
- CommonJS uses the
requirefunction to load modules and is synchronous. As a result, when you assign a module tomodule.exportsfor example, it too must be synchronous. If you have asynchronous stages in your module then you can export an uninitialized module but usingrequirewill mean that there is a chance it won't be ready to use in the code that requires it before it's initialized. An example of exporting and importing might look like this:
// titleCase.cjs
function titleCase(str) {
if (!str){
return '';
}
return str.toLowerCase().split(' ').map(word => {
if (!word){
return word;
}else{
return word.charAt(0).toUpperCase() + word.slice(1);
}
}).join(' ');
}
module.exports = titleCase;
// app.js
const titleCase = require('./titleCase');
console.log(titleCase('the good, the bad and the ugly'));
// "The Good, The Bad And The Ugly"
- ES modules use the
importkeyword to load modules and theexportkeyword to export them. They are static, so they need to be described at the top level of every module and support loading modules asynchronously. ES modules facilitate static analysis of the dependency tree, making dead code elimination (tree shaking) more efficient. The sametitleCasefunction above using ESM would look like:
// titleCase.js
export default function titleCase(str) {
if (!str){
return '';
}
return str.toLowerCase().split(' ').map(word => {
if (!word){
return word;
}else{
return word.charAt(0).toUpperCase() + word.slice(1);
}
}).join(' ');
}
// app.js
import titleCase from './titleCase.js'; //< File extension required on import
console.log(titleCase('the good, the bad and the ugly'));
// "The Good, The Bad And The Ugly"
- ES modules run implicitly in strict mode (can't be disabled). This is a good thing in many opinions as it enforces good coding practices.
- In CommonJS you can use the helpful
__filenameand__dirname. In ES modules you need to do a workaround to get the same functionality like so:
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
- You can dynamically load modules in CommonJS with
require:
let usefulModule;
if (process.env.DEV === true) {
usefulModule = require('./utils/devmodule')
} else {
usefulModule = require('./utils/prodmodule')
}
- And in ESM by using
importas function:
let lib;
if (process.env.DEV === true) {
lib = await import('./utils/devmodule.js');
} else {
lib = await import('./utils/prodmodule.js');
}
const usefulModule = lib.usefulModule;
One thing I have noticed over the years of programming is that languages and libraries evolve. I have observed a growing shift in Node package docs giving more and more of their examples in ESM format. CommonJS is still the dominant option on npm but it tells me the ecosystem is slowly shifting away from CommonJS and towards ESM. Maybe they will run in tandem but such is the nature of evolution, one becomes dominant over the others. All of the projects I work on use the ESM approach. Good luck deciding.
Answer from jQueeny on Stack OverflowHey, I'm working on a small side project and learning node and just can't wrap my head around what's happening here. Please forgive if this has already been answered for someone else. I searched around and the answers don't quite do it for me.
My situation is this. I've installed create-react-app, graphql, and apollo client/server. I'm working on getting the server set up and through that have added a few lines to the server file that use an import statement.
The guide that I was following to set up the server however uses Common JS. This confuses me.
I don't understand why one is used over the either. I'm getting an error now when I init the server file that says "Cannot use import statement outside of a module". I understand that this means that the import statement isn't valid in my current file but I don't know why it's invalid in this file but perfectly fine in my react typescript files.
Can someone please explain to me why/when I should be using import vs. require, and why it only works in certain files and folders?
Thank you in advance!
node.js - Is there any performance difference in ES modules vs Common JS modules in NodeJS servers? - Stack Overflow
Is it better to use es6 modules with Node?
CommonJS vs AMD vs RequireJS vs ES6 Modules
ELI5: CommonJS vs AMD vs ES6
AMD is dead (was killed by Node-compatible bundlers). CommonJS is dead (was killed by Node). Node uses something that looks like CommonJS and is commonly called CommonJS but isn't actually CommonJS (there's a spec for CommonJS imports and Node knowingly violates good chunks of it).
ES6 imports aren't currently natively supported in any JS engine I know of. They can however be translated to Node-style "CommonJS" and there are bundlers that support them directly.
As for what CommonJS is: CommonJS was an effort to define common standards for the various competing server-side JavaScript environments that existed at the time (including well-known but now mostly obsolete stuff like Rhino as well as a few lesser known alternatives to Node that have died out). Node mostly won, so the new common standard is "whatever Node does".
One of the reasons AMD failed was that it is a lot more complex than the alternative. AMD is intended to be asynchronous at runtime. This means it not only defines dependencies, it also conflates the issue of lazy loading dependency bundles. As it turned out, lazy loading dependency bundles at runtime is a difficult, rare and heterogeneous enough problem that a module system can't easily solve it. But having this kind of complexity in it meant that it was effectively overengineered for the more common simple problems.
That said, if you write code today, just use either Node-style imports directly or a tool like Babel that translates ES6 imports to Node-style ones.
More on reddit.comIs it worth migrating a CommonJS project to ES Modules?
Why doesn’t tree‑shaking work well with CommonJS?
Which module system should new projects use?
Videos
Node.js official docs are excellent at explaining the two different types of module systems. CommonJS and ESM.
There is a very good chapter in Node.js Design Patterns, Chapter 2: The Module System (2020) that describes where CommonJS came from. It emerges from the need to provide a module system for JavaScript in browserless environments simply because one did not exist, other than the AMD and UMD initiatives, and to not rely on urls and <script> tags for resources. It was successful so became popular.
We now have ECMAScript Modules thanks to the ES2015 official proposal and it attempts to align the management of modules in both server-side and browser environments.
The main points to help you decide are:
- CommonJS uses the
requirefunction to load modules and is synchronous. As a result, when you assign a module tomodule.exportsfor example, it too must be synchronous. If you have asynchronous stages in your module then you can export an uninitialized module but usingrequirewill mean that there is a chance it won't be ready to use in the code that requires it before it's initialized. An example of exporting and importing might look like this:
// titleCase.cjs
function titleCase(str) {
if (!str){
return '';
}
return str.toLowerCase().split(' ').map(word => {
if (!word){
return word;
}else{
return word.charAt(0).toUpperCase() + word.slice(1);
}
}).join(' ');
}
module.exports = titleCase;
// app.js
const titleCase = require('./titleCase');
console.log(titleCase('the good, the bad and the ugly'));
// "The Good, The Bad And The Ugly"
- ES modules use the
importkeyword to load modules and theexportkeyword to export them. They are static, so they need to be described at the top level of every module and support loading modules asynchronously. ES modules facilitate static analysis of the dependency tree, making dead code elimination (tree shaking) more efficient. The sametitleCasefunction above using ESM would look like:
// titleCase.js
export default function titleCase(str) {
if (!str){
return '';
}
return str.toLowerCase().split(' ').map(word => {
if (!word){
return word;
}else{
return word.charAt(0).toUpperCase() + word.slice(1);
}
}).join(' ');
}
// app.js
import titleCase from './titleCase.js'; //< File extension required on import
console.log(titleCase('the good, the bad and the ugly'));
// "The Good, The Bad And The Ugly"
- ES modules run implicitly in strict mode (can't be disabled). This is a good thing in many opinions as it enforces good coding practices.
- In CommonJS you can use the helpful
__filenameand__dirname. In ES modules you need to do a workaround to get the same functionality like so:
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
- You can dynamically load modules in CommonJS with
require:
let usefulModule;
if (process.env.DEV === true) {
usefulModule = require('./utils/devmodule')
} else {
usefulModule = require('./utils/prodmodule')
}
- And in ESM by using
importas function:
let lib;
if (process.env.DEV === true) {
lib = await import('./utils/devmodule.js');
} else {
lib = await import('./utils/prodmodule.js');
}
const usefulModule = lib.usefulModule;
One thing I have noticed over the years of programming is that languages and libraries evolve. I have observed a growing shift in Node package docs giving more and more of their examples in ESM format. CommonJS is still the dominant option on npm but it tells me the ecosystem is slowly shifting away from CommonJS and towards ESM. Maybe they will run in tandem but such is the nature of evolution, one becomes dominant over the others. All of the projects I work on use the ESM approach. Good luck deciding.
ES modules are designed to be loaded statically, while CommonJS modules are mainly loaded dynamically and synchronously. This can lead to slower performance and a blocking of the main thread. Static analysis refers to the process of analyzing code without executing it, and dynamic imports introduce runtime behavior into a module system. This means that the exact module that will be imported cannot be determined until the code is executed, making it difficult to analyze the module dependencies and relationships ahead of time (AOT).
Read this article please. https://dev.to/costamatheus97/es-modules-and-commonjs-an-overview-1i4b
An import statement can reference an ES module or a CommonJS module. import statements are permitted only in ES modules, but dynamic import() expressions are supported in CommonJS for loading ES modules.
When importing CommonJS modules, the module.exports object is provided as the default export. Named exports may be available, provided by static analysis as a convenience for better ecosystem compatibility.
The major difference in CommonJS and ES module is of synchronous and asynchronous nature, which might affect performance:
- CommonJS modules are synchronous, which isn't an issue in the case of small module execution. However it can delay execution for larger modules.
- Loading and parsing of ES modules is asynchronous.
I do have a module which does have nested imports/exports. About 100 of them from about 15 files. The package is written with ESM and Commonjs support is done with Babel. I did a very simple test where I import only the basic class from my package and console it.
When importing the basic class using commonjs it is faster then using ESM. the difference is small but visible.
?> time node index.cjs
> 0.15
?> time node index.mjs
> 0.25
It might be just my npm package, but in this case it is pretty consistent. Running on node 20.