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 andexportsvariable - NodeJS module system implements the CommonJS specification and uses the
require()function andmodule.exportsobject - 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
argumentsobject,withstatement, undeclared variables, whereas in modules code is automatically in strict mode - in ES5 strict mode, top level code
thispoints to the global context, whereas in ES6 modules top level codethisvalue isundefined
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 defaultcan 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
exportstatement 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'
-
defaultExportis an identifier that names the default export in the client module -
module-nameis 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
namedExportNis 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
modulevalue oftypeattribute
<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 withdeferattribute - browsers load the script as soon as it encounter them, but script is executed after HTML parsing is complete
The async attribute
- adding the
asyncattribute to a module script element, modifies when the module is executed - the
asyncmodule 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"andnomoduleattribute, so they execute fallbak.js - module-aware browsers recognise
type="module"andnomoduleattribute, 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
.mjsfilename extension for modular code - web browser script element can load modules with both
.mjsor.jsfilename extension,
if your web servers uses the.mjsextension, it must be configured to use the same MIME type as of.jsfiles
No comments:
Post a Comment