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)
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
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