NPM dependency version conflict handling mechanism



npm(full name of node package manager, or “node package manager”) is Node.js A default package management tool written in JavaScript. Although it is Node.js But now more and more are used to cooperate with the front-end building tools to manage the front-end package.

As a package manager, the most important thing is to manage dependencies. For complex dependency trees, the processing mechanism of NPM is different from other package managers. This article will introduce these details in detail

The NPM2 and npm3 + versions handle dependencies differently, but there are few projects that use npm3 or below. All the introductions in this paper are based on npm3 + or above

NPM dependency management mechanism

Generally speaking, NPM is similar to other package managers. All packages are package dependent packages, and these dependent packages are declared with version numbers.

Semantic version number

Used in NPMSemantic versionTo control the version of the version dependent package, such as^~>=<However, the resolution method of version number in this article is not the key point. You only need to know if you use the range version number,NPM will install the latest version available in scope
I make complaints about NPM documents. I just looked for the version strategy that I used in the scope version. I didn’t have a clear explanation in the document. Finally, we find a little introduction in the NPM update page

If app’s package.json contains:

“dependencies”: {“dep1”: “^1.1.1”}

Then npm update will install [email protected], because 1.2.2 is latest and 1.2.2 satisfies ^1.1.1.

The concept of NPM’s scope version design is still very advanced. Users can update the small version automatically through the range version number. Some bugs may be fixed after the upgrade, but there will be many risks caused by the update. After all, the version number is controlled by human beings, and there may be errors in human control. For example, some APIs are deleted in the update of a revision number, resulting in incompatibility

In my opinion, this kind of package management mechanism with range version number has more disadvantages than advantages and too high risk. If you change a (small) version without changing anything in the server scenario, there may be some serious accidents. Generally speaking, any changes need to be tested, especially the dependency package upgrade, which is quite risky. If it is the general basic package, the risk will be even greater. If there are too many references, there may be some incompatibility.

Dependency tree and transitive dependency

By default, NPM will install the packages that pass dependencies in the form of flat and also to node_ For example, there is a module a in the root directory of modules, which depends on module B:

Version conflict

Now add a module C, C also depends on B, but C depends on the higher version V2.0 of B, so the processing of NPM is a little different. Because the version of B module that C depends on is not compatible with that of a, NPM will first install b1.0 that module a depends on to the root directory, and then install b2.0 that C relies on to C’s own node_ In modules, as shown in the figure below

directory structure

|————[email protected]
|————[email protected]
|————[email protected]
    |————[email protected]

For the version incompatible dependency tree, the NPM first checks whether the version is compatible. If the version is compatible, the installation will not be repeated. If it is incompatible with the previous version of the transitive dependency package, the dependent package will be installed to the node of the currently referenced package_ Under modules
Although the solution of package version conflict based on NPM brings redundancy of package file, it can solve the conflict problem well

Is this version conflict resolution mechanism really perfect?

As can be seen from the previous introduction, NPM will install the dependent package to the node of the current package when the version is incompatible_ Under modules, there is a little meaning of submodule, but it is not really safe. There may be conflicts due to the coexistence of multiple versions.

Take the above three dependency modules a / B / C as an example. For example, B v1.0 registers an attribute with the window object, and bv2.0 also registers an attribute with the window. Because there is a big gap between bv1.0 and V2.0, although the same object is registered, there is a big gap between the properties and its functions. When a and C modules are introduced into a page, bv1.0 and B V2.0 will load, and some unexpected errors may occur. It is not acceptable to users

The above example may not be appropriate, because there is a certain risk in registering for window. Now imagine another common scenario. For example, in angular (2), two angular based components depend on different angular (core) versions. When a page uses two components at the same time, and the two components need to interact on the current page, the problem in the above figure is very easy to occur, such as assignment or function call.

Although there are also package management problems in the Java ecosystem, the forms are different

In Maven (the package management tool of Java Ecology), although the dependency is tree structure, the result after construction is actually flat. If there are multiple versions of jar packages, the runtime will generally load all jar packages; however, due to the parent delegate mechanism of classloader in JavaThe same class will only be loaded once, and the class with the same name (package name + class name) in the next n jar packages will be ignoredThe advantage of this is that it is simple. If there is a version conflict, it can be seen clearly. Users need to handle the conflict by themselves.

Maven build handles package (pass through) dependency on multiple versions, as shown in the following figure:

NPM also provides a solution to this problempeerDependencies


Peer dependencies are similar to the provide scope in Maven. When a dependency module x is defined in peer dependencies instead of devdependencies or dependencies, the dependency will not be automatically downloaded by the project that depends on the module.

In a project, you need to directly or indirectly declare the dependency that conforms to the version. Direct dependency refers to declaring directly in devdependencies or dependencies. Indirect dependency refers to that other modules that the current project depends on depend on modules that meet the version range of X. if both of them are not met, an alarm will appear during NPM install, such as:

npm WARN [email protected] requires a peer of [email protected]~1.3.1 but none is installed. You must install peer dependencies yourself.

npm & webpack

Now many projects will use webpack as a project building tool, but different from Maven in Java, webpack and NPM are two sets of independent tools, construction and package management are separate

In other words, even if NPM installs the conflict package in the current package in the form of “submodule”, webpack does not necessarily recognize it

For example, in the above example of ABC three modules, if the code of module aimport BObj from B modAfter the webpack is built, which version B will be referenced by a? V1.0 or v2.0?

This scenario is quite complex. This article will not introduce it. There is an article that introduces the processing methods and test scenarios under webpack in detail《Finding and fixing duplicates in webpack with Inspectpack》


Although the design concept of NPM package management is very good, it is not suitable for all scenarios. For example, this submodule mode is not feasible in Java, and the submodule mode still has certain risks, but the risk is reduced. Once you have multiple dependent code working or interacting on a page at the same time, it’s easy to have problems.

No matter what the most important thing is to avoid duplication of tools. After a new dependency is added or a new project is created, some dependency analysis and checking tools are used to detect and fix the duplicate / conflicting dependencies.

reference resources