Modules
Features
ES module is deferred by default
Comparison
ES6 vs CommonJS
Features | ES Module | CommonJS |
---|---|---|
Loading style | Asynchronous | Synchronous |
Exported with | Live binding | Cached value |
Import hanlding | Live binding | Cached Value |
Dependency Resolve Time | Build time | Runtime |
Static Analysis | ⭕ | ❌ |
Tree shaking | ❌ | ⭕ |
ES modules vs classic scripts
ES module uses
import
/export
syntax, not available in classis scriptsES modules have
strict
mode enabled by default, while disabled in classic scriptmodule
s have a lexical top-level scopethis
within modules does not refer to the globalthis
but is undefined. (UseglobalThis
to access the globalthis
.)//script.js
var foo = 1;
console.log(window.foo); // 1
// module.mjs
var bar = 1;
console.log(window.foo); // undefinedES module is deferred by default
Deep Dive
<script>
vs <script type='module'>
Refer to this blog post.
Using JS modules in the browser
module
fetching is deferred by default, while classicscript
blocks html parsing<!-- type="module" indicates main.mjs is an ES6 module, and only recognized by browsers that support ESM. -->
<script type="module" src="main.mjs"></script>
<!-- script with nomodule flag is ignored by browsers supporting ESM, but handled by browsers don't. -->
<!-- add defer to avoid blocking html parsing -->
<script nomodule defer src="fallback.js"></script>module
s are always evaluated only once, while classicscript
s are evaluated whenever encountered.<!-- classic.js executes multiple times. -->
<script src="classic.js"></script>
<script src="classic.js"></script>
<!-- module.mjs executes only once. -->
<script type="module" src="module.mjs"></script>
<script type="module" src="module.mjs"></script>
<script type="module">import './module.mjs';</script>module
s and their dependencies are fetched with CORS, meaning any cross-origin module scripts must be served with the proper headers, such asAccess-Control-Allow-Origin: *
.
Why module must be specified explicitly with type='module'
?
Great explanation here.
Export difference in ES6 and CJS
In short, ES6 modules exports objects through live binding while CJS modules exports cached values. Exporting binding in ES6 helps dealing with cyclic dependencies, refer to this blog post for in-depth explanation.
CommonJS exports cached value
//index.js
const foo = require("./foo");
console.log("Initial external value: ", foo.value); // Initial external value: 0
console.log("---Increment function call---");
foo.increment(); // Internal value: 1
console.log("External value: ", foo.value); // External value: 0
console.log("---Direct modification from external---");
foo.value += 1;
foo.printInternalValue(); // Internal value: 1
console.log("External value: ", foo.value); //External value: 1
//foo.js
let value = 0;
function increment() {
value += 1;
printInternalValue();
}
function printInternalValue() {
console.log("Internal value:", value);
}
module.exports = { value, increment, printInternalValue };ES6 exports binding
//index.js
import { value, increment, printInternalValue } from "./foo.mjs";
console.log("Initial external value: ", value); // Initial external value: 0
console.log("---Increment function call---");
increment(); // Internal value: 1
console.log("External value: ", value); // External value: 1
console.log("---Direct modification from external---");
value += 1; // TypeError: Assignment to constant variable.
printInternalValue();
console.log("External value: ", foo.value);
//foo.mjs
let value = 0;
function increment() {
value += 1;
printInternalValue();
}
function printInternalValue() {
console.log("Internal value:", value);
}
export { value, increment, printInternalValue };
🔗 References
- JavaScript modules
- ES6 In Depth: Modules
- Adding JavaScript Modules to the Web Platform
- Modules, introduction -- javascript.info
- ES modules: A cartoon deep-dive -- Lin Clark
- ECMA262 -- Modules
- HTML Living Standards -- Module-related host hooks
- Top-level await