Published in March 04, 2021

JavaScript Module Structure

Know the module structure

js-module-structure-anete-lusinaImage via pexels

Modules are everywhere these days in the JavaScript world. Without them, programmers would be forced to write and maintain every functionality required - whether minuscule or complex - for their program, which of course will be difficult, time-consuming and expensive. Furthermore, Some of the popular libraries and frameworks used today for modern web development comes as packages, which are modules wrapped together. Libraries are plugged into programs to provide the required feature, while frameworks are used to build programs. Modules provide extendable functions, structure and maintainability, but what brought about the idea for modules? How is it structured? How are the modules imported? These are the questions I will attempt to answer in the article.

Modules.

Programs grow organically, overtime the code is extended and new features are added. This requires the developer to start thinking about ways to structure and maintain the structure of the codebase. Structuring makes a codebase easy to explain, and each part of the program plays a well-defined role. This is hard work that can only pay off in the future when a different developer is working on that code. but what happens when this is neglected, as is often the case with some developers, allowing parts of the program to be entangled with each other?

First, understanding how the program works becomes difficult. since every part is touching other parts, it becomes quite difficult to work with the codebase. To understand the code, you would need a holistic view of the project. Secondly, No part is well defined. If you intend using a piece of code from the codebase, since no part is well-defined, it will do you better writing that piece yourself, rather than trying to detach it.

What is a Module?

A module is a piece of program that specifies other pieces it depends on, and provides an interface (functionalities) for other programs or module to use. Modules are a way to avoid having an entangled codebase.

Module interfaces are a part of the module that is made public to other programs, the rest of the parts are kept private (similar to JavaScript Object interfaces). Only well-defined pieces connect to other modules or programs.

Modules as Dependencies.

A module can depend on another module for a piece of code to function. The relationship between to modules is known dependencies. To know the specific program a model depends on, it is specified in the module itself. This tells the module what needs to present to be able to use a given module and to automatically load dependencies.

Modules as Packages.

A package is a packaged chunk of code that can be downloaded or installed from a registry or package manager. It is made up of modules and has documentation about other packages it may depend on and how the package itself works so others who didn’t write it will find it easy to use. The advantage of using a package is to provide a means to plug features into other programs and to prevent duplication. Everything resides in a neatly coupled pack that can be updated with new features. When a package gets updated, other programs that depend on it can also update the copy of that same package that they depend on via the package manager. This is a safer way than having to do the updates manually in all programs that makes use of that package.

NPM.

The Node Package Manager (NPM) is an infrastructure to store, install from and upgrade packages. NPM is both an online service to upload packages and a program for installing, and managing packages bundled with NodeJS.

JavaScript Module System.

The JavaScript programming language had no way of using a built-in module system, not until 2015. Before that, developers built large systems using custom modules that they designed on top of the language. They did this by using functions to create local scope and object to represent module interfaces.

For example, below is a module for getting month names and numbers in a year. The interfaces (montOfYear.name & monthOfYear.number) are returned by an object. The local bindings are hidden in a function expression that is immediately called.

const monthOfYear = function() {
    const names = [
        'January',
        'February',
        'March',
        'April',
        'June',
        'July',
        'August',
        'October',
        'November',
        'December'
  	];

    return {
        name(number) {return names[number]},
        number(name) {return names.indexOf(name)}
    };
}();

console.log(monthOfYear.name(11));
// ->  November

cool, right? However, this type of module provides isolations only to a certain degree. unlike the standard module system, it doesn’t state what it depends on. Its interfaces are returned by an object into the global scope, waiting for any dependent program to use it. For a long time, this was the main approach used in front-end web programming. Developers now use the standard built-in module system. Don’t be surprised if you stumble on a codebase implementing this [obsolete] method though.

Evaluating string as code.

To bring dependency relations to code, dependencies have to be loaded into the code. To achieve that, JavaScript strings have to be read as code. One way to do this is by using the Function constructor that takes in two arguments: a string of argument separated by a comma and another string containing the function body. The code is evaluated and wrapped in a function value, thus creating a scope that won’t interfere with other scopes.

const whoAmI = Function("name", "console.log('My name is' + n);");
whoAmI('Joe');

// -> My name is Joe

This is the idea behind a module system. Module code can be wrapped in a function, which in turn makes the function’s scope the module’s scope.

CommonJS.

CommonJS is an approach to loading dependencies in NodeJS and the system used by most NPM packages. It uses the require() function to load dependency and return its interfaces. The loader creates a local scope for the module by wrapping it in a function. The interfaces are put in an object that exports them. This is similar to the previous examples given above.

Here is an example, the code below is a module that formats date. It uses two NPM packages — ordinal to convert numbers to positions in strings, like “1st” and “2nd”, and date-names to get the English name for weekdays and month. It exports a function called formatDate with two arguments — a Date object and a template string.

The template string states how the date should be formatted. a format string like “MMM Do YYYY” will output “June 25th 2021”.

The interface for ordinal is a single function, but date-names exports an object containing two interfaces — days and names. De-structuring is used to extract the interfaces from the object.

const ordinal = require("ordinal");
const {days, months} = require("date-names");

exports.formatDate = function(date, format) {
    return format.replace(/YYYY|M(MMM)?|Do?|dddd/g/, tag => {
    	if (tag == "YYYY") return date.getFullYear();
    	if (tag == "M") return date.getMonth();
    	if (tag == "MMM") return months[date.getMonth()];
    	if (tag == "Do") return ordinal(getDate());
    	if (tag == "dddd") return days[date.getDay()];
   	});
}

The module exports its interface so that other programs (modules) that depend on it can make use of it.

const {formatDate} = require("./date-formatter");

const date = new Date(2021,6,25);
const format = "dddd the Do";

console.log(formatDate(date, format));
// -> June the 25th

ECMAScript Modules.

Although the CommonJS module worked quite well for importing and installing module via NPM, it was a patch-on system to JavaScript.

In 2015, the JavaScript standard, ECMAScript or ES, in short, introduced its own “different” module system. It maintains the idea of dependency and interfaces, but the implementation and details differ. One thing is clear, the syntax notation changed and the system has been integrated into the JavaScript language. Rather than calling a function, you can use the special import keyword. The export keyword is used, similarly, to export code — variables, functions and classes. If you have a Python programming background then the import keyword should be familiar, although a little bit different, the idea is the same.

import {days, months} from "date-name";
import ordinal from "ordinal";

export function formaDate(date, format) {/* ... */};

In ES, modules can export more than one thing at a time. This is because the interfaces are not a set of values, but name binding, just like variables. In the code above, formatDate is bounded to a function. When you import it from another module, you’re importing the binding and not necessarily the value. The export module can change its binding at any time and it will reflect in whichever module depends on the interface

To have a default bind, you have to affix the default keyword — after the export keyword — to an expression, function or class value in the exporting module. When the default keyword is detected, the value is treated as the module’s main export value. When importing a module without a {} around its binding name, you get its default value, if available. Such a module, can still export other bindings under a different name, alongside the default binding.

export let name1, name2, ..., nameN;

//Default export
export default function name() {/* ... */};

Module can be renamed when import using the as keyword.

import {days as dayNames} from './datenames';

console.log(dayNames.length);
// -> 7

JavaScript Code Bundling.

You probably have come across JavaScript files with compact code. Everything in the files is bundled together, making it difficult to understand a thing from it. Don’t bother about such files. They are not for human reading but contain translated and compressed code.

Before diving into why that is, you should understand how that came to be.

A lot of the programs in JavaScript aren’t [technically] written in JavaScript. People use extensions to the language to write programs. One popular example of such an extension is HypeScript, sorry [clears throat], I mean TypeScript. The JavaScript type checking extension.

This is made possible by compiling code and translating it from a chosen extension to normal plain JavaScript or its older versions so older browsers can execute the code.

Another reason is latency. For example, having multiple files of programs on a web page can be problematic. The time taken to fetch those files simultaneously will affect the speed of the website. Fetching a single big file is faster than fetching a lot of modular files. Knowing this, web developers built and used tools that would bundle their programs into single files. Such tools are known as Bundlers.

Furthermore, the size of that file mattered too. Sizes determine how fast data can be transferred over network. To curb this, Minifiers were invented. A minifier takes a file and makes in smaller in size by removing comment and white space, renaming binding and replacing pieces of code, where possible, with counterpart code that take less space.

Modern JavaScript code go through multiple stages of transformation --- from modern code to legacy supported by older system, from ES module to that of CommonJS, then to bundling and minifying.

Any modern code running on website or as a package is mostly not the code as it was written.

Summary.

Modules are a way of providing structure to programs by splitting code into pieces that provide interfaces and dependencies. Interfaces are the parts of code that are visible to other programs, and the dependencies are other programs or modules that depend on it.

Packages are used for providing features to programs and to avoid duplications. Unlike normal JavaScript modules, packages are installed into the package manager and are upgraded via a registry — NPM. The Node Package Manager (NPM) is a registry for downloading different kinds of JavaScript Packages.

CommonJS is a bolted-on for JavaScript to have a module system that it lacked. JavaScript now has a built-in module system that coexists with CommonJS.

Bundlers are used to translate code from extensions and to convert code from modern to legacy code for older systems — browsers. And in relation, minifiers are used to compress code from its raw state to a fixed state to reduce file size and latency.


Hi, my name is Romeo Peter. I'm a self-taught software developer and blogger --- I build web application, and automate business processes. I don't know it all, but what I do know, I manage to share for others to learn.

Follow me on Twitter. I share my thoughts there.

I'm available for work. Contact me to work on a project or work with you. I usually get back immediately.