How to dynamically load ESM in CJS

Use dynamic import() as workaround

Mario Kandut
2 min readMar 22, 2022

This story was originally published here.

This article is based on Node v16.14.0.

The main distinction between ESM and CJS is the loading of modules. ESM loads asynchronous and CJS loads synchronous. There are limitations in interoperability, while ESM can import CJS, CJS cannot require ESM, because it would break the synchronous constraint.

For modules to work in both worlds (ESM and CJS), they would have to expose a CJS interface, but ESM is JavaScripts native module system. As you can see, this is quite a tension point in the node ecosystem.

There is a workaround to asynchronously load an ESM module for use in a CJS module, it is based on dynamic import.

Dynamic import()

Dynamic import() introduces a new function-like form of import. import(moduleSpecifier) returns a promise for the module namespace object, which is created after fetching, instantiating, and evaluating all the module’s dependencies, as well as the module itself. Read more about it on v8.dev.

The sample code below (from the v8 docs), shows how to dynamically load the module utils.mjs.

<script type="module">
const moduleSpecifier = './utils.mjs';
import(moduleSpecifier).then(module => {
module.default();
// → logs 'Hi from the default export!'
module.doStuff();
// → logs 'Doing stuff…'
});
</script>

In the previous article How to convert a CJS module to an ESM, we were converting a CJS to an ESM and were not able to load it, because of the synchronous constraint. Let’s fix this.

Open the index.js file and load the module format.mjs dynamically, since import returns a promise we can use then, though async/await can be used as well.

import('./format.mjs').then(format => {
console.log(format.toUpper('this will be uppercase'));
});

Try to run it with node index.js. You should not get an error and the output should be THIS WILL BE UPPERCASE.

Using dynamic import to load an ESM module into CJS can force a change to the application API, a synchronous function becomes asynchronous. In some cases it might be worth to consider changing the entire project to an ESM package.

TL;DR

  • Dynamic import enables loading of ESM into CJS.
  • Using dynamic import can change the application API (sync to async).

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.

References (and Big thanks):

NodeJS, v8.dev, TC39, JSNAD, CommonJS

--

--

Mario Kandut

I help organizations build and ship products and services. I take an idea from concept to launch, across web & mobile with a modern Typescript Stack.