What are modules?
When programming was in its very early stages, there wasn't any kind of a notion of modules. Programs were written in one large chunk, more or less.
Slowly, with time, as this turned into a less maintainable and tedious approach, came in the idea of modules and modular programming.
So what exactly are modules?
Well, simply put:
Essentially, modules are the means of breaking down a large piece of code into multiple files and, thus, make development less of a hassle.
The practice of developing software around a system of modules is referred to as modular programming. In today's era, complex software is almost always built using modular programming principles.
There are many benefits of using modules instead of coding everything in a single file:
- Better maintainability: with smaller modules, it's much easier to maintain code and only touch upon those modules that require patches and updates.
- Reusability: with a module addressing a specific concern in a given project, it's possible to reuse it in other parts of the project, or even publish it as a third-party module for others to use.
- Encapsulation: a module effectively hides its internal workings from the outside world which is more than just desirable for large projects where name collisions are highly possible. Thus, we say that modules provide encapsulation for the code that they contain.
- Team work and collaboration: when a code base is split up into multiple modules, each with its own concern, it becomes possible for multiple teams and developers to work on the code base simultaneously, thus promoting easier and effective team work and better collaboration.
Without any doubt, modules are one of the coolest features of programming languages and one that all programmers should leverage or, at the very least, know how to leverage in their projects.
In languages that support modules, the typical behavior is to have:
- A means of importing stuff into a module, often known as defining the module's dependencies.
- A means of exporting stuff from a module, often known as the module's public interface.
Many of the mainstream languages today, such as Java, Python, PHP, Ruby, Go, support modules out of the box.
<script> tag approach for modularization.
A better approach was needed.
CommonJS in Node.js
Since the Node.js runtime was completely different than the browser environment, for example, with utilities to work with the underlying filesystem directly, a dedicated modular system was needed in Node.js. A working group was initiated by Mozilla engineer, Kevin Dangoor, to discuss about a specification for implementing modules in Node.js.
Initially called ServerJS, the group drafted a specification called CommonJS Modules/1.0. It's commonly referred to just as the CommonJS specification.
The proposition was very basic: a
require() function would be provided in order to import a given module into another module, while a
module object would be provided, with an
exports property, to hold the exports of the module.
The CommonJS specification quickly became superbly popular by virtue of its simplicity and adoption in Node.js. As more and more people embraced Node.js, so did they embrace CommonJS.
Unfortunately, one issue with CommonJS was that
require() was synchronous. That is, until and unless the referred module wasn't loaded, parsed and executed, the function won't exit, effectively blocking the main thread from other activity.
On the server side, this was OK because importing a module was just about loading a particular file from the local filesystem, which is a pretty quick operation.
But on the client, this would've wreaked havoc on the the browser. As the module's loading would require a network roundtrip, which could take a considerable amount of time, the browser's main thread would remain blocked, thus preventing any other activity from happening there, leaving users with a poor experience.
Henceforth, CommonJS never directly made it to the client. (But indirectly, it did through bundling tools, as we shall learn later on below.)
The birth of AMD
For the client, everyone loved the term 'asynchronous' and thus born a separate module specification called Asynchronous Module Definition, or simply AMD.
The library that first implemented AMD was Require.js.
The idea was once again pretty simple: every module would be encapsulated inside a global function (provided by Require.js) called
define(), defining dependencies of the module (via the first argument) and entertaining its exports as well (via a
return statement inside the function).
The purpose of encapsulating everything inside a function was so that Require.js could itself run the module once all of its dependencies were loaded.
AMD was a pretty popular approach of its time for client-side developers. After all, it brought forward a fairly simple way of implementing modules on the client. Projects increasingly started to use it until, eventually, another module innovation hit the road.
Browserify with a new approach
Browserify came up with a completely different plan to tackle modules on the client. Instead of putting the module system in action at run time (as was the case in Require.js), it proposed to have a module system at build time and only get a single script in the end to be sent to, and consequently executed by, the browser.
The motivation was to keep from requesting multiple files in the browser, as was the case with AMD, because that came with additional HTTP overhead.
This single script — known as the 'bundled' script, or simply the 'bundle' — would be built by calling the
browserify tool in the terminal, which would combine the code in all of the modules and dump it in a given file.
In this regard, Browserify decided to support CommonJS-style modules.
How amazing would this have been!
Obviously, using Browserify required an extra build step before any changes to code could be witnessed in the browser, but with watchers (to automatically rebuild the bundle as soon as any of its constituent files were changed), the process wasn't very intimidating.
Browserify surely brought more hope into the frontend development realm by introducing a win-win approach to modular programming — developers got to work with modules, thus improving development, while the browser only got one file delivered, thus saving from the HTTP overhead.
But soon, another tool was to give it a tough competition — or better to say, a really tough competition.
Webpack emerged in 2012, taking the idea of Browserify to the next level.
React, a UI library developed by Facebook (now Meta), came at this very time and adopted Webpack for its building process. That's when things took a turn for Webpack — it rose in popularity and surpassed Browserify in no time.
However, this was only until 2015, when finally Ecma International TC39 released the first standard specification for ECMAScript 6, including a long-awaited feature: modules.
In this unit
Anyways, coming back to the discussion, in the following chapter of this unit, we'll dive deeper into how ES modules work; in particular, work with the
We'll take a look at such things as dynamic imports using the
import() function, default imports/exports, module scope, implicit strict mode, and circular dependencies.
There is a lot to cover, and being able to effectively work with ES modules is paramount for newbie developers, so let's get learning.