left-icon

ECMAScript 6 Succinctly®
by Matthew Duffield

Previous
Chapter

of
A
A
A

CHAPTER 8

Modules

Modules


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.

Export

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.

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.

Default

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.

Wildcard

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.

Module loaders

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.

Importing modules and loading scripts

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:

  • Use modules inside <script> tags where module syntax is not supported.
  • Load modules conditionally.

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:

  • System.module(source, [options]) – evaluates the JavaScript code in source to a module (which is delivered asynchronously via a promise).
  • System.set(name, module) – is for registering a module (e.g. one you have created via System.module()).
  • System.define(name, source, [options]) – both evaluates the module code in source and registers the result.

Configuring module loading

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:

  1. Lint modules on import, e.g., via JSLint or JSHint.
  2. Automatically translate modules on import, even if the modules contain CoffeeScript or TypeScript code.
  3. Use legacy modules like AMD and Node.js.

Configurable module loading is an area where Node.js and CommonJS are limited.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.