CHAPTER 8
Modules provide support for exporting and importing values without polluting the global namespace. We have had this capability in JavaScript through third-party libraries like AMD and CommonJS, but ECMAScript 6 now has this ability as well.
Note: This chapter deals with actual files to demonstrate export and import features of ECMAScript 6. All “file” references are to a file with the same name as the snippet title in the following format: code-listing-xxx.js.
The best way of understanding export is by looking at it in action. Consider the following file:
Code Listing: 109
export function sum(x, y) { return x + y; } export var pi = 3.141593; |
We are exporting a function named sum as well as a variable, pi.
Let’s move onto the next section and see how we can import.
Now that we have created our own module, let’s take advantage and use it in our own code. Consider the following file:
Code Listing: 110
import {sum, pi} from './code-listing-109'; console.log('2 pi = ' + sum(pi, pi)); |
In this example, we are importing named exports from the module.
Code Listing: 111
2 pi = 6.283186 |
As you can see, we were able to access both the function and the variable that we exported.
We can provide aliases for our named exports as well as our imports. Let’s look at another example:
Code Listing: 112
function sum(x, y) { return x + y; } var pi = 3.141593; export { sum as add, pi} |
Here, we are exporting both sum and pi, but this time we have chosen to alias sum to add.
Let’s see how we use this in our file:
Code Listing: 113
import {add, pi} from './code-listing-112'; console.log("2 pi = " + add(pi, pi)); |
As you can see, we are simply referencing the named exports, which includes the alias for sum.
How about we flip this around? Let’s look at the following file:
Code Listing: 114
function sum(x, y) { return x + y; } var pi = 3.141593; export { sum, pi} |
Now let’s import this module as well as provide an alias in our file:
Code Listing: 115
import {sum as add, pi} from './code-listing-114'; console.log("2 pi = " + add(pi, pi)); |
This gives us a lot of flexibility when dealing with named exports. If we know that we already have a previous import that uses the same name, we can simply alias our import and avoid any type of conflicts. It is also possible to export another module inside your own after you have imported the other module.
Consider the following syntax:
Code Listing: 116
import {sum as add, pi} from './code-listing-113'; export {sum as add, pi} from './code-listing-113'; console.log("2 pi = " + add(pi, pi)); |
It will produce exactly the same output as well as export add and pi as named exports.
When authoring your modules, you have the ability to define a default export. This allows you to perform a basic import and receive the default functionality. Let’s first look at what we need to do to our file:
Code Listing: 117
export default function sum(x, y) { return x + y; } function notAvailable() { console.log("You can't call me..."); } export var pi = 3.141593; |
As you can see, the only difference here was that we added the default keyword to the first function.
Now, let’s look at the following file:
Code Listing: 118
import sum from './code-listing-117'; console.log('2 + 3 = ' + sum(2, 3)); |
If you know that you are only looking for the default export, this can be a handy feature. If you are authoring your own libraries and have a default export, this could become a nice, consistent feature. So even though we exported both sum and pi, all we had to do was name the reference sum and use it accordingly. The following is the output from running the preceding code:
Code Listing: 119
2 + 3 = 5 |
Note: You can only define one default export per module.
Wildcards allow us to load the entire module and refer to the named exports via property notation. Look at the following example:
Code Listing: 120
import * as math from './code-listing-113'; console.log('2 pi = ' + math.sum(math.pi, math.pi)); |
When we execute this code, we get the same result as the first import example:
Code Listing: 121
2 pi = 6.283186 |
Some of you may be wondering: is it possible to access functions or variables when we load the entire module using the preceding syntax? Consider the following code:
Code Listing: 122
export function sum(x, y) { return x + y; } function notAvailable() { console.log("You can't call me..."); } export var pi = 3.141593; |
How about the notAvailable function? Let’s see what happens when we try to access it.
Code Listing: 123
import * as math from './code-listing-122; math.notAvailable(); |
This is what makes me really love modules! You have the ability to decide what you wish to expose to the outside and what you want kept internal to your module. Observe what happens when we try to run the preceding code:
Code Listing: 124
TypeError: math.notAvailable is not a function |
Now that is pretty slick. The module loader recognizes that you have not identified notAvailable as a function you wish to export and make public, and a scoping error is thrown the moment you try to run the code.
This section addresses loader handle resolving module specifiers (the string IDs at the end of import…from), loading modules, and the like. The constructor is Reflect.Loader. Each browser or transpiler that implements ECMAScript 6 modules keeps a customized instance in the global variable System (the system loader), which implements its specific style of module loading.
In addition to the declarative syntax for working with modules that we have been examining, there is also a programmatic API. It allows you to do the following:
Programmatically work with modules and scripts.
Configure module loading.
You can programmatically import a module via an API based on ES6 Promises, which we will be discussing in a later chapter. For now, consider the following:
Code Listing: 125
System.import('some_module') .then(some_module => { // Use some_module }) .catch(error => { // Handle the error }); |
System.import() enables you to do the following:
System.import() retrieves a single module, but you can use Promise.all() to import several modules:
Code Listing: 126
Promise.all( ['module1', 'module2', 'module3'] .map(x => System.import(x))) .then(([module1, module2, module3]) => { // Use module1, module2, module3 }); |
We will cover how promises work in a later chapter.
More loader methods:
The module loader API has various hooks for configuration. It is still a work in progress.
The loader API will permit many customizations of the loading process. For example:
Configurable module loading is an area where Node.js and CommonJS are limited.