npm install

作者: 张培_ | 来源:发表于2019-03-31 18:59 被阅读0次

    Background

    假设此时你的repo中没有任何lock文件,当你执行npm install, npm会根据你在package.json中对各种依赖的定义去安装这些依赖。安装完之后,会产生两个结果:

    • node_modules: 所有依赖包
    • npm-lock.json: lock 文件精确描述了 node_modules 目录中所列出的目录的物理树,这个文件相当于是node_module产生的物理树的快照。

    package.json里面定义的是版本范围(比如^1.0.0),具体跑npm install的时候安的什么版本,要解析后才能决定,这里面定义的依赖关系树,可以称之为逻辑树(logical tree)。node_modules文件夹下才是npm实际安装的确定版本的东西,这里面的文件夹结构我们可以称之为物理树(physical tree)。安装过程中有一些去重算法,所以你会发现逻辑树结构和物理树结构不完全一样。

    package-lock.json可以理解成对结合了逻辑树和物理树的一个快照(snapshot),里面有明确的各依赖版本号,实际安装的结构,也有逻辑树的结构。

    去重算法

    对复杂的工程,可能需要安装非常多的依赖包,就有可能出现,一个包被多个包依赖,很可能在应用 node_modules 目录中的很多地方被重复安装。随着工程规模越来越大,依赖树越来越复杂,这样的包情况会越来越多,造成大量的冗余。npm3以后,为了解决这个问题,将node_module的包结构变成了变成了扁平的,因此可以避免安装很多重复的包。npm 3 都会在安装时遍历整个依赖树,计算出最合理的文件夹安装方式,使得所有被重复依赖的包都可以去重安装。

    npm官方有专门讲过去重算法的问题:

    npm中使用指令npm dedupe/npm ddp, 搜索本地包树,并尝试通过将依赖项向上移动到树的更高层来简化整个结构,在树的更高层,多个依赖包可以更有效地共享依赖项。

    举个例子:

    a
    +-- b <-- depends on c@1.0.x
    |   `-- c@1.0.3
    `-- d <-- depends on c@~1.0.9
        `-- c@1.0.10
    

    在这种情况,npm run dedup可以将树变成以下的状态:

    a
    +-- b
    +-- d
    `-- c@1.0.10
    

    解释:

    • b依赖c
    • d依赖c
      而且bd都依赖的c都是兼容的,因此可以使用同一份package。

    根据npm去重原则,会将c上移一个层级方便bd共享。

    • npm将每个依赖项尽可能地向上移动到树的根部,即使这个包不一定被多个包依赖(假设只有一个包依赖)。这样做将产生一个扁平的和去复制的树。
    • 如果这个包A只被一个包依赖,那么尽可能将A向树的高层级移动。
    • 如果在树的目标位置已经存在一个合适的版本,那么它将保持原样,但是其他副本将被删除。
    • 但是每个层级只能有一个类型的包(不同版本也不行)

    实例1:

    项目中安装了webpack@1.15.0,产生的package-lock如下

        "webpack": {
          "version": "1.15.0",
          "resolved": "https://registry.npmjs.org/webpack/-/webpack-1.15.0.tgz",
          "integrity": "sha1-T/MfU9sDM55VFkqdRo7gMklo/pg=",
          "requires": {
            "acorn": "^3.0.0",
            "async": "^1.3.0",
            "clone": "^1.0.2",
            "enhanced-resolve": "~0.9.0",
            "interpret": "^0.6.4",
            "loader-utils": "^0.2.11",
            "memory-fs": "~0.3.0",
            "mkdirp": "~0.5.0",
            "node-libs-browser": "^0.7.0",
            "optimist": "~0.6.0",
            "supports-color": "^3.1.0",
            "tapable": "~0.1.8",
            "uglify-js": "~2.7.3",
            "watchpack": "^0.2.1",
            "webpack-core": "~0.6.9"
          }
        }
    

    首先可以看到的是,webpack只有require,require中描述的是webpack中package.json的结构,但是没有dependency,说明webpack自己的node_module中啥也没有。

    • require:除最外层的 requires 属性为 true 以外, 其他层的 requires 属性都对应着这个包的 package.json 里记录的自己的依赖项
    • dependency: dependencies 层次结构与文件系统中 node_modules 的文件夹层次结构是完全对照的,这个字段可以理解成是node_module的快照

    那么webpack的依赖,比如acorn去哪了?发现他出现在根结构下

    {
      "name": "npm",
      "version": "1.0.0",
      "lockfileVersion": 1,
      "requires": true,
      "dependencies": {
        "acorn": {
          "version": "3.3.0",
          "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
          "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo="
        },
    

    根据查找,发现整个树上只有webpack依赖这个包,根据

    npm将每个依赖项尽可能地向上移动到树的根部,即使这个包不一定被多个包依赖(假设只有一个包依赖)。这样做将产生一个扁平的和去复制的树。

    这个包acorn就被移到了树的根部。

    实例二:

    如果是 A{B,C}, B{C,D@1}, C{D@2} 的依赖关系,得到的安装后结构是:

    A
    +-- B
    +-- C
       `-- D@2
    +-- D@1
    
    • A依赖C,B也依赖C,两个C兼容,因此C被上移到和B一个层级。
    • B依赖D1,C依赖D2,但是两个D不兼容,因此必须安装两个D。

    如果这个包A只被一个包依赖,那么尽可能将A向树的高层级移动。

    此时D1只被B依赖,因此D1被提高到和B一个层级

    • 如果在树的目标位置已经存在一个合适的版本,那么它将保持原样,但是其他副本将被删除。
    • 但是每个层级只能有一个类型的包(不同版本也不行)

    此时D2只能留在原来的层级,因此BC层级已经有一个D

    npm install工作流程

    常常我都会有一个疑问,npm install的时候到底是根据package.json安装呢?还是根据package-lock安装?

    因此我做了一个小实验,安装webpack (npm: 6.7.0/node: v11.13.0):

    package.json(before) package-lock.json(before) node_module(before) command package.json(after) package-lock.json(after) node_module(after)
    S1 ^1.8.0 1.8.0 null install ^1.8.0 1.8.0 1.8.0
    S2 ^1.8.0 1.8.0 1.8.0 install ^1.8.0 1.8.0 1.8.0
    S3 ^1.8.0 1.8.0 1.8.0 up ^1.15.0 1.15.0 1.15.0
    S4 ^1.8.0 1.15.0 1.15.0/null install ^1.8.0 1.15.0 1.15.0
    s5 ^1.8.0 3.0.0 null install ^1.8.0 1.15.0 1.15.0
    s6 ^3.0.0 1.15.0 null install ^3.0.0 3.12.0 3.12.0

    summary:

    • npm install首先肯定是希望根据package.json文件安装package。

      • 如果package-lock.json文件中的版本和package.json版本不能匹配(兼容)

        npm会根据package.json允许的package的最高版本安装package并且同时修改package-lock

      • 如果package-lock.json文件中的版本和package.json版本匹配(兼容)。比如s4的情况,一定会根据package-lock安装

    • 最后安装的版本一定和package-lock中的一定一致

    根据优先级来看:

    npm安装的包一定是遵循package.json中的要求,如果package-lock中版本满足条件,那么完全按照lock中安装,如果不满足要求,安装满足要求的最高版本并且更新lock文件。

    相关文章

      网友评论

        本文标题:npm install

        本文链接:https://www.haomeiwen.com/subject/pxvubqtx.html