JavaScript module.exports, require, import, export, define ??
What the heck!
You might have seen such kind of expressions in various JavaScript sources. The basic idea behind all of these is Modularization. Wait! What?
Modularization
Modularization is simply making our source code split into logical units of independent, reusable code which become the building blocks of our application.
Initial releases of JavaScript up to version ES5 didn’t support this concept. But there were workarounds suggested by third parties. ES6 version of JavaScript now supports this concept natively. To be precise, the JavaScript engines should have been implemented with these concepts according to the specifications.
All the expressions like import, export, module.exports, etc. are the result of not having a native, standard way of modularizing in early stages. We will explore some of them.
Methodology
It is better to take an example to discuss this further. Following is a simple HTML page with a simple JavaScript file attached. It can maintain a playlist. Songs can be added by entering the name in a text box and clicking a button. The added songs are displayed in a list. List items are clickable and would play the song. Actually, it would give an alert. Nothing much. You can find the example here.
The idea is to apply Modularization concepts to this application following various methods which were used along the JavaScript evolution ES5 to ES6. First, we will have a look inside.
The HTML contains a list, a text box and a button. They have been given ids for binding events with JavaScript. The script attached is as follows.
There are some variables and functions declared with the old style of JavaScript (ES5). The code contains three sections. App section has the functionality of the handing button clicks and rendering. Playlist section contains the storage for the songs added. Song section has the structure of a song stored.
Problems in the code
There are two major problems in this code. The code is obviously hard to read. The other major problem is the way of declaring variables and functions without thinking of the scope they are going to be in, the global scope or the window object.
All the variables and the functions are accessible via the window object as we can see in the above image. Those can be easily spoiled with variables and functions from another script source. What would be the result if we assigned null to the function playSong()? The functionality can’t be guaranteed in this case.
Solutions
This way of coding is not accepted. We will apply solutions to this problem. Wrapping all of these code lines inside a function would separate the functionality from the global scope since the functions are having a separate scope in JavaScript. We need to keep a variable and assign this function which would become our first module. We also need to call this function down the line to make the effect of it.
The reason to call this function is the way of JavaScript looking at out code. JavaScript is an interpreted language. But actually, there is a compile-time before interpretation occurs. All the declarations of variables in our source code are scanned in the compile time. The definitions or assignment of values to the variables happens next in the interpretation phase. The new scope created for our first module is going to have fully defined variables and functions only after calling it.
Although we could control the variable-creation in the global scope, still there is one variable residing in it, the variable keeping the module function. We can use Immediate Invoking Function Expressions or IIFE style to overcome this. Our function becomes anonymous and remains nothing in the global scope. The solution can be found here.
With this modification, we could completely reduce the usage of global scope. But the code is still containing more lines. If we could split the code into logical units or modules, the readability would be high. We can identify three units in this code. Those are App, Playlist and Song.
We will assume that a single JavaScript file is going to be a single module containing each of the above three. Hence the same code now becomes four files and the HTML page is modified as follows.
According to the previous solution, there should be three IIFEs. But the linkage among all three modules matters which is impossible to achieve after this separation. Even the order of these three modules declared in the HTML page matters. All of these approaches become unstable due to these reasons.
Module formats and loaders
As a solution to the problems mentioned above, module formats and loaders come into the picture. Module formats and loader have been suggested and implemented by various third parties. A module format can solve the linkage problem among the modules. There are unique syntax patterns used by various module formats to declare the dependencies. But these patterns are not a part of the language. Loaders are required to solve this problem.
Most commonly used module formats are AMD and CommonJS. AMD format is popular in Browser-based JavaScript sources and CommonJS format is popular in server-side JavaScript developments like Node.js applications.
AMD format can be loaded with a loader like require.js and CommonJS format can be loaded with a loader like system.js. Node environments have the native support of CommonJS format and no loaders are externally supplied. But the browser-based environments should be treated differently.
AMD module format
AMD format uses a keyword called define to express dependencies among modules. We will use the AMD format in our application. The module APP uses the other two modules as dependent modules and declared as follows. Note that the array of modules contains the file paths relative to the module app.js without extensions.
We will use require.js as the loader. We need to include it in our application. The distributed source can be downloaded from the GIT location or a package manager like npm. The solution can be found here.
The HTML page is modified to import require.js rather than our app.js. But we need to inform require.js where to begin, the entry point. This is given by the attribute data-main of the script tag.
CommonJS module format
CommonJS introduces a kind of import-export mechanism to declare dependencies. Every module is going to export something which would be available to be imported by another module with the variable module.exports. The keyword used for importing is require. We will use this format in our application as follows.
The loader we are going to use is system.js. The distributed source can be downloaded from the Git location or a package manager like npm. The solution can be found here.
The configuration in the HTML page is as follows.
However, this style is commonly used in Node.js developments and the functionality of the loader is inbuilt.
ES6 style
With the evolution of JavaScript, the requirement of a proper standard of Modularization was found important and addressed. The specification has introduced two keywords for declaring dependencies among modules. Those are import and export.
We can export anything that should be available to be imported by another module with the keyword export. We can have a default export and any number of named exports per module. The new style is applied to our application as follows.
The HTML page is modified to include only the module app.js. But the way that the browser should treat this module should be given as an attribute to the script tag. The solution can be found here.
Today browsers don’t understand all the syntax patterns in ES6 style. Some of the features introduced in the new specification are supported and some of them are still not supported. Even the Node environments don’t understand this import-export syntax. But we can write our JavaScript code in ES6 style without thinking of the implementations by the engines. The magic is done by the Transpilers. The code written in ES6 style is converted to old-style by these. Babel is one of them.
You can find the Git repository containing all of these concepts in the following link.
Further reading …
There is a concept called bundlers. Webpack is an example. I would suggest you read on Transpilers, bundlers, Module formats and loaders more. We will discuss them further in the coming articles.