JavaScript module.exports, require, import, export, define ??

What the heck!

Vishwa Jayaratne
6 min readJul 29, 2019

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.

Playlist application: rendered view

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.

Playlist application: HTML source

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.

Playlist application: JavaScript source

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.

Window object: all the declared variables and function

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.

First module: wrapping inside a function

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.

First module: IIFE

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.

Three modules: included in the HTML page

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.

Module format: app.js in AMD format

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.

Module format: require.js loader in use

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.

Module format: CommonJS used in three modules

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.

Module format: system.js loader in use

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.

Modules: application in ES6 style

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.

Modules: ES6 style in use

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.

--

--