原文档:https://docs.npmjs.com/cli/install#algorithm
安装一个package,npm遵循以下算法:
从磁盘读取已存在的node_modules依赖树(以下简称为依赖树)
克隆以上获取的依赖树
读取package.json以及各元数据,将其添加到克隆的依赖树上
遍历克隆的依赖树,添加没有的依赖,遵循以下两条原则:
尽可能靠近树的根部添加依赖
不破坏其他modules
对比原始树,定制出一系列将原始树变为克隆树的操作
执行操作,最深层的(目录)优先处理
操作包括安装、更新、移除和移动
以 package{dep} 这种形式举个例子: A{B,C}, B{C}, C{D},会生成如下目录结构:
A
+-- B
+-- C
+-- D
其中,因为A已安装了C,所以B所依赖的C提升了一个层级。D也在最高层级,是因为它不和其他产生冲突。
再看一个例子:A{B,C}, B{C,D@1}, C{D@2},这将生成如下目录结构:
A
+-- B
+-- C
`-- D@2
+-- D@1
由于B所依赖的D@1版本将被安装在最顶层,C必须将D@2版本安装在其下目录中。这种算法是有确定性的,但不同命令安装两个依赖(D@1和D@2)会生成不同的树。
查看npm-folders获取更多npm生成详细文件结构的信息。
限制
npm不会在当前文件夹下安装任何与文件夹同名的package。可以用--force忽略这个限制,但更常见的是重命名本地package。
这样做会有些少见的不合理的极限例子——循环引用会导致npm安装package进行无限安装。下面是最简单的例子:
A -> B -> A' -> B' -> A -> B -> A' -> B' -> A -> ...
A是某版本的package,A‘ 是同一个package不同的版本。由于忽略限制,B不能依赖已安装好的A,必须再单独拷贝一份A。同样的道理,之后的A’ 必须安装B‘。然后又因为B‘ 依赖于A,即便A已经在顶层安装,但已被忽略,循环就进入了无限递归。
为了避免以上情形,npm不允许安装任何和依赖树上已存在的祖先节点同名的包。这样虽然更准确,但是也更复杂,已存在的版本需要被链到新的需要引用的地方。
名词解释
元数据(Metadata)
元数据是关于数据的数据,在某些时候不特指某个单独的数据,可以理解为是一组用来描述数据的信息组/数据组,该信息组/数据组中的一切数据、信息,都描述/反映了某个数据的某方面特征,则该信息组/数据组可称为一个元数据。
元数据可以为数据说明其元素或属性(名称、大小、数据类型等),或其结构(长度、字段、数据列),或其相关数据(位于何处、如何联系、拥有者)。
容易混淆概念:数据元(Data element)
又称数据类型,通过定义、标识、表示以及允许值等一系列属性描述的数据单元。在特定的语义环境中被认为是不可再分的最小数据单元。