DotNetCurry Logo

Modules in ECMAScript 6 (ES6)

Posted by: Ravi Kiran , on 4/17/2015, in Category HTML5 & JavaScript
Views: 20941
Abstract: Modularization is highly essential to any large application. ES6 modules bring this feature to JavaScript and these modules provide a lot of options to export and import objects. This articles explores Modules in ES6.

One of the key features in any programming language used for large scale application development is modularizing code of an application. The concept has different names in different languages viz header files in C, namespaces in C++ and C#, packages in Java; but all of these address a common problem. As mentioned in the first article of this series ECMAScript 6 – New language improvements in JavaScript, initially JavaScript was not designed to write a lot of code with it, like creating huge frameworks, apps etc. As we started writing a lot of JavaScript and due to lack of support of modules in the language, open source developers came up with standards like CommonJS module pattern, Asynchronous Module Definition (AMD) and libraries, implementing these approaches. Over the past few years, these libraries have gained a lot of attention and they are successfully used in several applications of enterprise scale.

 

ES6 brings the feature of modules into the language. It is going to take some more time before browsers implement this feature, as they will have to define a way to download files on the fly. Before browsers implement this feature, we can use one of the available transpilers like Traceur, 6to5, ES6 Module Loader or any available converter to turn ES6 modules to ES5. Converters polyfill the feature using one of the existing methodologies.

JavaScript Modules Today

CommonJS Module System

CommonJS is a team of open source developers that have worked on designing some APIs and practices for JavaScript development. This team came up with a specification of modules in JavaScript. Every file is treated as a module and each file gets access to two objects: require and export. The object require is a factory function that accepts a string (name of a module) and returns object exported by the module. The export object is used to export objects and functions from the module. The require function returns this export object. Modules are loaded synchronously. The server side JavaScript engine Node.js uses this module system.

Asynchronous Module Definition (AMD)

AMD is a module system that loads dependent modules asynchronously. If the modules are in other files, they are loaded using XHR. Module depending on a set of other modules would be executed after the dependencies are loaded. An AMD module has to be a function passed as an argument to the define function. Return value of the function will be exported to the depending modules and these values are passed as arguments to the module function. The library require.js can be used to implement AMD in applications.

TypeScript Modules

TypeScript, the typed superset of JavaScript provides a module system. When it gets transpiled, it uses JavaScript module pattern. A TypeScript module is defined using module keyword and any objects to be exported have to be specified after the export keyword. The keyword import has to be used to load other modules into a module and to capture the exported object from the module. TypeScript modules are loaded synchronously.

ES6 Module System

ES6 module system is inspired from all the above existing module systems. It has the following features:

1. Exporting objects using export keyword. The keyword can be used any number of times

2. Importing modules into a module using import keyword. It can be used to import any number of modules together

3. Support for asynchronous loading of modules

4. Programmatic support for loading modules

Let us look at each of these features with code.

Exporting Objects

As in existing module systems, every JavaScript code file is a module in ES6. A module may or may not export any objects from it. Only the objects exported from a module are visible to the outside world. Rest of them remain private to the module. This behavior can be used to abstract details and export only essential features.

ES6 provides us with different ways to export objects from a module. They are discussed below:

 

Exporting Inline

Objects in an ES6 module can be exported in the same statement where they are defined. As export can be used any number of times in a module, all of the objects would be exported together. Following is an example:

export class Employee{
  constructor(id, name, dob){
    this.id = id;
    this.name=name;
    this.dob= dob;
  }

  getAge(){
    return (new Date()).getYear() - this.dob.getYear();
  }
}

export function getEmployee(id, name, dob){
  return new Employee(id, name, dob);
}

var emp = new Employee(1, "Rina", new Date(1987, 1, 22));

Here, the module exports two objects: class Employee, function getEmployee. The object emp remains private to the module as it is not exported.

Exporting a Group of Objects

Though inline export works, it is less useful in case of large modules, as we may lose track of the objects exported from the module. In such cases, it is better to have a single export statement at the end of the module specifying all objects to be exported from the module.

The above module can be re-written as follows to use a single export statement:

class Employee{
  constructor(id, name, dob){
    this.id = id;
    this.name=name;
    this.dob= dob;
  }

  getAge(){
    return (new Date()).getYear() - this.dob.getYear();
  }
}

function getEmployee(id, name, dob){
  return new Employee(id, name, dob);
}

var x = new Employee(1, "Rina", new Date(1987, 1, 22));

export {Employee, getEmployee};

It is also possible to rename the objects while exporting. Say, I want to export the class Employee as Associate and the function getEmployee as getAssociate. It can be done as follows:

export {
    Associate as Employee,
    getAssociate as getEmployee
  };

Default Export

An object can be marked as the default object to be exported from a module by using the keyword default. The keyword default can be used only once per module. It can be used in both inline as well as group export statements.

Following statement marks a group export statement as default:

export default {
    Employee,
    getEmployee
};

Importing Modules

Existing modules can be imported into other modules using the keyword import. A module can import any number of modules. Out of the objects exported by an imported module, the importing module may refer all, none or some of the objects. The objects to be referred have to be specified in the import statement. Following listing shows different ways of importing modules:

Importing with no Objects

If a module contains some logic to be executed and doesn’t export any objects, it can be imported to another module without expecting any objects from it. Following is an example of this:

import './module1.js';

Importing with Default Object

If a module exports an object using the default keyword, the object can be directly assigned to a reference in the import statement. Following is an example:

import d from './module1.js';

Importing with Named Objects

As discussed in the previous section, a module can export a number of named objects. If another module wants to import the named objects, it needs to specify them in the import statement, as shown below:

import {Employee, getEmployee} from './module1.js';

It is also possible to import both default and named objects in the same statement. The default object must have an alias name defined in such cases. It is shown below:

import {default as d, Employee} from './module1.js';

Importing with All Objects

In the case of named importing, only the objects specified in the import statement would be imported and other export objects won’t be available to the importing module. Also, consumer should know about the objects exported to use them. If a module exports a large number of objects and the consuming module wants to get all of these, it has to use an import statement similar to the following one:

import * as allFromModule1 from './module1.js';

The alias, allFromModule1 will have references to all objects exported from module1. They can be accessed as properties in the importing module.

 

Importing Programmatically On-demand

If a module needs to load another module based on some condition or, wants to defer loading of the module till certain event happens, it can do so by using the programmatic API of loading modules. The modules can be loaded programmatically using the System.import method. This is an asynchronous method and it returns Promise.

Syntax of the method is shown below:

System.import('./module1.js')
    .then(function(module1){
        //use module1
    }, function(e){
        //handle error
    });

The promise passes if the module is successfully loaded and it passes the export object of the module to the success callback. The promise fails if there is a typo in name of the module or if the module doesn’t load for some reason like network latency.

Using ES6 Modules Today

As ES6 modules are not supported natively in any of the browsers as of now, we need to use a transpiler to get the code converted to ES5 before we load it on a browser. As I have been using Traceur as my choice of transpiler till now, let’s use the same tool to convert the modules to a browser-understandable way. Let’s see the different ways in which we can transpile the ES6 modules.

 

Transpiling ES6 Modules On the fly using Traceur

ES6 modules can be transpiled on the fly after loading the script on the browser using Traceur’s client-side library. We don’t need to run any commands to make the modules run if we use this approach. All we need to do is, load the traceur library on the page and add script to run the WebPageTranscoder.

<script>
    new traceur.WebPageTranscoder(document.location.href).run();
</script>

Now, we can import any ES6 file inside a script tag with type assigned as module.

<script type="module">
    import './modules/import1.js';
</script>

Any script tag with type assigned as module would be picked by the ES6 client-side library and is processed by it. The import statement in the above snippet makes an AJAX request for the corresponding JavaScript file and loads it. If this module internally refers to another module, a separate AJAX request will be made to load the file corresponding to that module.

Transpiling ES6 Modules using Traceur Commands

ES6 modules can be transpiled to AMD or, CommonJS modules using commands exposed by Traceur. There are two advantages of using this approach over the on the fly transpiler:

1. Browser won’t have to perform additional work to transpile the modules

2. If an application is already half-built using ES5 and it used either AMD or CommonJS module systems, other half of the application can be built using ES6 and transpiled into one of these module systems, instead of migrating entire application to ES6 immediately

To be able to use the transpiled AMD/CommonJS modules, we need to include the libraries supporting the module system. AMD is my personal favorite, so I will cover it here. The process remains same for CommonJS modules as well.

Following is the command for transpiling a folder containing ES6 modules to AMD and store them in a separate folder:

traceur --dir modules es5Modules --modules=amd

To use CommonJS, you need to assign value commonjs to the modules flag in the above command.

Here, modules is the folder containing ES6 code and es5Modules is the output directory. If you check any file inside the folder es5Modules, you would see the AMD define blocks inside it. As require.js supports AMD, we need to add a script reference to require.js on the HTML page and specify the start file in data-main attribute, as shown below:

<script src="bower_components/requirejs/require.js" data-main="es5Modules/import2.js"></script>

Transpiling ES6 Modules using Traceur Grunt Task

Transpiling modules using commands can be tiring and error prone at times. We can automate the transpilation process using the grunt-traceur task. For this, you need to install the NPM package of the task.

npm intall grunt-traceur –save

The data that we need to provide to the Grunt task is same as the data provided to the command. Following is configuration of the task:

traceur: {
  options: {
    modules: "amd"
  },
  custom: {
    files: [{
      expand: true,
      cwd: 'modules',
      src: ['*.js'],
      dest: 'es5Modules'
    }]
  }
}

Now you can run the above grunt task on the console using the following command:

grunt traceur

As you see, it produces same result as we got by running the command.

Conclusion

Modularization is highly essential to any large application. ES6 modules bring this feature to JavaScript and these modules provide a lot of options to export and import objects. I look forward to that day when this feature would be in the browsers and we don’t need to add any third party library to create and load JavaScript modules. The popular client side MV* framework Angular.js uses ES6 modules instead of its own module system in its version 2, which is still under development.

Let’s start using the module system to make our code more organized and readable!

Download the entire source code of this article (Github)

Was this article worth reading? Share it with fellow developers too. Thanks!
Share on Google+
Further Reading - Articles You May Like!
Author
Rabi Kiran (a.k.a. Ravi Kiran) is a developer working on Microsoft Technologies at Hyderabad. These days, he is spending his time on JavaScript frameworks like AngularJS, latest updates to JavaScript in ES6 and ES7, Web Components, Node.js and also on several Microsoft technologies including ASP.NET 5, SignalR and C#. He is an active blogger, an author at SitePoint and at DotNetCurry. He is rewarded with Microsoft MVP (Visual Studio and Dev Tools) and DZone MVB awards for his contribution to the community


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!