1 introduction

The development of JS in different periods gives birth to different modularity mechanisms; In recent years, with the implementation of front-end modular standards, different end JS have done their own implementation. Let’s talk about that today.

In this paper, we will mainly discuss the following four aspects:

  • An overview of JavaScript modularity mechanisms;
  • How to use ES Modules in Node;
  • CommonJS and ES Modules are the same.
  • CommonJS and ES Modules refer to each other.

An overview of JavaScript modularity mechanisms

There are three common modularity mechanisms in JavaScript:

  • AMD (Asynchronous Module Definition): Use this parameter in the browserdefineFunction definition module;
  • CommonJS (CJS): used in NodeJSrequireandmodule.exportsImport and export modules;
  • ESM (ES Modules): Native module mechanism supported by JavaScript from ES6(ES2015), usedimportandexportImport and export modules;

3 Node supports ES Modules

Node Verison 13.2.0 officially supports ES Modules.

Note: The –experimental-modules startup parameter is removed, but since the ESM loader is experimental, running ES Modules code still gets a warning:

(node:47324) ExperimentalWarning: The ESM module loader is experimental.
Copy the code

4 Use ES Modules in NodeJS

There are two ways to use ESM in Node:

1) In package.json, addtype: "module"Configuration;

File directory structure:

.├ ─ ├─ download.txt ├─ download.txtCopy the code
// utils/speak.js
export function speak() {
    console.log('Come from speak.')}// index.js
import { speak } from './utils/speak.js';
speak(); //come from speak
Copy the code

2) The.mjs file can be used directlyimportexport;

File directory structure:

.├ ─ ├─ download.├ ─ ├─ download.├ ─ download.├ ─ download.├ ─Copy the code
// utils/sing.mjs
export function sing() {
    console.log('Come from sing')}// index.mjs
import { sing } from './utils/sing.mjs';
sing(); //come from sing
Copy the code

Note:

  • If you use ES Modules directly from Node without adding either of these two items, you will get a warning:
Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
Copy the code
  • According to the ESM specification, using the import keyword does not populate file paths with file extensions by default, as CommonJS modules do. Therefore, ES Modules must specify the file extension.

Module scope:

The scope of a module, defined by the parent package.json file path with type: “module” in it. Using.mjs extension files to load modules is not limited to the scope of the package.

Similarly, files without the type flag in package.json use CommonJS module mechanism by default, and.cjs extension files use CommonJS mode to load modules without the scope of the package.

The entry of the package

There are two ways to define the entry to a package: in package.json you can define the main field or exports field:

{
  "main": "./main.js"."exports": "./main.js"
}
Copy the code

Note that when the exports field is defined, all subpaths of the package are wrapped and the subpath files cannot be imported.

For example: require(‘ PKG /subpath.js’) will return an error:

ERR_PACKAGE_PATH_NOT_EXPORTED error.`
Copy the code

5. The difference in execution timing between the two module mechanisms

  • ES Modules Import Modules that are pre-parsed so that they can be imported before the code runs.
  • In CommonJS, modules are resolved at run time;

Here’s a simple example to illustrate the difference:

// ES Modules

// a.js
console.log('Come from a.js.');
import { hello } from './b.js';
console.log(hello);

// b.js
console.log('Come from b.js.');
export const hello = 'Hello from b.js';
Copy the code

Output:

Come from b.js.
Come from a.js.
Hello from b.js
Copy the code

The same code uses the CommonJS mechanism:

// CommonJS

// a.js
console.log('Come from a.js.');
const hello = require('./b.js');
console.log(hello);

// b.js
console.log('Come from b.js.');
module.exports = 'Hello from b.js';
Copy the code

Output:

Come from a.js.
Come from b.js.
Hello from b.js
Copy the code

You can see that ES Modules pre-parses the module code, whereas CommonJS is parsed while the code is running.

Note that according to the EMS specification import/export must be at the top level of the module, not in scope; Second, import/export is promoted to the top of the module at compile time.

6. The differences between the two module mechanisms in principle

  1. CommonJS

Node treats each file as a separate Module and defines a Module constructor that represents the Module itself:

function Module(id = ' ', parent) {
  this.id = id;
  this.path = path.dirname(id);
  this.exports = {};
  this.parent = parent;
  this.filename = null;
  this.loaded = false;
  this.children = [];
};
Copy the code

The require function takes a value representing the module ID or path as an argument and returns objects exported using module.exports. Before executing the code module, NodeJs uses a wrapper to encapsulate the code in the module:

(function(exports.require.module, __filename, __dirname) { 
    // Module code actually lives in here 
}); 
Copy the code

By doing so, Node.js accomplishes the following:

  • It keeps top-level variables (defined as var, const, or let) module-scoped and not global objects.
  • It helps to provide some seemingly global but actually module-specific variables, such as:
    • Implementers can use module and exports objects that export values from modules.
    • Contains the __filename and __dirname shortcuts for the module’s absolute filename and directory path.

In short, each module has its own function wrapper, which Is how Node ensures that the code inside the module is private to it.

The exported contents of the module are indeterminate until the wrapper executes. In addition, modules loaded for the first time are cached in module._cache.

A complete load cycle looks like this:

Resolution -- > Loading -- > Wrapping -- > Evaluation -- > CachingCopy the code
  1. ES Modules

In ESM, import statements are used to import module-dependent static links when parsing code. File dependencies are determined at compile time. For ESM, the loading of modules can be roughly divided into three steps:

Construction -> Instantiation -> EvaluationCopy the code

These steps are performed asynchronously, and each step can be considered independent of the other. This is very different from CommonJS, where every step is done synchronously.

7. Reference between the two modules

Both CommonJS and ES Modules support Dynamic Import (), which supports the import of both module mechanisms.

Import ES Modules in the CommonJS file

Because ES Modules load, parse, and execute asynchronously, and require() is synchronous, you can’t reference an ES6 module by require(). The import() function proposed by ES6 will return a Promise that completes after the ES Modules load. We can use this to import ES Modules asynchronously in CommonJS:

// Use then() to operate after the module is imported
import(" es6 - modules. MJS). Then ((module) = >{/ *... * /}).catch((err) = >{/ * *... * /})
// Or use async functions
(async() = > {await import('./es6-modules.mjs'); }) ();Copy the code
Import the CommonJS module in the ES Modules file

It is convenient to use import to reference a CommonJS module in ES6 modules because asynchronous loading is not required in ES6 modules:

import { default as cjs } from 'cjs';

// The following import statement is "syntax sugar" (equivalent but sweeter)
// for `{ default as cjsSugar }` in the above import statement:
import cjsSugar from 'cjs';

console.log(cjs);
console.log(cjs === cjsSugar);
Copy the code

8. Current situation and future of ES Modules in Node

Prior to the introduction of the ES6 standard, server-side JavaScript code relied on the CommonJS module mechanism for package management. Today, with the introduction of ES Modules, developers can enjoy many of the benefits associated with publishing specifications. It is important to note, however, that this feature is experimental (Stability: 1) in the latest version of Node V15.1.0 as of this release and is not recommended for use in production environments.

Finally, converting the current project from CommonJS to ES Modules can be a challenge due to the incompatibility between the two module formats. The conversion between CommonJS and ES modules can be realized by using babel-plugin-transform-modules-commonjs.

Refer to the link

  • Node Documentation
  • Node version13+ release log
  • Node version14+ release log
  • Node modules wrapper
  • Node Source code: cjs
  • ECMA262 Modules
  • TC39 Proposal Dynamic import