Features of ES6 modules

With the new version of JavaScript ES6 comes integrated support for modular programming.
- Supports modules placed in files – one module per file
- Modules are unique instances, each module is executed only once (singleton model).
- ES6 modules can work both synchronously and asynchronously.
- Supports “cyclic dependencies”.
- The syntax is even more compact than CommonJS
- ES modules have a static structure. Since the module structure is immutable, it is often enough to look at the code and figure out what is being imported where. This is not the case with a dynamic structure, where the code often needs to be executed to see what is being imported. Possible errors can also be found at the time of compilation (with the module bundler), because all actions related to the import and export of modules are determined at that moment.
- All imported elements are immutable from the importing module. Any operation to assign a value to an imported element would cause an error (TypeError).
- ES6 does not make a copy of a property but shares a link to that property. This is true even for sharing primitive properties (“number” or “string”). In the following example, we see that if the parent module changes the value of the shared property (the change was made by a method from the parent module!), the module that imported that property “sees” that change.
EXAMPLE
budget.js
1234export let brojac = 1;export function povecaj() {brojac++;}main.js
We import all the elements that were exported from the module “proracun.js”
1234import {brojac} from './proracun';console.log(proracun.brojac); // 1proracun.povecaj(); // Pozivanje metotde iz uvezenog modulaconsole.log(proracun.brojac); // 2If the variable “counter” was used now in some “third” module, it would have the value 2.
Export module
Standard export
Standard exporting is achieved by placing a reserved word in front of the variable (function) declaration:
|
1 2 3 4 5 6 7 8 |
export var foo = 1; export var foo = function () {}; export var bar; export let foo = 2; export let bar; export const foo = 3; export function foo () {} export class foo {} |
Named export
Named (named) exporting is the syntax with which we define the elements for export at the end of the module. The name came from the fact that their names are used to define the exported elements.
|
1 2 3 4 |
export {}; export {foo}; export {foo, bar}; export {foo as bar}; |
Default export
It is recommended that when exporting a module, define a part of the module that is exported by default. The default export is defined with the revised word default. Only one default export is allowed per module. The exported part is used in another module under the name “default”.
|
1 2 3 4 5 6 7 8 9 |
export default 42; export default {}; export default []; export default (1 + 2); export default foo; export default function () {} export default class {} export default function foo () {} export default class foo {} |
The default export can also be defined with a “named form” with the addition of the keyword “as”:
|
1 2 |
export {foo as default}; export {foo as default, bar}; |
NOTE: this syntax is not allowed (classic example of ES6 syntax inconsistency):
|
1 2 3 |
export default var=42; export default let=42; export default const=42; |
Re-exporting
Re-exporting means exporting from a non-native module.
|
1 2 3 4 5 6 7 8 9 |
export * from "foo"; export {} from "src/other_module"; export {foo} from "src/other_module"; export {foo, bar} from "src/other_module"; export {foo as bar} from "src/other_module"; export {foo as default} from "src/other_module"; export {foo as default, bar} from "src/other_module"; export {default} from "src/other_module"; export {default as foo} from "src/other_module"; |
Recommendations
If the module has quite a number of “scattered” elements that are exported, it is recommended to define the API clearly and clearly at the top of the module, as in the following example:
|
1 2 3 4 5 6 7 8 |
// calculator.js const api = { add, subtract, multiply, divide }; function add(a,b) {...} function subtract(a,b) {...} function m ultiply(a,b) {...} function divide(a,b) {...} function somePrivateHelper() {...} export default api; |
Module import
Features
- The variable or function declaration is raised to the beginning of the domain during the compilation phase (hoisting). Therefore, calling the function in the following example will not throw an error:
12foo();import { foo } from "foo"; - All imported elements are immutable from the importing module. Each operation of assigning a value towithin the module (which imports) would cause an error (TypeError). However, in the case of an object, we can change it indirectly:
EXAMPLE
lib.js
1export let obj = {};main.js
123import { obj } from './lib';obj = {}; // TypeErrorobj.prop = 123; // OK - It is not allowed to use variables in the import statement. Modules are static, so the import must not depend on something obtained at runtime. So the syntax in the following example is incorrect and returns an error:
1import foo from 'some_module'+SUFFIX; // Returns an error
- Conditional loading of modules with only ES6 syntax is not possible. The reason for this is that the “import” statement must always be “top level”, which is not achieved by indenting it inside an if loop. Therefore, for such a case, a programmable loader must be used – System.JS, which would have some additional script:
123456if (Math.random()) {System.import('some_module').then(some_module => {// Use some_module})}
Named import of modules (named import)
|
1 2 3 4 5 |
import {} from "foo"; import {bar} from "foo"; import {bar, baz} from "foo"; import {bar as baz} from "foo"; import {bar as baz, xyz} from "foo"; |
Import default elements
Importing default values is done by simply calling the module name or calling it by name.
|
1 2 |
import foo from "foo"; import {default as foo} from "foo"; |
Importing the entire module (Import namespace object)
We use global import if we want to load all exported elements of a module in one command. Namespace import requires the syntax * as.
|
1 |
import * as bar from "foo" |
After which all elements from module “foo” are available in the module under the “new” name “bar”:
|
1 2 |
bar.x; bar.baz(); |
If there were default values defined in the exported module, after importing the entire namespace, it is called with “default”:
EXAMPLE
budget.js
|
1 2 3 4 |
export default function povecaj() { brojac++; } export function bar(){} |
main.js
|
1 2 3 |
import * as proracun from './proracun'; proracun.bar(); proracun.default(); |
Journeys to production
Since the ES6 syntax is not yet sufficiently supported in browsers, it is necessary to carry out certain preparations before putting it into production:
- Select transpiler (Babel Typescript…)
- Selecting the format we want to convert to during transpilation (AMD or CommonJS).
- Transpiling code
- Choice of dependency manager “module loader” (require.js or SystemJS) or “module bundler” (Browswerify or Webpack).
- Integration of “dependencies manager”
Although this road is quite thorny, it is considered that ES6 modules are the future and will over time supplant all formats used so far, and even CommonJS on the server side.
