Originally, Javascript was used as a tool to create beautiful animations and interactions on the web, they were there to make websites look attractive and eye catchy, and that was all. But then, Google Maps was introduced, and that period represented when innovations and technologies were built using Javascript.
It was a whole new era with its quirks and downsides. Initially, javascript code didn’t exceed two to four lines, but as the complexity of websites and apps increased, the amount of javascript code shipped to the browser skyrocketed. As a result, we now have millions of javascript code running in the browser, thanks to the frameworks and libraries we use for development.
Managing and maintaining that much code requires a much more efficient and productive method, hence the creation of Javascript Modules.
In this article, I’ll explain how we can utilize the power of javascript modules in our project and why it’s a good pattern when developing javascript applications.
Prerequisites
Node installed on your local machine
yarn or npm installed on your local machine (npm comes pre-installed with node)
A text editor installed, e.g., VsCode.
Basic knowledge of HTML, CSS, Javascript, and the terminal (Check out this article for a refresher on them)
What is a Module?
A module is a self-contained javascript file containing variables, functions, or classes that can be available to other files across your project. Making a function or class available to other modules is called export. In contrast, using a function or class from another module is called import. Javascript Modules allow us to utilize this system of modules within our project, thereby enabling us to enjoy benefits like:
Code maintainability
Ease of code reusability
Code scalability
Javascript Modules allow us to break down and split our code into multiple and self-contained chunks that can be easily well-maintained and scalable should there be any need for any addition in the future. This feature has been available in NodeJS and in popular frameworks and libraries within the Javascript ecosystem for a while now. However, it’s only recently that this feature became available natively within the browser.
Looking at the Browser Compatibility Specs on MDN, it’s now supported in most popular browsers used by everyone.
Using Modules in a Project
To get started with modules in a project, your script tag in the HTML file needs to have the type
attribute included, like the snippet below:
Open your terminal and paste in the command below to create a new folder, navigate into it and create an HTML file named index.html
:
mkdir js-modules
cd js-modules
touch index.html
Open the folder in VsCode using the command below:
code .
Our project should look like this now:
Paste the starter code below in our index.html
file and create a javascript file called app.js
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Hello World</h1>
<script type="module" src="app.js"></script>
</body>
</html>
If you observe, the script tag in our HTML file has the type
attribute with a value of module
. This is because the app.js
file we just created would be a module.
Also, when using javascript files that are modules, we need to serve our HTML file using a server cause opening the file directly would throw a CORS error in the browser console due to our browser not identifying where the file is being served from. Not doing this would result in the error below:
We'll use Live Server in this tutorial. See this video on how to install it in VsCode.
Now, create another javascript file named utils.js and paste in the following snippets below:
const users = [
{
firstName: "John",
lastName: "Doe",
},
{
firstName: "Evan",
lastName: "Blacwk",
},
{
firstName: "Julian",
lastName: "Drey",
},
];
/**
* function to get all even numbers in an array of numbers
*
* @param {number[]} nums
* @return {number[]} Array of even numbers
*/
function getEvenNumbers(nums) {
if (nums.length === 0) return;
return nums.filter((num) => num % 2 === 0);
}
/**
* function to get all odd numbers in an array of numbers
*
* @param {number[]} nums
* @return {number[]} Array of odd numbers
*/
function getOddNumbers(nums) {
if (nums.length === 0) return;
return nums.filter((num) => num % 2 !== 0);
}
/**
* function to get all numbers greater than 5 in an array of numbers
*
* @param {number[]} nums
* @return {number[]}
*/
function getNumbersLargerThanFive(nums) {
if (nums.length === 0) return;
return nums.filter((num) => num > 5);
}
/**
* function to combine firstName and lastName into a fullname
*
* @param {string} firstName
* @param {string} lastName
* @return {string} FullName
*/
function combineNames(firstName, lastName) {
return `${firstName} ${lastName}`;
}
Using the export and import keyword
There are two main ways the export/import keyword can be used in a script, they are:
Using as a Named Export/Import
Using as a Default Export/Import
Named Exports
Let’s update the code snippet we pasted in our utils.js file by appending the export keyword before each function. Our code should look like this now:
export const users = [
{
firstName: "John",
lastName: "Doe",
},
{
firstName: "Evan",
lastName: "Blacwk",
},
{
firstName: "Julian",
lastName: "Drey",
},
];
export function getEvenNumbers(nums) {
if (nums.length === 0) return;
return nums.filter((num) => num % 2 === 0);
}
export function getOddNumbers(nums) {
if (nums.length === 0) return;
return nums.filter((num) => num % 2 !== 0);
}
export function getNumbersLargerThanFive(nums) {
if (nums.length === 0) return;
return nums.filter((num) => num > 5);
}
export function combineNames(firstName, lastName) {
return `${firstName} ${lastName}`;
}
The export keyword makes whichever variable, function, or class it’s appended to available to all other scripts in our project. If we have so many functions we want to export, adding the export keyword on each of them might be a task on its own, so there’s a much cleaner approach which is having a single export object at the end of the file as we have below:
const users = [
{
firstName: "John",
lastName: "Doe",
},
{
firstName: "Evan",
lastName: "Blacwk",
},
{
firstName: "Julian",
lastName: "Drey",
},
];
/**
* function to get all even numbers in an array of numbers
*
* @param {number[]} nums
* @return {number[]} Array of even numbers
*/
function getEvenNumbers(nums) {
if (nums.length === 0) return;
return nums.filter((num) => num % 2 === 0);
}
/**
* function to get all odd numbers in an array of numbers
*
* @param {number[]} nums
* @return {number[]} Array of odd numbers
*/
function getOddNumbers(nums) {
if (nums.length === 0) return;
return nums.filter((num) => num % 2 !== 0);
}
/**
* function to get all numbers greater than 5 in an array of numbers
*
* @param {number[]} nums
* @return {number[]}
*/
function getNumbersLargerThanFive(nums) {
if (nums.length === 0) return;
return nums.filter((num) => num > 5);
}
/**
* function to combine firstName and lastName into a fullname
*
* @param {string} firstName
* @param {string} lastName
* @return {string} FullName
*/
function combineNames(firstName, lastName) {
return `${firstName} ${lastName}`;
}
export {
users,
getEvenNumbers,
getOddNumbers,
getNumbersLargerThanFive,
combineNames,
};
Having done this, we can now import any of these functions in app.js
and make use of it below:
import {
getEvenNumbers,
getOddNumbers,
getNumbersLargerThanFive,
users,
combineNames,
} from "./utils.js";
console.log(getEvenNumbers([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])); // Prints [2, 4, 6, 8, 10]
console.log(getOddNumbers([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])); // Prints [1, 3, 5, 7, 9]
console.log(getNumbersLargerThanFive([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])); // Prints [6, 7, 8, 9, 10]
console.log(users.map((user) => combineNames(user.firstName, user.lastName))); // Prints ['John Doe', 'Evan Blacwk', 'Julian Drey']
We should get the output below in our console when we open our index.html
file in the browser:
NB: Since the curly braces for the export/import
looks like an object, we have the option of being able to rename a variable or function in an export/import
statement using the as
keyword like this:
// For export
export {
users as userList,
getEvenNumbers as extractNumbersDivisibleByTwo,
getOddNumbers as extractOddNumbers,
getNumbersLargerThanFive,
combineNames,
};
// For import
import {
extractNumbersDivisibleByTwo as evenNumbersFunc,
getOddNumbers as oddNumbersFunc,
getNumbersLargerThanFive,
usersList,
combineNames,
} from "./utils.js";
One other advantage of named import is the fact that we can import everything exported from the utils.js
file into a module object which we can then be accessed using the dot notation like this:
import * as utils from './utils.js';
console.log(utils.getEvenNumbers([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])); // Prints [2, 4, 6, 8, 10]
console.log(utils.getOddNumbers([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])); // Prints [1, 3, 5, 7, 9]
console.log(utils.getNumbersLargerThanFive([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])); // Prints [6, 7, 8, 9, 10]
This is very useful for situations where our utils.js
file might be used in multiple files. That way, the variables or functions can be named according to the file where it’s being used.
Default Exports
In a situation where we would like to have a default variable or function exported, we can include default
alongside the export
keyword prefixing the variable or function to be exported. Also, there can only be one default statement in a module. For example, let’s export our users
array as a default export below:
const users = [
{
firstName: "John",
lastName: "Doe",
},
{
firstName: "Evan",
lastName: "Blacwk",
},
{
firstName: "Julian",
lastName: "Drey",
},
];
/**
* function to get all even numbers in an array of numbers
*
* @param {number[]} nums
* @return {number[]} Array of even numbers
*/
function getEvenNumbers(nums) {
if (nums.length === 0) return;
return nums.filter((num) => num % 2 === 0);
}
/**
* function to get all odd numbers in an array of numbers
*
* @param {number[]} nums
* @return {number[]} Array of odd numbers
*/
function getOddNumbers(nums) {
if (nums.length === 0) return;
return nums.filter((num) => num % 2 !== 0);
}
/**
* function to get all numbers greater than 5 in an array of numbers
*
* @param {number[]} nums
* @return {number[]}
*/
function getNumbersLargerThanFive(nums) {
if (nums.length === 0) return;
return nums.filter((num) => num > 5);
}
/**
* function to combine firstName and lastName into a fullname
*
* @param {string} firstName
* @param {string} lastName
* @return {string} FullName
*/
function combineNames(firstName, lastName) {
return `${firstName} ${lastName}`;
}
export {
getEvenNumbers,
getOddNumbers,
getNumbersLargerThanFive,
combineNames,
};
export default users;
In our app.js
file, we can import the users
variable individually without having the curly braces as we had from the earlier examples. This is because our users
array is the default variable exported from utils.js
.
import {
getEvenNumbers,
getOddNumbers,
getNumbersLargerThanFive,
combineNames,
} from "./utils.js";
import users from './utils.js';
// The rest of the code
...
Or we could have it like this:
import users, {
getEvenNumbers,
getOddNumbers,
getNumbersLargerThanFive,
combineNames,
} from "./utils.js";
Conclusion
This article introduced you to Javascript Modules and how to utilize them in your project. I hope you’ve found it helpful.
If you have any questions or suggestions for me, feel free to drop a comment below. And if you are looking for additional resources, I’d recommend these ones:
Javascript Modules, MDN Docs
Modules Introduction, Javascript Info
ES6 In Depth: Modules, Mozilla Hacks