Skip to main content

module_ES6 Notes 13

Free2016-10-29#JS#es6模块语法#js模块化#es6模块与AMD#es6模块与CommonJS#es6模块机制

Static Module Mechanism

Preface

This is the last content of the ES6 notes series, and also the only feature that can only be used in the future

When is the future?

Perhaps when HTTP2 becomes widespread. But more likely, it will "not be usable" in the future either (still can only be used in build tools, existing only in "compile time")

1. AMD, CMD and CommonJS

AMD/CMD, some extended knowledge as follows:

CommonJS is a set of theoretical specifications (for example, the theoretical specification for js is ES), while SeaJS and RequireJS are both concrete implementations of the Modules part of CommonJS

CommonJS was formulated for js outside the browser (server side), so it uses synchronous module loading. SeaJS is an implementation of CommonJS, and while RequireJS is also an implementation of CommonJS, it uses asynchronous module loading, which is more suited to the browser's single-threaded environment

Summary: The Modules part of CommonJS proposed the theory of modular code management. To enable js to load modules, various implementations like RequireJS and SeaJS can be called modular script loaders

CMD: Common Module Definition, such as SeaJS

AMD: Asynchronous Module Definition, such as RequireJS

Both are a set of specifications for defining code modules, facilitating modular script loading and improving response speed

Differences between CMD and AMD:

CMD has nearby dependencies. Easy to use, dependencies can be fetched as needed within the module, no need to declare dependencies in advance, so there's some performance reduction (need to traverse the entire module to find dependencies)

AMD has前置 dependencies. Must strictly declare dependencies, for dependencies within logic (soft dependencies), solved through asynchronous loading and callback handling

(Quoted from [JS Programming Common Sense](/articles/js 编程常识/#articleHeader9))

If you've paid attention to JS modularization, you should be clear about the confused relationship between these three. ES6 modules hope to end this confusion through standards

2. ES6 Module Syntax

1. Module Scope

module introduces module scope, with the following characteristics:

  • Currently (2016/1/31 2016/10/29) no browser supports ES6 modules (possibly such module loading mechanism is not suitable for browser environment), using tools like webpack can integrate all imported content into one file

  • ES6 modules are in strict mode by default, whether or not you add 'use strict';

  • Supports renaming during import/export, import/export {api as newApi}, renaming during import mainly solves naming conflicts, renaming during export can implement aliases ($ and jQuery)

  • Supports default import/export, can import CommonJS and AMD modules

  • Can only use import/export at the outermost scope of the module, and cannot use in conditional statements

Summary: Promotes strict mode; compatible with CommonJS and AMD; just a simple static module mechanism, doesn't solve problems like on-demand loading

Renaming during import/export, examples as follows:

// Rename during import, solve naming conflicts
import {flip as flipOmelet} from "eggs.js";
import {flip as flipHouse} from "real-estate.js";

// Rename during export, implement aliases
function v1() { ... }
function v2() { ... }
export {
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
};

2. import

import {api1, api2...} from 'xxx.js' syntax, characteristics as follows:

  • Supports partial api import (don't import unnecessary function interfaces, of course, xxx.js is fully loaded, partial import is just scope control)

  • If xxx.js also has import statements, will load and execute depth-first

  • Executed modules will be ignored, avoiding forming circular references

  • Supports default import, used to support importing CommonJS and AMD packages (default is the export object), import api from 'xxx.js' is equivalent to import {default as api} from 'xxx.js'

  • Supports importing module objects, import * as apis from 'xxx.js', * represents everything exported in xxx.js, integrates everything exported from xxx.js into the apis object, access through apis.xx

Summary: Loading mechanism is similar to CSS's @import, handling of circular dependencies is also similar; also compatible with CommonJS and AMD

At the scope level supports partial import, useful, but not very meaningful, better配合 with build tools for "pruning" (tree shaking) during compilation

3. export

export {api1, api2...} syntax, characteristics as follows:

  • No need to declare on the first line, can export at any position in the module's outer scope

  • Can declare multiple exports, but must ensure api names have no duplicates, duplicate names may cause errors

  • Supports default export, export default api or export {api as default}

  • Supports aggregated export, export {api1, api2...} from 'xxx.js' is equivalent to import + export, but export from won't introduce each api variable into the current module scope (imports then directly exports, cannot reference)

  • The api list exported by export must be in literal form, cannot traverse an array to export array elements

Summary: Organizes export list during loading, so can export at any position in outer scope; supports aggregation, extracts parts from various third-party modules and integrates them; static limitation, doesn't allow dynamic export

Examples as follows:

// Default export
 let myObject = {
  field1: value1,
  field2: value2
};
export {myObject as default};
// Equivalent to
export default {
  field1: value1,
  field2: value2
};

// Aggregated export
// Import "sri-lanka" and re-export part of what it exports
export {Tea, Cinnamon} from "sri-lanka";
// Import "equatorial-guinea" and re-export part of what it exports
export {Coffee, Cocoa} from "equatorial-guinea";
// Import "singapore" and export everything it exports
export * from "singapore";

3. Module Execution Mechanism

ES6 standard doesn't specify the concrete module loading mechanism, left to the final implementation to decide, but clearly specifies the module execution mechanism, divided into 4 steps

  1. Syntax Parsing

Check for syntax errors

  1. Loading

Recursively load everything imported, how exactly to load, not specified, completely left to the final implementation to decide

  1. Linking

Create module scope, and stuff everything imported into the scope

If import errors, it will trigger an error, specific behavior unknown (because no browser has yet passed step 2)

  1. Runtime

Execute every statement of every module, at this point encounter import/export and ignore them, because module-related processing has ended

Static Limitations

  • Can only use import/export at the module's outermost scope, cannot use in conditional statements, nor in function scope

  • Identifiers exported by export must be in literal form (must have corresponding declaration in source code), cannot traverse an array and then export a bunch of things

  • Module objects are frozen, cannot add polyfill-style new features by hacking module objects

  • All dependencies of a module must be loaded, parsed and linked before module code execution, there's no syntax for on-demand lazy loading through import

  • Errors produced by importing modules have no corresponding recovery mechanism. If a module fails to load or link, no modules will execute, and cannot catch import errors

  • Cannot execute other code before module loads dependencies, this means cannot control the module's dependency loading process

Because these limitations exist, so possibly after HTTP2 becomes widespread, ES6 module mechanism still may not rise in browsers, like CSS's @import, usable, but nobody wants to use it

4. HTTP2 and Modularization

In the HTTP1.1 environment, to reduce HTTP request count, all modularization solutions ultimately rely on build tools to integrate into a single file

But HTTP2 brings some changes, perhaps able to change this engineering process, such as Multiplexed stream and Server Push:

Http2 connections can carry dozens or hundreds of multiplexed streams, multiplexing means data packets from many streams can be mixed together and transmitted through the same connection, two different trains mixed together for transmission, when reaching the destination, they are separated and reassembled into two different trains.

Client requests a resource X, server judges that perhaps the client also needs resource Z, pushes resource Z to the client without asking the client in advance, after client receives it, can cache it for later use.

(Quoted from Introduction to Http 2.0 Protocol)

Multiplexed streams level the advantage of file merging, server push helps solve deep import problems, so ES6 modules may rise in browser environments

HTTP2 has important significance for the modularization process, providing opportunities to maintain modularization in production environments, JS, CSS and even other resources may welcome true modularization

P.S. For more details about HTTP2, please check https://github.com/bagder/http2-explained

5. Current Status of ES6 Modules

As the various milestones of the roadmap are completed, browsers will be able to implement them. See the following trackers for the current status of the main browsers:

IE/Edge: Under Consideration

Firefox: In progress

Chrome: In progress

Webkit: Meta Bug

(Quoted from https://github.com/whatwg/loader)

For more information about ES6 module loaders, please follow this repo, ES6 specification doesn't specify the concrete implementation of loading, so browsers are all stuck on loader implementation

References

  • "ES6 in Depth": Free e-book provided by InfoQ Chinese site

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment