A Note on Node-Modules





0/5 (0 vote)
Node-modules folder and its structure
Introduction
This is a note on the node_modules folder and its structure.
Background
This is a note on the node_modules folder and its structure. In particular, I want to answer the following questions when a NODE project references different versions of the same package. These questions are simple questions, but they may give us a nasty version hell if not taken proper care.
- How are the different versions of the same package placed in the node-modules folder?
- When the application runs, are all the versions of the same package invoked or just one version of the package is invoked?
To answer the questions, I created three NODE packages. The package-client
project references two versions of the package-0
directly and indirectly.
- We will publish two versions of
package-0
, version1.0.0
and version1.0.1
. - The
package-1
referencespackage-0
version1.0.0
. - The
package-client
referencespackage-1
andpackage-0
version1.0.1
. As a consequence, thepackage-client
referencespackage-0
for both version1.0.0
and version1.0.1
.
The experiment is done with a private NPM registry. If you do not know how to setup a private NPM registry, you can take a look at my another note. In order to make my work easy, all the packages are scoped to "@example". For example, the full name of the package-0
is @example/package-0
. I also put a .npmrc file under each package, so I do not need to specify the registry URL (--registry) every time I publish or install a package.
@example:registry=http://localhost:4873/
If you want all the packages scoped as @example
to use the registry http://localhost:4873/, you can also put the entry in the .npmrc file in your home directory. We can confirm if the registry is up and running by curling its URL.
curl http://localhost:4873/
The "package-0"
The package.json of the @example/package-0 is the following:
{
"name": "@example/package-0",
"version": "1.0.0",
"description": "The @example/package-0",
"main": "index.js",
"author": "Song Li",
"license": "ISC"
}
The only functionality of the Index.js file is to export a string
stating the version number.
module.exports = 'This is Version 1.0.0';
We can publish the package to the registry by the following command:
npm publish
We can then update the package.json and the index.js file to the version 1.0.1
, and publish it.
{
"name": "@example/package-0",
"version": "1.0.1",
......
}
module.exports = 'This is Version 1.0.1';
We can confirm that both versions are published to the registry by the following command:
npm view @example/package-0 versions
The "package-1"
The @example/package-1 references the @example/package-0.
{
"name": "@example/package-1",
"version": "1.0.0",
"description": "The @example/package-1",
"main": "index.js",
"author": "Song Li",
"license": "ISC",
"dependencies": {
"@example/package-0": "1.0.0"
}
}
There are three ways to reference a package version.
- ~version “Approximately equivalent to version” - It will update you to all future patch versions, without incrementing the minor version. ~1.2.3 will use releases from 1.2.3 to <1.3.0;
- ^version “Compatible with version” - It will update you to all future minor/patch versions, without incrementing the major version. ^2.3.4 will use releases from 2.3.4 to <3.0.0;
- Absolute version - In this note, I used the absolute version "1.0.0"
The index.js requires the @example/package-0 and re-exports the string
from it. Since the @example/package-1 references the @example/package-0 version 1.0.0
, ideally it should export the string
"This is Version 1.0.0
".
const msg = require('@example/package-0');
module.exports = msg;
We can then publish the package and confirm its availability in the registry.
npm publish
npm view @example/package-1 versions
The "package-client"
With both @example/package-0 and @example/package-1 published to the registry, we can take a look at the package-client
.
{
"name": "package-client",
"version": "1.0.0",
"description": "The package-client ",
"main": "index.js",
"private": true,
"keywords": [],
"author": "Song Li",
"license": "ISC",
"dependencies": {
"@example/package-1": "1.0.0",
"@example/package-0": "1.0.1"
}
}
It references both @example/package-1 and @example/package-0, the index.js prints out the string
from both packages.
const msg_0 = require('@example/package-0');
const msg_1 = require('@example/package-1');
console.log(`Message from package-0: ${msg_0}`);
console.log(`Message from package-1: ${msg_1}`);
We can then install the referenced packages and take a look at the node_modules folder.
npm install
find . -name "package-0" -type d -prune -print | xargs du -chs
If we now run the index.js, we will see the following result:
node index.js
Conclusion
We can now come to the conclusion.
- When multiple versions of a package are referenced by a NODE application, all the versions will be installed in the node_modules folder. In this example, the
1.0.1
is installed at the top level, and the1.0.0
is installed under the @example/package-1 folder; - When multiple versions of a package are used in a program, all the versions will be invoked if actually used in the program.
We can further take a look at the package-lock.json file to better understand how the different versions of the package are installed.
We need to share the package-lock.json file with the team, so all the people have the same node_modules folder when they npm install
. If we want to clean up the experimental packages from the registry, we can use the following command:
npm cache clean -f
npm unpublish @example/package-0 -f
npm unpublish @example/package-1 -f
Points of Interest
- This is a note on the node_modules folder and its structure.
- It is not common that you will encounter problem with different versions of a package. But if you do, it is better to know that you may be running different versions of a dependency package simultaneously in the same program.
- I hope you like my posts and I hope this note can help you one way or the other.
History
- 16th October, 2020: First revision