How the module system works in JavaScript

Posted by

We can define a system as a set of related components working together to solve a particular task. From this definition, we can imply that the JavaScript module system is a set of features that can be lazily loaded into an application when desired to solve a particular problem.

These features are included in an application to form a fully functional system. Before we started developing applications using technologies such as node, applications used very few lines of JavaScript and there was no need for modules.

With the growth of technology, applications have become larger and larger which translates to more JavaScript code for the business logic.

To manage the many script files, we leverage the module system which helps to break the large application into small programs that can be loaded into the application when needed.

In this tutorial, we will learn how the module system works. We will also cover different ways that we can implement the module system for the benefit of the system.

The export keyword

When we want to define a feature that we will use in another module of the application, we always use the export keyword.

The keyword can be used with const, let, var, functions, and classes. Note that the keyword can only be used at the top level. For example, if you try to use it inside a function, it will not work.

Open the IntelliJ development environment and press File > New > Project to create a new Node.js application. On the window that opens, select Node.js, enter the project name as module-system on the Location section, and press the Create button to generate the project.

Under the root folder, create another folder named string-utility. Create a file called string-utils.js under the string-utility folder and copy and paste the following code into the file.

export {theMessage}

const theMessage = "Welcome to Jurassic world"

In this code, we have created a read-only variable that holds a variable of type string. The export keyword is prefixed on the string to indicate that it will be used in another module.

The import keyword

When we want to use a feature from another module in another module, we always use the import keyword.

Create a file named app.js under the root folder and copy and paste the following code into the file.

import {theMessage} from "./string-utility/string-util.js";

console.log(theMessage);

This code imports the string variable that we exported in the previous file and logs its content to the console.

If you want to import more features on the same file, you can add the name of the feature in the curly brackets, each separated by a comma.

The from keyword of the import statement specifies the absolute path in which the code is implemented.

We can also you the relative path on our import to scan the file from the root folder, but the absolute approach is preferred because it makes the path portable and shorter.

Run the app.js file using the following command.

node app.js

output:

Welcome to Jurassic world

The following code shows how to import a feature using the relative path. Note that the relative path provides the root directory of the application.

import {theMessage} from "../module-system/string-utility/string-util.js";

console.log(theMessage);

Using default exports

When we want to export multiple features in another module, we use the export keyword and add the names of the features inside the curly brackets as we did with the string variable, then separate each of them with a comma.

We will often hear this being referred to as named exports. The default exports are features that are exported with no curly brackets. This is attributed to the fact that there can exist only one default feature in a module.

Copy and paste the following code into the file named string-util.js after the const variable named theMessage.

export default function upperCaseMessage(){
    return theMessage.toUpperCase();
}

We have defined a default function named upperCaseMessage() that converts the string we declared to the upper case.

As you can see, no curly brackets were used for the default export. The following code shows how to import the default type to another module. Note that the import syntax also does not the curly brackets.

Copy and paste the following code into the file named app.js after the console log() method.

import upperCaseMessage from "./string-utility/string-util.js";

function showUpperCaseMessage(){
    console.log(upperCaseMessage())
}
showUpperCaseMessage();

Use the same command as the one we use used in the previous example to run the code to verify that the output is as shown below.

WELCOME TO JURASSIC WORLD

Conflicts in naming module features

When we are making use of multiple modules in our application, we may have features that have the same, and these might lead to errors in our application.

When we try to import the features using the same names but referencing a different path, we get the syntax error: Identifier has already been declared.

We can always rename these features when exporting or importing using the as keyword. The best-preferred way of renaming the features with conflicting names is during the import because the developer does not have control over the third-party modules.

Copy and paste the following code into the file named string-util.js after the upperCaseMessage() function.

export function applyTextColor(message){
    return message
}

Under the string-utility folder, create another file named string-util-two.js and paste the same code we have copied above into this file.

export function applyTextColor(message){
    return message
}

Note that these methods are created in two separate modules with the same name but their functionality is different from the other.

Copy and paste the following code into the app.js file after the showUpperCaseMessage() function.

import {applyTextColor as redText} from "./string-utility/string-util-two.js";
import {applyTextColor as blueText} from "./string-utility/string-util.js"

function displayRedText(){
  console.log(redText("Apply red color"))
}
displayRedText()

function displayBlueText(){
   console.log(blueText("Apply blue color"))
}
displayBlueText();

To avoid the syntax error while importing the two functions in the app.js file, we have renamed the first function to redText and the second function to blueText as shown above.

Run the above code using the same command used in previous examples, and note that the code executes without any errors.

Apply red color
Apply blue color

Using an object of a module

In all the examples we have covered till now, we have seen that the imports can be very many in one file getting challenging to track the source of a feature that is used in an application.

We can create an object for a module that contains all the exports for that module and we only need the name of that module whenever we need to access a feature.

Create a file named string-util-three.js under the string-utility folder and copy and paste the following code into the file.

export function getGender(gender){
    if (gender.toUpperCase() === "male".toUpperCase()){
        return "MALE"
    }else {
        return "FEMALE"
    }
}

Copy and paste the following code into the app.js file after the displayBlueText() function.

import * as Gender from "./string-utility/string-util-three.js"

function getGenderByName(){
 console.log(Gender.getGender("female"))
}
getGenderByName();

Note that we have created an object named Gender using the syntax * as. The asterisk means that it will import all the features that we exported from the module.

The getGender() method has been invoked using the module object that has access to only one function.

Run to verify that the code works as expected.

FEMALE

Using classes with modules

Object-oriented programming defines a class as a blueprint that can be used to create objects. In JavaScript, we can export and import classes into different modules, and the syntax to achieve this is as shown below.

Create a file named class-util.js under the string-utility folder. Copy and paste the following code into the file.

export class Customer{
    constructor(firstName, lastName, email) {
        this.firstName = firstName
        this.email = email
        this.lastName = lastName
    }
}

Copy and paste the following code into the app.js file after the getGenderName() function.

import {Customer} from "./string-utility/class-utlil.js";

function findCustomerEmail(){
    let customer = new Customer(
        "john",
        "doe",
        "john@gmail.com");
    console.log(customer.email)
}
findCustomerEmail();

The syntax is the same as the one for functions and variables. If you have multiple classes in the same file you can separate them with a comma during import or create a module object for the classes.

Run the code and ensure the code outputs the following.

john@gmail.com

Conclusion

This tutorial has covered important concepts about the module system but has not exhausted everything. If you want to read more about the module system go to the JavaScript documentation which covers more concepts in detail. Note that the final imports in the top-level module named app.js should be as shown below.

import {theMessage} from "../module-system/string-utility/string-util.js";
import upperCaseMessage from "./string-utility/string-util.js";
import {applyTextColor as redText} from "./string-utility/string-util-two.js";
import {applyTextColor as blueText} from "./string-utility/string-util.js"
import * as Gender from "./string-utility/string-util-three.js"
import {Customer} from "./string-utility/class-utlil.js";

Leave a Reply

Your email address will not be published. Required fields are marked *