JavaScript Modules: Strategies to Improve Code Quality and Efficiency

Büşra Özmen
ÇSTech
Published in
7 min readNov 8, 2023
designed by Elif Yardımcı

JavaScript is one of the milestones of web development. It is used to increase interaction on websites and add dynamism to the websites. The size of JavaScript varies depending on the size of websites. Since the JavaScript used in large projects will be large, the code must be clean, readable, and reusable. JavaScript Modules are used for this. In this article, I will explain what the JavaScript Module concept is, why it is needed, what solutions have been developed, how it is used, and what advantages it offers.

What is the concept of JavaScript Modules?

JavaScript modules are used to split your code into smaller, more manageable pieces rather than keeping it in a single file and moving those pieces to other files or projects. Modules can contain functions, variables, and objects that perform specific functions. With modules, code complexity decreases, code readability and reusability increase significantly and repetition is minimized.

Why are JavaScript Modules needed?

When JavaScript first came out, people’s interaction scenarios on websites were limited. For this reason, JavaScript files were small and consisted of code fragments. But over time, websites grew and many more scenarios emerged. With these developments, JavaScript codes have also grown and become more complex. As the codes grew larger, it became harder for developers to keep track. This is where JavaScript modules came into play. Instead of writing all the codes in a single file, the codes began to be written in modules.

Module Solutions

When JavaScript modules first came into use, there was no official standard or structure. That’s why different developers created their own solutions. CommonJS, AMD(Asynchronous Module Definition), UMD(Universal Module Definition), and ES Modules are widely used JavaScript module systems. Now let’s examine these module systems.

CommonJS

CommonJS aims to provide a standard way of writing JavaScript, especially for server-side applications. It has been used especially in Node.js projects. It was developed in 2009 by Kevin Dangoor, who worked as a software engineer at Mozilla Corporation. The module.exports object is used to export the codes in a module. We can use the require method to import these codes in another file. These operations ensure the synchronous loading of modules. Now, let’s take a look at its usage.

//user.js
function User(fullName, phone) {
var returnObj = {
fullName : fullName,
phone : phone,
contactInfo: 'Contact Info: ' + fullName + ' ' + phone
}
return returnObj
}

module.exports = User;
//main.js
const User = require('./user.js');
const person = User('James Bond', '5555555555');

console.log(person.contactInfo) -> Contact Info: James Bond 5555555555

AMD (Asynchronous Module Definition)

AMD is a module system designed specifically for client-side applications. It was developed by James Burke, who worked at Mozilla Corporation, in 2011 (https://github.com/amdjs/amdjs-api). Unlike CommonJS, modules are loaded asynchronously in AMD. It is a system adopted by the RequireJs library and is better in terms of performance and usability because it is loaded asynchronously. The define method is used to define modules. Let’s take a look at AMD’s usage.

//user.js
define([], function() {

return {
fullName: function() {
console.log('James Bond');
},
phone: function() {
console.log('5555555555');
}
};
});
//main.js
define(['user', 'otherUser'], function(user, otherUser) {
console.log(user.fullName()); -> James Bond
});

UMD (Universal Module Definition)

UMD is a universal module system that supports features from AMD and CommonJs, which can be used in both client-side and server-side applications. UMD controls the environment in which the module is run. It provides the flexibility to use it on both the client and server side. You can review this module system, developed in 2015, from Addy Osmani’s repository: https://github.com/umdjs/umd
Due to its syntax structure, I think it may cause complexity in projects.

//user.js
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
//for AMD
define([], factory);
} else if (typeof exports === 'object') {
//for CommonJS
module.exports = factory();
} else {
//for Browser
root.User = factory();
}
}(typeof self !== 'undefined' ? self : this, function () {
//Methods
function contactInfo(fullName, phone){
return 'Contact Info: ' + fullName + ' ' + phone;
};
//The part of the module to be exported
return {
contactInfo: contactInfo
}
}));
//main.js
var User = require('./user');
var result = User.contactInfo('James Bond', '5555555555');
console.log(result); -> Contact Info: James Bond 5555555555

ES Modules

ECMAScript (ES) Modules represent the entry of the JavaScript world into a more modern era. Module support was added to JavaScript with ECMAScript 6 (ES6) in 2015. It can be used in client-side and server-side applications and provides support for asynchronous module loading. Syntax of the ES Module is also much simpler than other module solutions. It manages the data flow between modules using the import and export keywords.

Among the solutions offered for modules, the current solution is ES Modules. However, it took quite a long time to reach this official solution. The different module systems created in this process have led to incompatibility and complexity in the JavaScript ecosystem.

Now, let’s take a look at the use of ES Modules.

//user.js
export const user = (fullName, phone) => {
var returnObj = {
fullName : fullName,
phone : phone,
contactInfo: 'Contact Info: ' + fullName + ' ' + phone
}
return returnObj
}
//main.js
import { user } from './user.js'
const person = user('James Bond', '5555555555');

console.log(person.contactInfo) -> Contact Info: James Bond 5555555555

Since ES Modules are the current method used at the moment, I would like to discuss some terms and usage patterns related to import and export in subheadings.

export default: We can export multiple functions, variables, or objects to a different file by using the ‘export’ keyword. In the example above, if there was an age calculation function in addition to the ‘user’ function, we could export it by adding the ‘export’ keyword to the beginning of this function in the same way. Additionally, we could export the two functions without using the ‘export’ keyword as follows: export {user, ageCalculate}While importing, we could also import two functions using the ‘import’ keyword: import {user, ageCalculate} from ‘./user.js’This usage method is very useful in cases where we want to transfer more than one function, variable or object. However, if we only want to export a single function in a file, the ‘export default’ keyword may be more useful. We can import the function we exported with ‘export default’ with a different name. In the example below, the results for ‘User’ and ‘Profile’ will be the same.

//user.js
export default User = (fullName, phone) => {
......
}
//main.js
import User from './user.js'
//or
import Profile from './user.js'
  • alias: When we want to change the name of a function while importing it, we can use the “as” keyword. This method is generally used to prevent functions with the same name from conflicting in different files.
import {user as person} from './user.js'
  • namespaces: We can use “* as” keywords to import all functions within a single object. We can import all the functions we export in a file with a single name.
import * as profile from '/user.js'
profile.ageCalculate();
profile.user();
  • combine exports: When we need multiple modules in a single file, we can create a new file and import the modules we want to use there. When we need several modules in one file, we do not need to open a new file. However, when we need a lot of modules, we can collect the modules in a new file as in the example below. Then, we can write much cleaner code using this module directly. I use this method frequently and I think it increases the readability and reusability of the code.
// combine.js
import * as profile from 'user.js';
import * as calc from 'calc.js';
....
// main.js
import * as combine from 'combine.js'

combine.profile.ageCalculate();
  • dynamic import: We can use the dynamic import feature to import the module we want to use only when necessary. In this way, we can make the code more efficient and control the modules we need better. You can see an example of using dynamic import in the code block below.
//main.js
if(a == b) {
const {user} = await import('./user.js');
user('James Bond', '5555555555');
}

The Advantages of Modules

Let’s summarize the advantages I mentioned when talking about modules.📝

  • Code Readability
  • Reusability
  • Ease of Teamwork
  • Ease of Debugging
  • Performance Improvements

Conclusion

JavaScript modules are an essential part of modern web development. It is the preferred tool for developers to split code into small independent pieces and use these pieces as needed. They enable better organization and reuse of code in small and large-scale applications and make the development process more efficient.

The modules I use frequently make my development process easier. Thanks to modules, I think that making the code more readable and understandable is important for the sustainability of the project. For this reason, I recommend that every developer learn about modules and use them in their projects. 🤗
Best regards.

Resources

--

--