ECMAScript Module and CommonJS learning notes

Note: esm below refers to ECMAScript Module, that is, the module syntax of ES6 (import/export), and cjs refers to CommonJS (module.exports/require)

Browser side ESM module loading

When the browser uses the esm module syntax to import/export or load the ES6 module through the script tag, type="module" must be added so that the browser will know that this is an ES6 module.

The browser loads < script > with type="module" asynchronously, which will not block the browser. That is, wait until the whole page is rendered before executing the module script, which is equivalent to opening the defer attribute of the < script > tag (async attribute can be used)

Differences between esm and cjs

esm is loaded at compile time (static loading), with an independent module dependent parsing stage. The module outputs the interface (Reference), and the import command is loaded asynchronously;
cjs refers to runtime loading (dynamic loading), and the module outputs the value (module object). The value will be cached, and require refers to synchronous loading

esm outputs an interface, which is a read-only reference to the value in the module. Therefore, the interfaces obtained when different scripts load modules point to the same instance. The value is taken according to the output interface, so the change of the internal value of the module will be read by different scripts

cjs outputs a copy of the value (the module.exports object, which caches the output value), which is why require is loaded at run time, because a module object can only be generated at run time. Once the module file is loaded and executed, a cached value will be output. Subsequent changes within the module will not affect this value (the same value is referenced between different scripts). If you want to get the value after the change within the module, you need to define a get function as a value collector to return the value inside the module

CommonJS A module of is a script file. require The first time the command loads the script, it executes the entire script and then generates an object in memory

{
  id: '...', // Module name
  exports: { ... }, // Each interface of module output
  loaded: true, // Is the execution completed
  ...
}

When this module is needed in the future, it will arrive exports Property.
Even if executed again require The command will not execute the module again, but take values in the cache (this is the value copy and cache mentioned above).
in other words, CommonJS No matter how many times the module is loaded, it will only run once when it is loaded for the first time. If it is loaded later, the result of the first run will be returned, unless the system cache is cleared manually

reference resources: https://es6.ruanyifeng.com/#docs/module-loader#CommonJS-%E6%A8%A1%E5%9D%97%E7%9A%84%E5%8A%A0%E8%BD%BD%E5%8E%9F%E7%90%86

The esm module and cjs module refer to each other

cjs module introduces esm module - because the loading, parsing and execution of esm are asynchronous and cjs are synchronous, you can't introduce esm module through require(), you need to use import()

(async () => {
  await import("...");
})()

esm syntax introduces cjs module -- it can be referenced directly, but it can only be loaded as a whole

import m from "cjsModule.js";
import { a } from "cjsModule.js"; // report errors

// This is because the ES6 module needs to support static code analysis, and the output interface of the CommonJS module is module Exports is an object that cannot be statically analyzed, so it can only be loaded as a whole

It is not recommended to mix ESM and CJS syntax in a file, especially if babel compilation is involved in the project, some inexplicable errors often occur

https://github.com/xiaoxiaojx/blog/issues/27
https://www.tangshuang.net/7686.html

Cyclic loading

"Cyclic loading" means that the execution of a script depends on b script, and the execution of b script depends on a script

// such as
// a.js
const a = require("./b.js");
// b.js
const a = require("./a.js");

In esm module and cjs module, the performance of cyclic loading is different:

cjs only outputs the executed part, and the unexecuted part will not be output (at this time, the module script has not been executed, and the module output value will change)

When the script a executed by esm refers to another module script b, it will execute script b first. When script a is referenced in b, the engine will think that the interface to be introduced by b from a already exists, and the b script will execute normally. At this time, undefined errors may be reported (such undefined errors can be solved through the declaration promotion of var or function)

https://es6.ruanyifeng.com/#docs/module-loader#%E5%BE%AA%E7%8E%AF%E5%8A%A0%E8%BD%BD

Module content dynamic injection

esm injection: use the output interface to point to the same instance in different scripts

// module.js
// Output an object reference, which is read-only, but the internal value can be modified
export default {
  // The properties inside the object can be modified
  aInterface: {}
}

// inject.js
import m from "./module.js";
m.aInterface = {
  msg: "new things"
}

cjs injection: take advantage of the feature that once you use require(), you will output a module object cache, and then use the cache value between different scripts

// module.js
// After being require d, an object will be generated, which is used by all scripts
module.exports = {
  aInterface: {}
}

// inject.js
// Once require d, an object that will be cached will be generated after executing the module script
const m = require("./module.js");
m.aInterface = {
  msg: "new things"
}

reference resources

https://es6.ruanyifeng.com/#docs/module
https://es6.ruanyifeng.com/#docs/module-loader
https://zhuanlan.zhihu.com/p/337796076

Keywords: Javascript Front-end ECMAScript

Added by dila125 on Fri, 28 Jan 2022 12:19:12 +0200