Ideas2IT rewards key players with 1/3rd of the Company in New Initiative.  Read More >
Back to Blogs

Executing npm-install the Right Way to Benefit the Most From It

It is common knowledge that most of the happening frameworks like Node.js, React.js, Vue.js, Angular, etc. are all built with npm as its back-bone. The npm registry maintains all the required libraries and dependencies of various frameworks.

It is a command-line utility for interacting with a repository that aids in package installation, version management, and dependency management. With npm, you can install a package with just a single command-line command.

In order to get the most out of npm repositories, it is essential to understand how the npm install command works, the order of the downloaded dependencies, and the node_modules folder structure.

Executing the npm install command

Once you execute the command npm install dependency module will be downloaded on your device from the npm registry. There are 3 ways to execute this command.

  1. npm install - to fetch all dependencies mentioned in the dependency tree.
  2. npm install <dependency_name> or npm install <dependency_name>@<version> - to fetch a particular dependency by name and version (if no version is specified, then it pulls the latest version).
  3. npm install <git remote url> - to fetch a library pushed to github or bitbucket or gitlab.

Simplifying npm install

Once you execute the npm install command, you will have your dependency module downloaded on your system - that’s its job. Many of the configuration parameters in the modules will have some direct impact on the installation. To keep track of what changes are happening during the installation, follow these steps:

Step 1: Check whether the node_modules folder exists or the package-lock.json folder. Once you spot the folder, trace the existing dependency tree and clone the tree. You can create an empty tree if you prefer to.

Step 2: Fetch the relevant dependencies (dev, prod, or direct dependencies) from package.json and add them to the clone tree (from step-1). There are a few benefits of doing this:

  • This process helps you find the difference between the trees and add the missing dependencies if needed.
  • All the new dependencies will be added at the top of the tree and that will help you make changes easily, if and when required.
  • New dependencies are added without disturbing the other roots/branches of the tree and so your params remain as undisturbed as possible.

Step 3: Compare the original tree (from step-2) with the cloned tree (step-1) and make a list of actions required for replicating the new tree in other node_modules. The required actions may be install (new dependencies), update (existing dependency versions), move (change the location of the dependency within the tree), and remove (uninstall libraries that are not needed by the new tree). Execute all the commands identified starting from the deepest one.

Folder-Structure in node_modules

There are 4 possible scenarios in the node_modules. The folder structure that the npm follows varies according to these scenarios.

  1. No existing node_modules, package-lock.json or dependencies are available in package.json.
  2. No existing node_modules or package-lock.json, but package.json with a dependency list is available in package.json.
  3. No existing node_modules, but package-lock.json and package.json with dependency list are available in package.json.
  4. The node_modules, package-lock.json and package.json with dependency list are all available in package.json.

No existing node_modules, package-lock.json or dependencies is available in package.json

This scenario occurs when a JS framework application starts without any dependency and then adds them at a later stage one by one. In this scenario, the dependencies are downloaded in the order of installation.

For example, when you execute npm install <B> in a new application.

Here B is a dependency and let’s assume it has an internal dependency on alpha@v2.0, then both get installed at the root level of the node_modules.

Inference: All the dependencies try to get a place in the root of the node_modules unless there is a conflict with the dependency existing in a different version.

Final resulting node_modules:

|_ B
|_ alpha @v2.0

No existing node_modules or package-lock.json, but package.json with a dependency list is available in package.json

In this scenario, an application has dependencies listed in package.json without a lock-file.

For example, when you execute npm install in the application directory which has a package.json with dependencies like,

{

"dependencies":
{
"A": "1.0.0",
"B": "2.0.0"
}

}

Here, A internally depends on alpha@v1.0 and B depends on alpha@v2.0. All the new dependencies and the pre-existing dependencies try to get a place in the root of the node_modules unless there is a conflict with the same dependency in a different version. When such a conflict arises, it creates a sub-node_modules under each dependency and pushes conflicting internal libraries in it.

Final resulting node_modules:

|_ A
|_ alpha @v1.0
|_ B
|_ node_modules
|_ alpha @v2.0

No existing node_modules, but package-lock.json and package.json with dependency list are available in package.json

Assume, A internally depends on alpha@v1.0 whereas, B depends on alpha@v2.0 and beta@v3.0.

package-lock.json snippet would look like,

{

"dependencies": {
"A": {
"version": "1.0.0"
"resolved": "NPM REGISTRY URL of A",
"requires": {
"alpha": "1.0.0"
}

},

"alpha": {
"version": "1.0.0",
"resolved": "NPM REGISTRY URL of alpha v1",
},

"B": {
"version": "2.0.0",
"resolved": "NPM REGISTRY URL of B",
"requires":
{
"alpha": "2.0.0",
"beta": "3.0.0"
},

"dependencies":
{
"alpha":
{
"version": "2.0.0",
"resolved": "NPM REGISTRY URL of alpha v2",
}

}

},

"beta":
{
"version": "3.0.0",
"resolved": "NPM REGISTRY URL of beta v3",
}

}

}

Irrespective of how the dependencies are ordered in package.json, the packages will be installed according to the tree structure defined by the package-lock.json.

And the resulting dependency tree structure would be:

node_modules
|_ A
|_ alpha @v1.0
|_ B
| |_ node_modules
| |_ alpha @v2.0
|_ beta @v3.0

The node_modules, package-lock.json and package.json with dependency list are all available in package.json

The node_modules folder will be rearranged to match the incoming new tree from package-lock.json and installed in the order as defined in the package-lock.json file.

Package.json vs. Package-lock.json

To understand the difference between package.json and package-lock.json, consider the following sequences of dependency installation in a new application without an existing dependency tree or node_modules in it.

Assume, A internally depends on alpha@v1.0 whereas, B depends on alpha@v2.0.

npmScenario-1Scenario-2Commandsnpm install A
npm install Bnpm install B
npm install Apackage.json{
"dependencies": {
"A": "1.0.0",
"B": "2.0.0"
}
}{
"dependencies": {
"A": "1.0.0",
"B": "2.0.0"
}
}package-lock.json{
"dependencies": {
"A": {
"version": "1.0.0",
"requires": {
"alpha": "1.0.0",
}
},
"alpha": {
"version": "1.0.0",
},
"B": {
"version": "2.0.0",
"requires": {
"alpha": "2.0.0",
},
"dependencies": {
"alpha": {
"version": "2.0.0",
}
}
}
}
}{
"dependencies": {
"A": {
"version": "1.0.0",
"requires": {
"alpha": "1.0.0",
},
"dependencies": {
"alpha": {
"version": "1.0.0",
}
}
},
"alpha": {
"version": "2.0.0",
},
"B": {
"version": "2.0.0",
"requires": {
"alpha": "2.0.0",
}
}
}
}node_modulesnode_modules
|_ A
|_ alpha @v1.0
|_ B
| |_ node_modules
| |_ alpha @v2.0node_modules
|_ A
| |_ node_modules
| |_ alpha @v1.0
|_ alpha @v2.0
|_ B

The above comparison depicts the role of package-lock.json and package.json.

If the package alpha is imported from the JS application like var alpha = require('alpha');, scenario-1 imports to v1 whereas, scenario-2 imports v2. So, the behavior of the code snippets might differ based on the imported file might differ.

It is not package.json that determines the tree structure. The npm install command downloads dependencies in alphabetical order as saved in package.json. The best practice is to push and maintain the package-lock.json into the source code (like git), to ensure the same dependency tree is being used by all members on the project.

Are you looking to build a great product or service? Do you foresee technical challenges? If you answered yes to the above questions, then you must talk to us. We are a world-class custom .NET development company. We take up projects that are in our area of expertise. We know what we are good at and more importantly what we are not. We carefully choose projects where we strongly believe that we can add value. And not just in engineering but also in terms of how well we understand the domain. Book a free consultation with us today. Let’s work together.

Ideas2IT Team

Connect with Us

We'd love to brainstorm your priority tech initiatives and contribute to the best outcomes.