How to Handle ES6 modules in Node.Js
Tags: node, javascript, devops, tutorial
Published: 2020-10-23 10:14:07

Learning the JavaScript language, you will find that it has two types of modules.

One is the ES6 module, referred to as ESM; the other is Node.js dedicated CommonJS module, referred to as CJS. The two modules are not compatible.

Many people use Node.js and only `require()` load modules, but don't know what to do when they encounter ES6 modules. This article will talk about how to use ES6 modules in Node.js.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/lda7clcx7t96q61c2sa2.jpg)
<figcaption>Source: https://www.zymr.com/es6-impact-node-js/ </figcaption>
---

## __The difference between the two modules__
There is a big difference between ES6 modules and CommonJS modules. The syntax of the module being loaded is different. CJS modules are loaded with `require()` and exported with `module.exports`. But ESM modules are loaded with `import` and exported with `export`. 
`require()` is doing synchronous loading, the following statements must wait for this statement to be executed before it is executed. But `import` is doing asynchronous loading, to be precise it has a static code analysis and dependency resolution phase.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/nxkx0lgjvlzksv4i2yd8.jpg)
<figcaption>Source: https://twitter.com/Manz/status/1265341200007036929</figcaption>

---

## __The distinction of Node.js__
Node.js requires ES6 modules to use the `.mjs` file extension. In other words, as long as the `import` or `export` is used in the script file, the `.mjs` file extension should be used. When Node.js encounters a `.mjs` file, it considers it to be an ES6 module, and strict mode is enabled by default, and it is not necessary to specify it at the top of each module file `"use strict"`. If you do not want to change the file extension to `.mjs`, you can specify the `type` field as a `module` in the project file's  `package.json`.
``` JSON
{
   "type": "module"
}
```
Once set, the JS scripts in the directory will be interpreted as ES6 modules.
``` bash
# Interpreted as ES6 Module
$ node my-app.js
```
If you still want to use the CommonJS module at this time, you need to change the file extension of the CommonJS script to `.cjs`.  If there is no `type` field, or the `type` field is `commonjs`, the `.js` script will be interpreted as a CommonJS module.
__Summarized in one sentence__: `.mjs` files are always loaded as ES6 modules, `.cjs` files are always loaded as CommonJS modules, and the loading of `.js` files depends on the settings of the `type` field inside the `package.json`.

__*Note*__: Try not to mix ES6 modules and CommonJS modules. The `require()` cannot load the `.mjs` file, and an error will be reported. Only the `import` can load the `.mjs` file. Conversely, `require()` cannot be used in `.mjs` files, they should use `import`.

---

## __CommonJS module loads ES6 modules__

`require()` cannot load ES6 modules and an error will be reported in the CommonJS module. You can only use the `import()` method to load ESM.
``` JavaScript
(async () => {
  await import('./my-app.mjs');
})();
```
The above code can be run in the CommonJS module.

One reason why `require()` does not support ES6 modules is that, it is loaded synchronously, and the top-level `await` statement can be used inside ES6 modules, which would cause the module not to be able to be loaded synchronously.

---

## __ES6 modules load CommonJS modules__

In the ES6 module, `import` can load CommonJS modules, but the CJS module should be loaded altogether, and can not just load a specific single exported object.
``` JavaScript
// Correct
import packageMain from 'commonjs-package';

// Report an error
import { method } from 'commonjs-package';
```
This is because ES6 modules need to support static code analysis, and the export interface of CommonJS modules is `module.exports`, which is an object that cannot be statically analyzed, so it can only be loaded as a whole.
To load a specific single exported object, it can be written as follows.
``` JavaScript
import packageMain from 'commonjs-package';
const { method } = packageMain;
```
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/l5tyuc00v836gn9ra7jx.png)

---

## __Support two formats of modules at the same time__

It is easy for a module to support both CommonJS and ES6 formats. If the original module is in ES6 format, you need to give an overall output interface, such as `export default obj`, so that CommonJS can be loaded with `import()`.
If the original module is in CommonJS format, a packaging layer can be added.
``` JavaScript
import cjsModule from '../index.js';
export const foo = cjsModule.foo; 
```
The above code first enters the CommonJS module as a whole and then exports the named interface as needed. You can change the extension of this file to `.mjs`, or put it in a subdirectory, and put a separate `package.json` file in this subdirectory, specifying `{type: "module" }`.
Another approach is to specify the respective loading entry of the two format modules in the `exports` field of the `package.json` file.
```JSON
"exports"：{ 
    "require": "./index.js"，
    "import": "./esm/wrapper.js" 
}
```
The above code specifies `require()` and `import`, and loading the module will automatically switch to a different entry file.