In Node.js, Import ES6 modules in CommonJS code, and more – UPDATED by using Dynamic import

Author: Amresh Mishra | Published On: May 4, 2024

With ES6, JavaScript software engineers at long last acquired a standard module design that chipped away at both program-side and server-side (Node.js) JavaScript runtimes. In addition, ES6 modules offer many fascinating elements.

In any case, Node.js software engineers need to know how to coordinate ES6 modules with the CommonJS module design we’re acclimated to utilizing. Now that Node.js v14 is coming, and carrying full ES6 module support as a standard element, the time has come to refresh a past blog entry about Dynamic Import on Node.js.

In Node.js, Import ES6 modules in CommonJS code, and more - UPDATED by using Dynamic import

The Dynamic Import highlight, a.k.a. the import() capability, gives JavaScript software engineers more opportunity over the standard import articulation. It also allows us to bring an ES6 module into CommonJS code.

What is Node.js?

  • Node.js is an open-source server climate.
  • Node.js is free.
  • Node.js runs on different stages or platforms (Windows, Linux, Unix, Mac OS X, and so forth.).
  • Node.js utilizes JavaScript on the server.

What is CommonJS?

CommonJS is a module organizing framework. It is a norm for organizing and putting together JavaScript code. CJS aids the server-side advancement of applications and its arrangement has intensely impacted NodeJS’s module on the board.

[A module is only a tad or bit of code epitomized in a record, and traded to another document.]

The issue is – how would we get to a module from another module? Generally, Node.js has utilized the CommonJS module design since it was the system that Ryan Dahl found for use in Node.js when he fostered the stage. The CommonJS module determination gave us the necessary assertion and the module.exports object we’ve been utilizing from the outset of Node.js.

While CommonJS and ES6 modules are reasonably basically the same, they are contrary in numerous commonsense ways. Where CommonJS utilizes expect to stack a module, ES6 modules utilize the import proclamation. The grammar of ES6 modules is totally different, just like the stacking system. This means:

  • A CommonJS module can stack other CommonJS modules utilizing the expected assertion.
  • Since require is really a capability call, we can utilize a powerfully registered string to decide at runtime the module to be stacked.
  • An ES6 module can stack either CommonJS or ES6 modules utilizing the import proclamation.
  • The import proclamation takes a static string as the module identifier, and can’t powerfully process a module name.

Until one of the Node.js 13.X deliveries ES6 modules were a trial including requiring the utilization of an order line banner to empower. As it remains at Node.js 13.7, utilizing an ES6 module actually prints an admonition message about an exploratory element, yet it tends to be completely utilized without being empowered by an order line banner.

The Dynamic Import include adds two significant capacities:

  • Progressively deciding the module to stack at run time – in light of the fact that import() is a capability very much like require()
  • Stacking an ES6 module into a Node.js/CommonJS module

Here, in this guide, we will utilize the file extension .mjs to stamp ES6 modules and .js for CommonJS modules. Node.js straightforwardly upholds this mix, however, the defaults can be changed utilizing different arrangement choices.

We want to investigate how to utilize import() in both ES6 and CommonJS modules. The points covered include:

  • Utilizing an ES6 module from another ES6 module.
  • Utilizing an ES6 module from a CommonJS module the correct way.
  • Bombing use case in worldwide degree – nonconcurrent stacking.
  • For a couple of modules w/ similar API, progressively stacking either at runtime.

Use an ES6 module along with import or import( )

An ES6 module can be imported either with the import articulation, in an ES6 module, or by means of the import() capability in either an ES6 or CommonJS module. How about we start with the typical case, an ES6 module stacking an ES6 module?

We should make a straightforward ES6 module, calling it simple.mjs:

var count = 0;
export function next() { return ++count; }
function squared() { return Math.pow(count, 2); }
export default function() { return count; }
export { squared };

Here the count begins at nothing and is augmented by calling straightaway. The default send-out returns the ongoing worth, and we can get the square of the ongoing worth utilizing squared. The usefulness doesn’t make any difference a ton, since we simply need to exhibit utilizing an ES6 module.

Utilizing this from another ES6 module is simple. Make a record, demo-straightforward 1.mjs, containing:

import * as simple from './simple.mjs';

console.log(`${simple.next()} ${simple.squared()}`);
console.log(`${simple.next()} ${simple.squared()}`);
console.log(`${simple.default()} ${simple.squared()}`);

We need to import a few modules and settle on a couple of decisions while printing out the results. Clearly, one more method for doing the import is this:

import { next, squared, default as current } from './simple.mjs';

Yet, one way or the other has no effect, since the two are the same. To run the demo:

$ node demo-simple-1.mjs 
(node:41714) ExperimentalWarning: The ESM module loader is experimental.
1 1
2 4
2 4

We’re actually cautioned that this is a trial include, yet essentially we don’t need to indicate a banner any longer.

Load an ES6 module in a CommonJS module

Assume we have a cross-breed situation where a portion of our code is CommonJS, and some of it is ES6 modules. While that is a poor situation, in this transitionary stage we might be confronted with changing over an application piece by piece and subsequently be confronted with utilizing an ES6 module from a CommonJS module.

Having the import() capability in CommonJS modules permits us to utilize ES6 modules. Yet, it accompanies a major proviso, and that will be that both endless import() are nonconcurrent tasks. Looking at this logically, require() is a simultaneous activity since it doesn’t return until the module completely stacks. Anyway import() returns a Promise, and eventually either the module will stack or a mistake will be tossed.

This implies – to utilize an ES6 module implies something like this – save it as cjs-import-1.js:

(async () => {
    const simple = await import('./simple.mjs');

    console.log(`${simple.next()} ${simple.squared()}`);
    console.log(`${simple.next()} ${simple.squared()}`);
    console.log(`${simple.default()} ${simple.squared()}`);
})().catch(err => console.error(err));

Here we have an inline async capability in the worldwide degree. We utilize the anticipate watchword to trust that the module will stack, prior to utilizing it. Since async capabilities return a Promise, we need to utilize a .catch to catch any conceivable mistake and print it out.

It is run as so:

$ node cjs-import-1.js 
(node:41882) ExperimentalWarning: The ESM module loader is experimental.
1 1
2 4
2 4

The result is true to form and notice that we utilize a CommonJS module this time.

Additionally – precisely the same language structure works precisely for what it’s worth as an ES6 module. That is, the import() capability works the very same in an ES6 setting. To demonstrate this we should duplicate the demo changing the record name expansion to .mjs, so Node.js deciphers it as an ES6 module, and afterward rerun it:

$ cp cjs-import-1.js cjs-import-mjs.mjs
$ node cjs-import-mjs.mjs 
(node:42229) ExperimentalWarning: The ESM module loader is experimental.
1 1
2 4
2 4

This is precisely the same source code. Since Node.js treats records with the .mjs expansion as ES6 modules, changing the document name implies Node.js is deciphering it in an unexpected way. As was guaranteed, import() works in an ES6 module.

A variation is – to save it as cjs-import-2.js:

import('./simple.mjs')
.then(simple => {
    console.log(`${simple.next()} ${simple.squared()}`);
    console.log(`${simple.next()} ${simple.squared()}`);
    console.log(`${simple.default()} ${simple.squared()}`);
})
.catch(err => {
    console.error(err);
});

Rather than utilizing an async capability to deal with the Promise returned by import() we handle it utilizing .then, at that point, and .catch.

$ node cjs-import-2.js 
(node:42004) ExperimentalWarning: The ESM module loader is experimental.
1 1
2 4
2 4

Execution is the same, again using a CommonJS module.

The import() the function is asynchronous, and in Node.js’ current features that makes it is difficult to use an ES6 module as a global scope module identifier.

This is demonstrated by this failure mode – save it as cjs-import-fail-1.js

const simple = await import('./simple.mjs');

console.log(`${simple.next()} ${simple.squared()}`);
console.log(`${simple.next()} ${simple.squared()}`);
console.log(`${simple.default()} ${simple.squared()}`);

The anticipate catchphrase can’t be utilized beyond an async capability, right now. In this manner running it will fall flat:

$ node cjs-import-fail-1.js 
/home/david/t/cjs-import-fail-1.js:2
const simple = await import('./simple.mjs');
               ^^^^^

SyntaxError: await is only valid in async function
    at wrapSafe (internal/modules/cjs/loader.js:1067:16)
    at Module._compile (internal/modules/cjs/loader.js:1115:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1171:10)
    at Module.load (internal/modules/cjs/loader.js:1000:32)
    at Function.Module._load (internal/modules/cjs/loader.js:899:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47

Furthermore – imagine a scenario in which we leave out the anticipated catchphrase. – Save this as cjs-import-fizzle 2.js

const simple = import('./simple.mjs');
console.log(simple);

console.log(`${simple.next()} ${simple.squared()}`);
console.log(`${simple.next()} ${simple.squared()}`);
console.log(`${simple.default()} ${simple.squared()}`);

We’ve added a console.log to see the worth returned by import().

$ node cjs-import-fail-2.js 
Promise { <pending> }
/Volumes/Extra/ws/techsparx.com/t/cjs-import-fail-2.js:5
console.log(`${simple.next()} ${simple.squared()}`);
                      ^

TypeError: simple.next is not a function
    at Object.<anonymous> (/Volumes/Extra/ws/techsparx.com/t/cjs-import-fail-2.js:5:23)
    at Module._compile (internal/modules/cjs/loader.js:1151:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1171:10)
    at Module.load (internal/modules/cjs/loader.js:1000:32)
    at Function.Module._load (internal/modules/cjs/loader.js:899:14)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12)
    at internal/main/run_main_module.js:17:47

We see that without a doubt import() gives us a Promise. Subsequently, there is no next capability on the Promise object, and consequently, it comes up short.

To load Dynamically computing the ES6 module at runtime.

The essential distinction between endlessly import() is the last option that allows us to process the module identifier string. Does this appear to be a little distinction? Ok, however, it is a significant distinction and provides us with an unimaginable area of opportunity in planning Node.js applications.

Consider an inside API or some likeness thereof where you have different executions. For instance, you should store/recover records from various cloud-based document-sharing administrations. It will work on your code to have different driver modules, one for each help, all of which have similar APIs.

Must Read:

In my book, Node.js Web Development, I show a progression of modules for putting away similar articles in a few different data set frameworks. The modules all help similar API, yet under the covers, one module utilizes SQL orders, another purpose Sequelize orders, and another purpose MongoDB orders.

With CommonJS modules we can process the module identifier like so:

const api = require('./api-${process.env.VERSION}.js');

Furthermore, this simply works, a piece of cake. Yet, as we saw prior some consideration should be taken while utilizing import().

We should execute an incredible API that makes certain to change the world – – save it as programming interface 1.mjs:

export function apiFunc1() { console.log(`Function 1`); }
export function apiFunc2() { console.log(`Function 2`); }
export function apiFunc3() { console.log(`Function 3`); }

Then, at that point, since we want a similar API to go against an alternate help, we carry out this – save it as programming interface 2.mjs:

export function apiFunc1() { console.log(`Function 1 - ALTERNATE`); }
export function apiFunc2() { console.log(`Function 2 - ALTERNATE`); }
export function apiFunc3() { console.log(`Function 3 - ALTERNATE`); }

We have two modules, each trading similar capability names with similar marks. Believe us, please, that there are two executions of similar APIs.

To consume either, make the programming interface consume.js containing:

var api;

const loadAPI = async () => {
    if (api) return api;
    api = await import(`./api-${process.env.VERSION}.mjs`);
    return api;
}

(async () => {
    await loadAPI();
    api.apiFunc1();
    api.apiFunc2();
    api.apiFunc3();
})().catch(err => console.error(err));

This is only one of the various ways of doing this. On the off chance that you really want a more substantial model, in an Express app.mjs record (the principal of an Express application) we could do:

A substitute execution is this:

(async () => {
    (await loadAPI()).apiFunc1();
    (await loadAPI()).apiFunc2();
    (await loadAPI()).apiFunc3();
})().catch(err => console.error(err));

These are the same, however, the second is briefer. What’s going on here is that in light of the fact that loadAPI is an async capability, we need to look for it prior to calling any of the capabilities. In the past model, our code would need to realize it had as of now awaited and thusly it can utilize programming interface as opposed to anticipating loadAPI().

A comparative methodology is this, which tries not to need to call a capability yet rather manage an item programming interface.

var _api;
export { _api as api };
import(`./api-${process.env.VERSION}.mjs`)
.then(loaded => { _api = loaded; })
.catch(err => { HALT FAIL PRINT ERRORS AND EXIT });

Then any code utilizing that API would utilize import { programming interface } from ‘app.mjs’; to utilize the powerfully chosen module.

UPDATE As brought up in the remarks, this has an issue. The item, programming interface, would have three expresses:

a) vague

b) an unsettled Promise

c) the stacked module. Consequently,

any code wishing to utilize a programming interface would need to manage the unsettled Promise. Consequently, code utilizing a programming interface ought to utilize (anticipate api).apiFunc1() to trust that the module will complete the process of stacking prior to executing a capability from the module.

import { api } from './that-example-script.mjs';
(async () => {
    (await api).apiFunc1();
    (await api).apiFunc2();
    (await api).apiFunc3();
})().catch(err => console.error(err));

Which is best? This might be ideal since it doesn’t seem to be a capability call and you feel somewhat unsure about the presentation influences. Yet, it ought to be generally a similar execution influence with respect to the loadAPI capability.

/UPDATE

Anyway, we should run the demo content to show our point:

$ VERSION=1 node api-consume.js 
(node:42502) ExperimentalWarning: The ESM module loader is experimental.
Function 1
Function 2
Function 3
$ VERSION=2 node api-consume.js 
(node:42507) ExperimentalWarning: The ESM module loader is experimental.
Function 1 - ALTERNATE
Function 2 - ALTERNATE
Function 3 - ALTERNATE
$ cp api-consume.js api-consume-mjs.mjs 
$ VERSION=2 node api-consume-mjs.mjs 
(node:42673) ExperimentalWarning: The ESM module loader is experimental.
Function 1 - ALTERNATE
Function 2 - ALTERNATE
Function 3 - ALTERNATE

Furthermore, there we have effectively utilized two renditions of our widely acclaimed API. Further, we can utilize a similar method in either CommonJS or ES6 modules.

Author: Amresh Mishra
Amresh Mishra is a passionate coder and technology enthusiast dedicated to exploring the vast world of programming. With a keen interest in web development, software engineering, and emerging technologies, Amresh is on a mission to share his knowledge and experience with fellow enthusiasts through his website, CodersCanteen.com.

Leave a Comment