Converting CommonJS to EcmaScript modules
Posted
Updated
Europe’s developer-focused job platform
Let companies apply to you
Developer-focused, salary and tech stack upfront.
Just one profile, no job applications!
This article is based on Node v16.14.0.
Since Node v14, there are two kinds of modules, CommonJS Modules (CJS) and EcmaScript Modules (ESM) .
EcmaScript Modules (ESM) were introduced as part of ES6 (EcmaScript 2015).
The main goal was for module includes to be statically analyzable, so browsers would be able to pre-parse out imports.
This behaviour is similar to collecting all <script>
tags as the web page loads.
💰 The Pragmatic Programmer: journey to mastery. 💰 One of the best books in software development, sold over 200,000 times.
It took more than three years for major browsers to implement this retrofitting of a static module system into a dynamic language. And even longer for ESM to be implemented in NodeJS, because of interoperability with the existing CJS module system, though there are still some issues.
The main difference between CJS and ESM is that CJS loads every module synchronously, and ESM loads every module asynchronously.
There are also two types of ESM: native ESM and "transpiled ESM". Transpiled ESM is ESM-like syntax that would be typically be transpiled with Babel. The syntax looks similar, but the behaviour can vary. "Transpiled ESM" compiles to CommonJS in Node and will be compiled in the browser using a bundled synchronous loader. This means, that "transpiled ESM" loads modules synchronously, native ESM loads modules asynchronously. A Node application (or module) can contain CJS and ESM files.
Let's convert the module created in How to create a CSJ module.
The CSJ module contains this code:
'use strict';
const toUpper = str => {
if (typeof str === 'symbol') str = str.toString(); // convert to string if symbol is used as input
str += '';
return str.toUpperCase();
};
module.exports = { toUpper };
To convert it to an ESM module, the only we have to do change the file extension from .js
to .mjs
.
If you are on linux you can use the mv
command.
mv format.js format.mjs
CJS modules modify the module.exports
object, in ESM its native syntax.
Hence, to create a named export, we can just use the export
keyword.
Let's update our module:
export const toUpper = str => {
if (typeof str === 'symbol') str = str.toString(); // convert to string if symbol is used as input
str += '';
return str.toUpperCase();
};
The 'use strict'
pragma to enforce strict mode is also not needed anymore, since ESM executes in strict-mode.
That's it for converting CSJ to ESM. There is only one thing, try to run the node application with node index
.
You will see an error.
internal/modules/cjs/loader.js:948
throw new ERR_REQUIRE_ESM(filename);
^
This error occurs because the require
function will not automatically resolve a filename without an extension ('./format')
to an .mjs
extension.
Ok, so let's fix this and try again.
node -p "require('./format.mjs')"
We will get an error again.
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module.
The major difference between CSJ and ESM, is that CJS is synchronous. CJS cannot require ESM since that would break the synchronous constraint, but ESM can import CJS.
There is a way around this constraint with dynamic import. Have a look at this article How to dynamically load an ESM Module in CJS.
.mjs
and use the export
keyword instead of module.exports
.Thanks for reading and if you have any questions, use the comment function or send me a message @mariokandut.
If you want to know more about Node, have a look at these Node Tutorials.
Never miss an article.