A module is a standalone software component implementing specific functionalities and is easily reusable in different applications.
- A module can depend on the functionality provided by another module.
- A module exports its functionality for other modules to consume.
Within a module, values that are not explicitly exported are private within the module and cannot be accessed by client modules.
Modules have become very critical in building modern and complex JavaScript applications as they help make code organization in large applications easier to reason about.
JavaScript module systems are standards that define how to write JavaScript modules.
Example module systems include:
- CommonJS
- ES modules
- AMD (Asynchronous Module Definition)
One defining feature of all module systems is that they define how dependencies are loaded into a module that depends on it.
The CommonJS module system has a built-in mechanism to load modules. The require
statement triggers this mechanism.
The ES module system has a built-in mechanism to load modules. The import
statement triggers this mechanism.
The AMD system makes use of a range of loaders that must be AMD-compatible.
Some of these AMD-compatible loaders include LABjs, Steal.js, yepnope.js, $script.js, bdLoad, and RequireJS.
The process of loading dependencies into a JavaScript module involves:
- fetching the JavaScript code from where it is located (OS file system for Node.js, remote server for browsers). This process is the loading phase.
- Instantiating the loaded modules.
- Evaluating or executing the modules.
The process of instantiating and evaluating modules is synchronous even for ES modules.
It is the process of fetching and loading the modules that could be asynchronous and synchronous depending on the environment.
The different environments have their implementation of the module loaders which are responsible for fetching these files/modules.
The ES module specification says how you should parse files into module records, and how you should instantiate and evaluate that module. However, it doesn’t say how to get the files in the first place.
For browsers, the HTML specification defines how the module loaders should be implemented.
For server environments like Node.js, and because OS file system calls don’t take that long compared to calls over the internet, the process of fetching and loading modules is synchronous.
For browser environments, where modules are fetched over the internet, and networks are generally slower and less predictable than OS file operations, the loading phase is asynchronous to prevent any piece of JavaScript code from delaying the browser's main thread.
In the ES module system, the phase responsible for loading a file is called the construction phase.
The construction phase has 3 steps:
- Figure out where to download the file containing the module from (aka module resolution)
- Fetch the file (by downloading it from a URL or loading it from the file system)
- Parse the file into a module record
Module loading for CommonJS modules is synchronous because the dependencies below it are loaded, instantiated, and evaluated simultaneously, without any breaks in between.
The synchronous constraint for CommonJS modules expects that the module will not produce side effects.
module.exports
are not expected to have side effects and thus cannot be set asynchronously.
The synchronous constraint CommonJS demands is the primary reason for synchronous APIs that can be used for initialization tasks.
Note that synchronous means the task blocks on the main thread, and must finish before the next task can be executed. Thus, these sync APIs used for initialization are blocking.
ES modules however do not have this constraint. A module can have side effects like making network connections etc.
Because ES modules can be async is the primary reason why the CommonJS module cannot be used to load an ES module as a dependency. But ES modules can load CommonJS modules.
If you want to load modules that can have side effects in a CommonJS module, there is a Dynamic import method that can help with this.
The Dynamic import method is a key component in code-splitting and lazy loading of JavaScript on demand. This is possible because of the asynchronous nature. The JavaScript code will only be available when it is needed and will not be bundled upfront with other parts that are actually needed.
Top comments (0)