ECMAScript Modules ECMAScript Modules, also known as JavaScript Modules, is a modular solution officially released with ES6(ES2015) in June 2015.

Before the RELEASE of ESM, there were several modular loading solutions in the community, the most important being CommonJS and AMD. CommonJS for node.js on the server, AMD for browsers (Webpack), and UMD, which is compatible with CommonJS and AMD specifications and supports traditional global variable patterns.

For those who are interested, take a look at the development History of ESM: modules-History & Future

The ESM scheme can be used both server (Deno) and directly in the browser, which perfectly achieves the isomorphism of the front and back ends. The ESM scheme includes export and import commands. See Ruan Yifeng’s Introduction to ECMAScript 6 for a detailed syntax explanation:

  • ECMAScript 6 Getting started – Syntax for modules
  • ECMAScript 6 getting started – Load implementation of Module

This article covers things not covered in the ECMAScript 6 introduction and the mechanism for loading browser modules.

Differences from traditional Script syntax

  • The ESM automatically sets the mode to Strict mode.
  • The ESM does not support HTML comment syntax<! -- hello -->.
  • ESM has module scope. All declarations in files are in module scope and can only passexportExport internal variables, as declared in the ESMvar foo = 42; A global variable is not declared and cannot pass outside the modulewindow.fooRead.
  • ESM within the topthisundefinedRather thanWindowObject, you can useglobalThisTo take the place ofthiscallWindowObject.
  • importexportThis only works in ESM, and will cause an error when used in traditional Script.
  • ESM supportTop level await, is not supported in traditional Script.

Use the ESM in a browser

Load the ESM

In a browser, you can load an ESM directly for module by specifying type using the

<script type="module" src="main.mjs"></script>
<script nomodule src="fallback.js"></script>
Copy the code

Browsers that recognize type=”module” ignore script tags with nomodule attributes, which can be degraded for non-ESM-enabled browsers.

If the browser supports ESM, it also means that the browser supports other ES6 syntaxes, such as Promise and async-await, which can reduce compilation of ES6 and other advanced syntaxes in the code, reduce the size of the code package, improve loading speed and JS code execution speed, and improve page performance. It then provides a compiled version for browsers that do not support ESM and degrades it.

The file suffix

ESM files support.js and.mjs file suffixes. The suffixes are not important in the browser, and the type attribute and content-type are mainly used to determine whether the file is a JS file. However,.mjs is not a standard file extension, and some file servers may not be able to correctly recognize the MIME type of.mjs files as text/javascript. As a result, the content-type is not Application /javascript, and the loading fails. Therefore, it is recommended to use.js as the file suffix.

Cross domain

ESM has CORS cross-domain problems in browsers. All cross-domain ESM resource loads require the access-Control-Allow-Origin response header to be added to the resource response header, which is not required in previous JS resource loads.

Loading mechanism

The default

The difference between defer and Async is that defer does not execute until the entire page has been rendered in memory properly (the DOM structure has been fully generated and other scripts have been executed); Once async is finished downloading, the rendering engine interrupts the rendering, executes the script, and continues rendering. In a word, defer means “render before execution” and Async means “download after execution”. Also, if you have multiple defer scripts, they are loaded in the order they appear on the page, which is not guaranteed by multiple Async scripts.

ESM loads by default as defer, so there is no need to add the defer attribute to the script tag.

Illustration:

The mechanism of the import

ES6 does not describe module loading details and relies entirely on the JS engine implementation.

When the JS engine executes an ESM, there are approximately four steps:

  1. Parsing: Reading the module’s code and checking for syntax errors.

  1. Loading: Recursively Loading all imported modules to create a Module graph.

  1. Linking: For each newly loaded Module, a Module instance module.instantiate is created and imports are mapped using the memory address of all exported content in that Module.

  2. Run time: Finally, Run the body code for each newly loaded module, at which point import processing is complete.

Therefore, all module static dependencies must be downloaded, resolved, and Linking before the module code is executed. An application may have hundreds of dependencies, and if a dependency loads incorrectly, no code is run.

Dynamic import()

Chrome67+, Released May 21, 2019

In static Import, the code will recursively download all dependencies, build the Module Graph, and execute the dependent code before actually running. Sometimes we need to load on demand instead of loading all the code ahead of time, and we need Dynamic import().

You can return a Promise containing the ESM export object by passing in a module path as an argument through the import() function,

<script type="module">
  const moduleSpecifier = './05/lib.js';
  import(moduleSpecifier)
    .then(({ repeat, shout }) = > {
      repeat('hello');    / / - > 'hello hello'
      shout('Dynamic import in action');    // → 'DYNAMIC IMPORT IN ACTION! '
    });
</script
Copy the code

Dynamic Import creates a new Module graph that is handled separately from the original one. Singular instances of the same module will still be shared because loading will cache the module. There is only one module instance for each module linked identically

Top level await

Chrome89+, Released Mar 1,2021

Top-level await is a feature provided by ESM where you can use the await keyword directly at the Top of a module, treating the entire module as one large asynchronous function. If it is a child module, the code of the child module can be executed before the parent module, but it does not prevent the sibling module from loading.

<script type="module">
  const moduleSpecifier = './lib.js';
  const { repeat, shout } = await import(moduleSpecifier);
  repeat('hello');    / / - > 'hello hello'
  shout('Dynamic import in action');    // → 'DYNAMIC IMPORT IN ACTION! '
</script>
Copy the code

Import Maps

Chrome89+, Released Mar 1,202

Importing dependencies through urls is not very convenient. If you want to Import dependencies through Pacakge Name, you can use Import Maps.

<script type="importmap">
{
  "imports": {
    "moment": "/node_modules/moment/src/moment.js"."lodash": "/node_modules/lodash-es/lodash.js"}}</script>
Copy the code

Import Maps define a mapping of module Import names, which can be imported using bare.

In addition to specifying package name directly, you can also use path resolve:

{
  "imports": {
    "lodash": "/node_modules/lodash/lodash.js"."lodash/": "/node_modules/lodash/"}}Copy the code

You can use the following methods to import the js file under the file:

// You can directly import lodash
import _lodash from "lodash";
// or import a specific moodule
import _shuffle from "lodash/shuffle.js";
Copy the code

import.meta

Chrome64+, Released Jan 24,2018

The metadata of the front-end module can be obtained in the code through import.meta. The metadata content is not specified in ECMAScript, so it depends on the runtime runtime environment. In browsers, there is usually import.meta.url, which represents the module’s resource link.

Performance optimization

Dev /features/mo…

With ESM, it is possible to develop a website directly without resorting to packaging tools such as Webpack/Parcel /rollup, but only a few scenarios apply:

  • Local development
  • Simpler pages with less than 100 dependencies and a shallow dependency level (up to 5 at the deepest).

Performance analysis of web pages loaded with 300 modules found that the performance of packaged pages was better than that of unpackaged pages:

It’s because static import/export can be analyzed statically, it’s Tree Shaking to optimize useless code. Static import/export is not just syntax. It’s also a very important tool capability.

Therefore, packaging optimization is still needed in the production process, which can reduce the code volume and improve the loading speed. Vite also uses rollup.js to package the production environment.

preload

The ESM can be preloaded using < Link rel=” ModulePreload “>. In this way, the browser can preload and even precompile the ESM and its dependencies.

<link rel="modulepreload" href="lib.mjs">
<link rel="modulepreload" href="main.mjs">
<script type="module" src="main.mjs"></script>
<script nomodule src="fallback.js"></script>
Copy the code

This is useful for applications with many dependencies and deep hierarchies. However, if rel=” modulePreload “is not used, then the browser needs to build the Module Graph through multiple HTTP requests when the ESM is actually loaded. If all modules are preloaded, dependency loading time can be greatly saved.

Use HTTP/2, HTTP/3

With HTTP/2 or HTTP/3 multiplexing, multiple request and response messages can be transmitted simultaneously, providing a high performance improvement for module tree loading.

In fact, multi-file loading has disadvantages for the first load, but also has advantages for multiple accesses. When we have 100 modules and one module changes something and is packaged into a single file, the browser has to re-download the entire file and cannot cache it. With ESM, modules can be compressed individually without affecting other modules that have not been modified.

reference

  • JavaScript modules – JavaScript | MDN
  • Using JavaScript modules on the web, by Addy Osmani and Mathias Bynens
  • ES modules: A cartoon deep-dive, Hacks blog post by Lin Clark
  • ES6 in Depth: Modules, Hacks blog post by Jason Orendorff
  • Axel Rauschmayer’s book Exploring JS: Modules
  • Using ES modules in browsers with import-maps, by Kasra Khosravi
  • ES6 Tutorial: The syntax of Module by Ruan Yifeng