Wednesday, October 7, 2020

ECMAScript 6 Modules

Table of Contents

    1 Modules in ECMAScript 6
    2 Exports
    3 Imports
    4 JavaScript Modules in Web browsers

Modular programming is encapsulating functionalities and hiding implementation details

  • modular programming allows to keep the global context clean
  • modules may not accidentally modify anymore shared bindings defined by other modules

Before ES6 modules, many different solutions were adopted to the problem of name conflicts and encapsulation

  • closures as namespace
  • CommonJS module specification for server JavaScript runtime environments
    • CommonJS defines the require() function and exports variable
    • NodeJS module system implements the CommonJS specification and uses the require() function and module.exports object
  • AMD (Asynchronous Module Definition) specification
    • many asynchronous module systems run on browsers
    • RequireJS is the most important AMD API implementation and uses the define() function

1 Modules in ECMAScript 6

What is a Module?

The ECMAScript 2015/ES6 standard introduces the import and export keywords into JavaScript to support modularity

Module scope

  • each module has its separate scope
  • module scope is not the global scope of browser or NodeJS
  • in normal scripts, bindings declared at top level code go into the global scope, whereas in modules bindings exist only in the scope of the module

Private and public values

  • each JS file is its own module
  • JavaScript constants and variables, functions and classes defined within a module are private to that module
  • if a module explicitly export a value, that value will be available to other modules that explicitly import it

Naming conficts

  • modules provides a way to manage naming conflicts
  • names of public identifiers do not clash because they are unique
  • names of private identifiers are not important; even if they happen to have the same name, they do not clash
ECMAScript Modules

Modules code is always executed in strict mode

  • in normal script, code can use loose syntax such as arguments object, with statement, undeclared variables, whereas in modules code is automatically in strict mode
  • in ES5 strict mode, top level code this points to the global context, whereas in ES6 modules top level code this value is undefined

Modules are different from classes

  • classes look like modules, as they have public and private fields and methods, but you cannot use classes in place of modules
  • classes are meant to be instantiated and there may exist many instances of a class
  • a module is a collection of JavaScript statements that are executed only once, when the module is loaded

Modules have dependencies

  • a module can depend on other modules
  • a module should state which other modules they depend on
  • module dependencies are statically resolved:
    the module system determine dependencies by reading the file and without executing them
  • ES6 module system supports cyclic dependencies

ES6 module system loads modules asynchronously

  • loading modules asynchronously is a main feature that affects browser usability
  • otherwise, the browser have to wait until modules are finished loading

2 Exports

A module exports one or multiple values

  • a module exports values that your module will import
  • a module contains any kind of declaration (constants, variables, function and classes) and expressions
  • usually, the exported value is a function or class, but may also be an object, an array or a constant

A module should select one value as default export

  • actually, modules are well designed if they export just one value
2.1 Default Exports

to export a default declaration/expression, precede it with export default keywords

  • choose at most one declaration/expression as default
  • it is common having modules that exports a single value
  • export default can export: function expressions, class expressions or object literals
// exporting a class declaration
export default class MyClass { ... }
// exporting a class expresssion
export default class { ... }
2.2 Named Exports

How to export declarations?

  • to export declarations, precede any declaration with export
  • alternatively, write a single export statement at the end of the module

// declare and export on one line
export const myConstant = 1111
export function myFunction(param) { ... }
export class MyClass { ... }

// declare and then export
const myConstant = 1111
function myFunction(param) { ... }
class MyClass { ... }
export { myConstant, myFunction, MyClass }

Default exports vs named exports

  • It is uncommon that a module have both regular exports and one default export
  • Default exports are easier to import than non-default exports
  • named exports can only be used on declarations that use an identifier
    whereas default exports can be used on anonymous expressions

3 Imports

Import a value, already exported by a module, using the import and from keywords

3.1 Default Imports

Importing a value from a module that exports a single default value

import defaultExport from 'module-name'
  • defaultExport is an identifier that names the default export in the client module
  • module-name is a string literal that specifies the module from where you are importing its default export
    the string should be: an absolute path starting with "/", a relative path starting with "." or an URL with protocol and hostname; variables and string template string are not allowed.
    In web browser the string in interpreted as URL relative to location of the current module, in NodeJS the string is interpreted as a filename relative to current module.

The import statement assign the imported value to a constant, it is equivalent to:
const <identifier> = <imported_value>

Like exports, imports must appear at top level code:

  • imports must not be within, classes, function or nested in conditionals
  • usually they are place at start of the module

Example: import a default export

// give a name of your choosing to the default import
import MagicClass from './modules/myclass.js'
3.2 Named Imports

A module can export default values or/and named values

Importing values from a module that export values by names

// import a single named value
import { namedExport } from 'module-name'
// import two named values
import { namedExport1, namedExport2 } from 'module-name'
// you can rename the imported value using the as syntax
import { namedExport1 as myName1, namedExport2 as myName2 } from 'module-name' 
  • where namedExportN is the name given by author of the module

Example: import two functions exported by names trick1 and trick2

import { trick1, trick2 } from 'modules/magicModule.js'

Importing default exports VS importing named exports

  • default exports are not required to have a name in the module that export them, but the you give them a local name in the module that import them
  • named exports have a name in the module that export them, and you must refer to those names, if you want to import their values
  • Note: the exporting module can export any number of named values and the import statement that refernces that module can import any subset of those values
  • Note: the identifiers within import curly braces are all hoisted to the top of the importing module and are assigned to constants

Importing both default and named exports

  • usually, modules define either one default export or multiple named exports
  • it is uncommon for module to define both default and named exports
  • nevertheless you can import default and named exports

Example: importing both default and named exports

import defaultExport, { namedExport1, namedExport2 } from 'module-name'

There a third form of the import statement: you can use the import statement just to execute the module

import 'init.js'
  • employ the code import 'init.js' not to import a module value, but to execute the body of the module once

4 JavaScript Modules in Web browsers

Packaging modules for web browsers

  • at the time of this writing, production code using ES6 modules is packaged with module bundling tools, such as Webpack, Rollup and Parcel. Webpack combines all your imported ES6 module files and packages them into one or more output files known as bundles
  • ES6 modules are natively supported by modern browsers and therefore a bundler is not required in development environment, but it is still worth having a bundler in production environment, because browsers have to download fewer files

Applying modules to HTML documents

  • declare a script is module by using the module value of type attribute
<script type="module" src="./main.js"></script> // as external script
// or
<script type="module">import "./main.js";</script> // as inline script

A <script type="module"> element indicates the starting point of a modular program

  • the starting module loads on demand the other modules the program consists of
    • the other modules are expected to be defined in its own javascript file
    • none of the other modules are expected to be defined as inline modules
  • code between two <script type="module"></script> tags is an ES6 module
    • inline modules should no be used as there is no a way to import values exported from inline modules

Execution of modules is deferred

  • script elements with the type="module" attribute are loaded and executed like script elements with defer attribute
  • browsers load the script as soon as it encounter them, but script is executed after HTML parsing is complete

The async attribute

  • adding the async attribute to a module script element, modifies when the module is executed
  • the async module will be executed as soon as the script is loaded and before HTML parsing is complete

The nomodule attribute

<script type="module" src="main.js"></script> // type="module"
<script nomodule src="fallback.js"></script>  // nomodule attribute
  • module-agnostic browsers ignore type="module" and nomodule attribute, so they execute fallbak.js
  • module-aware browsers recognise type="module" and nomodule attribute, so they execute main.js

Converting newer ECMAScript versions into older versions

  • a code translator is a tool that converts code from a programming language to another programming language
    a compiler is a translator used to convert high-level programming language to low-level programming language
  • Babel is a JavaScript (trans)compiler that converts ECMAScript 2015+/ES6+ code into a version of JavaScript, that can be run by older JavaScript engines.
  • using Babel and Webpack tools, you can convert modular JavaScript code to non-modular ES5 code
    and then load the ES5 code through the <script nomodule></script> element

Module filename extensions .mjs or .js

  • NodeJS adopts the .mjs filename extension for modular code
  • web browser script element can load modules with both .mjs or .js filename extension,
    if your web servers uses the .mjs extension, it must be configured to use the same MIME type as of .js files

No comments:

Post a Comment