CHAPTER 1CommonJS Modules
CommonJS is the most successful module system prior to the modern Javascript modules (explained in the next section). There are still many packages using it and it will last for a while even if now the official module system in Javascript is well established.
The module object
In CommonJS, the simplest module is a file in which we presuppose the existence of a module
global object. This object cannot be changed (we only have its reference), but we can add things to it. The exports
field in this object is the place to put everything that we want other modules to access:
// in file 'circle.js'
const area = (r) => Math.PI * r ** 2;
const circumference = (r) => 2 * Math.PI * r;
module.exports = { area, circumference };
This circle.js
module defines area
and circumference
and they are exported outside of the module.
When Javascript loads a CommonJS module, it must execute the code in it, and look at module.exports
to see what is made available from it. (Anything else is not accessible by anyone else.)
Module execution
The execution of modules is a key part of CommonJS (and also its curse), because it is possible to do things like:
for (let i = 0; i < 100; i++) {
module.exports[`add${i}`] = (x) => x + i;
}
To know what functions this module exports, one has to execute the module, there is no other way in general of determining the exported values.
require
To use a module in CommonJS, we call require
. This function will:
- Resolve a module: find where its code is in the file system.
- Load the module: execute the module in its own context and give the
module.exports
object as a result.
Module resolution
-
Local: The module starts with "
.
" or "..
"const circle = require('./circle.js'); console.log(circle.area(5.1));
-
Installed: Look for the named module in the
node_modules
folder (has to be installed first). It also looks fornode_modules
upwards parent directories.const colors = require('colors'); console.log('red'.red); console.log('green'.green); console.log('blue'.blue);
-
Internal: the referenced module might be internal to the Javascript implementation (from the "runtime library"). In NodeJS, there are many packages like this:
os
,fs
,child_processes
,http
,net
, etc.
Diamond problem
What happens if we have a "diamond" in our dependency graph? The problem is this: a module main.js
requires part1.js
and part2.js
, and then both part1.js
and part2.js
require circle.js
. Do we execute circle.js
twice, and with different contexts?
In this case, Javascript reuses the same module.exports
(and the context) of the first execution of the module, so both part1.js
and part2.js
would get the same object and the context that it refers to (because of the closures returned!). The same happens in general with more dense dependency graphs. There is only one copy of each module, so global variables in that module are unique, and if exported, they are the same for every part of the program.
The Node Package Manager
The node_modules
directory is managed by the npm
command, the Node Package Manager. This command will let you add and remove packages from npmjs.com
, a global repository (probably the largest software repository in the world, with more than 1.3 million packages).
But the most important job of npm
is installing, along with some package, all the packages that it declares as dependencies. That is, any package in the repository describes, in a file, what other packages it uses so that they can be installed along with it. The dependencies affect those other packages as well, so by installing one package, you often end up downloading tens or even hundreds of packages.
package.json
The package.json
file, when found at some folder, marks that folder as a Javascript package. The file contains metadata about the package: name
, version
, description
, keywords
, etc.
The most important section, though, is the dependencies
, in which all the packages that are require
d in some file in the project are listed with the version required.
Installing dependencies
When having a project with a package.json
at its root, the first step is always installing the dependencies listed in it:
npm install
This populates the node_modules
with the code downloaded from the repository, and also, if not present, creates a file package-lock.json
. If you want to share your project, or put it into version control, it is a good idea to delete (or .gitignore
) node_modules
since those are your "libraries" and you can reinstall them at any time doing "npm install
".
package-lock.json
It is not possible, just using package.json
to perfectly reconstruct the same node_modules
directory that you had, looking only at package.json
. The reason for this is the way in which Semantic Versioning works, since you can list in your package.json
a dependency with a version number and maybe npm
installed a slightly more recent version.
To have the full information about the package tree and exact versions (along with hashes and many more things), package-lock.json
is extra metadata that makes it possible to reconstruct, avoid versioning problems, and other features of the node_modules
directory.
Adding and removing packages
To add a packages, find its name (for instance, colors
) and install it with:
npm install colors
This will add it to package.json
and also download it and save it in node_modules
. (You could also edit the package.json
by hand and then just do an npm install
, which reconciles the current directory with what you list in package.json
).
To remove a dependency
npm uninstall colors
devDependencies
Another kind of dependencies are listed in the devDependencies
section of package.json
, which registers those packages needed to develop your project, but not to run it. Typescript could be a good example: you need it to compile, but since it transpiles to Javascript, you don't need it to run your app.
Entry Point
A directory with a package.json
is also considered a module, so using such a module by referring to its directory also works. One important field for this use is main
, which indicates the entry point for the package (the Javascript file which is considered the "root" module). If a directory module doesn't list a main
file, then index.js
is used.