npm install 如何⼯作

npm install 如何⼯作

依赖管理是 npm 的核⼼功能,原理就是执⾏ npm install 时, 从 package.json 中的 dependencies, devDependencies 将依赖包安装到当前⽬录的 ./node_modules ⽂件夹中,使⽤者⽆需关注这个⽬录⾥的⽂件夹结构细节,只管在业务代码中引⽤依赖包即可。

⼀个 npm 版本号包含三个部分:MAJOR.MINOR.PATCH,

MAJOR 表⽰主版本号,当做了不兼容的 API 修改;

MINOR 表⽰次版本号,当做了向下兼容的功能性新增;

PATCH 表⽰修订号,当做了向下兼容的问题修正;

npm 2 到 npm 5 有哪些变化和改进?假设应⽤⽬录为 app, ⽤两个流⾏的包 webpack, nconf 作为依赖包做⽰例说明

npm2 - 嵌套安装

npm 2 在安装依赖包时,采⽤简单的递归安装⽅法。执⾏ npm install 后,npm 2 依次递归安装 webpack 和 nconf 两个包到 node_modules 中。执⾏完毕后,我们会看到 ./node_modules 这层⽬录只含有这两个⼦⽬录

node_modules/

├── nconf/

└── webpack/

进⼊更深⼀层 nconf 或 webpack ⽬录,将看到这两个包各⾃的 node_modules 中,已经由 npm 递归地安装好⾃⾝的依赖包。包括./node_modules/webpack/node_modules/webpack-core ,./node_modules/conf/node_modules/async 等等。⽽每⼀个包都有⾃⼰的依赖包,每个 包⾃⼰的依赖都安装在了⾃⼰的 node_modules 中。依赖关系层层递进,构成了⼀整个依赖树,这个依赖树与⽂件系统中的⽂件结构树刚好层层对应

最⽅便的查看依赖树的⽅式是直接在 app ⽬录下执⾏ npm ls 命令

app@0.1.0

├─┬ nconf@0.8.5

│ ├── async@1.5.2

│ ├── ini@1.3.5

│ ├── secure-keys@1.0.0

│ └── yargs@3.32.0

└─┬ webpack@1.15.0

├── acorn@3.3.0

├── async@1.5.2

├── clone@1.0.3

├── ...

├── optimist@0.6.1

├── supports-color@3.2.3

├── tapable@0.1.10

├── uglify-js@2.7.5

├── watchpack@0.2.9

└─┬ webpack-core@0.6.9

├── source-list-map@0.1.8

└── source-map@0.4.4

这样的⽬录结构优点在于层级结构明显,便于进⾏傻⽠式的管理:

例如新装⼀个依赖包,可以⽴即在第⼀层 node_modules 中看到⼦⽬录

在已知所需包名和版本号时,甚⾄可以从别的⽂件夹⼿动拷贝需要的包到 node_modules ⽂件夹中,再⼿动修改 package.json 中的依赖配置

要删除这个包,也可以简单地⼿动删除这个包的⼦⽬录,并删除 package.json ⽂件中相应的⼀⾏即可

但这样的⽂件结构也有很明显的问题:

对复杂的⼯程, node_modules 内⽬录结构可能会太深,导致深层的⽂件路径过长⽽触发 windows ⽂件系统中,⽂件路径不能超过 260 个字符长的错误

部分被多个包所依赖的包,很可能在应⽤ node_modules ⽬录中的很多地⽅被重复安装。随着⼯程规模越来越⼤,依赖树越来越复杂,这样的包情况会越来越多,造成⼤量的冗余。

webpack 和 nconf 都依赖 async 这个包,所以在⽂件系统中,webpack 和 nconf 的 node_modules ⼦⽬录中都安装了相同的 async 包,并且是相同的版本。

+-------------------------------------------+

| app/ |

+----------+------------------------+-------+

| |

| |

+----------v------+ +---------v-------+

| | | |

| webpack@1.15.0 | | nconf@0.8.5 |

| | | |

+--------+--------+ +--------+--------+

| |

+-----v-----+ +-----v-----+

|async@1.5.2| |async@1.5.2|

+-----------+ +-----------+

npm 3 - 扁平结构

主要为了解决以上问题,npm 3 的 node_modules ⽬录改成了更加扁平状的层级结构。⽂件系统中 webpack, nconf, async 的层级关系变成了平级关系,处于同⼀级⽬录中。

+-------------------------------------------+

| app/ |

+-+---------------------------------------+-+

| |

| |

+----------v------+ +-------------+ +---------v-------+

| | | | | |

| webpack@1.15.0 | | async@1.5.2 | | nconf@0.8.5 |

| | | | | |

+-----------------+ +-------------+ +-----------------+

虽然这样⼀来 webpack/node_modules 和 nconf/node_modules 中都不再有 async ⽂件夹,但得益于 node 的模块加载机制,他们都可以在上⼀级 node_modules ⽬录中找到 async 库。所以 webpack 和 nconf 的库代码中 require('async') 语句的执⾏都不会有任何问题。

这只是最简单的例⼦,实际的⼯程项⽬中,依赖树不可避免地会有很多层级,很多依赖包,其中会有很多同名但版本不同的包存在于不同的依赖层级,对这些复杂的情况, npm 3 都会在安装时遍历整个依赖树,计算出最合理的⽂件夹安装⽅式,使得所有被重复依赖的包都可以去重安装。

npm ⽂档提供了更直观的例⼦:

假如 package{dep} 写法代表包和包的依赖,那么 A{B,C}, B{C}, C{D} 的依赖结构在安装之后的 node_modules 是这样的结构:

A

+-- B

+-- C

+-- D

这⾥之所以 D 也安装到了与 B C 同⼀级⽬录,是因为 npm 会默认会在⽆冲突的前提下,尽可能将包安装到较⾼的层级。

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

这⾥是因为,对于 npm 来说同名但不同版本的包是两个独⽴的包,⽽同层不能有两个同名⼦⽬录,所以其中的 D@2 放到了 C 的⼦⽬录⽽另⼀个 D@1 被放到了再上⼀层⽬录。

npm 5 - package-lock ⽂件

npm 5 发布于 2017 年,这⼀版本依然沿⽤ npm 3 之后扁平化的依赖包安装⽅式,此外最⼤的变化是增加了 package-lock.json ⽂件 package-lock.json 的作⽤是锁定依赖安装结构,相当于本次 install 的⼀个快照,如果查看这个 json 的结构,会发现与 node_modules ⽬录的⽂件层级结构是⼀⼀对应的。

以依赖关系为: app{webpack} 的 ‘app’ 项⽬为例, 其 package-lock ⽂件包含了这样的⽚段。

{

"name": "app",

"version": "0.1.0",

"lockfileVersion": 1,

"requires": true,

"dependencies": {

// ... 其他依赖包

"webpack": {

"version": "1.8.11",

"resolved": "https://registry.npmjs.org/webpack/-/webpack-1.8.11.tgz",

"integrity": "sha1-Yu0hnstBy/qcKuanu6laSYtgkcI=",

"requires": {

"async": "0.9.2",

"clone": "0.1.19",

"enhanced-resolve": "0.8.6",

"esprima": "1.2.5",

"interpret": "0.5.2",

"memory-fs": "0.2.0",

"mkdirp": "0.5.1",

"node-libs-browser": "0.4.3",

"optimist": "0.6.1",

"supports-color": "1.3.1",

"tapable": "0.1.10",

"uglify-js": "2.4.24",

"watchpack": "0.2.9",

"webpack-core": "0.6.9"

}

},

"webpack-core": {

"version": "0.6.9",

"resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz",

"integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=",

"requires": {

"source-list-map": "0.1.8",

"source-map": "0.4.4"

},

"dependencies": {

"source-map": {

"version": "0.4.4",

"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",

"integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",

"requires": {

"amdefine": "1.0.1"

}

}

}

}

//... 其他依赖包

}

同样类型的⼏个字段嵌套起来,主要是 version, resolved, integrity, requires, dependencies

version, resolved, integrity ⽤来记录包的准确版本号、安装源的、内容 hash,决定了要安装的包的准确“⾝份”信息

只关注⽂件中的 dependencies: {} 我们会发现, dependencies 的 JSON 配置层次结构与⽂件系统中 node_modules 的⽂件夹层次结构是完全对照的

只关注 requires: {} 字段又会发现,除最外层的 requires 属性为 true 以外, 其他层的 requires 属性都对应着这个包的 package.json ⾥记录的⾃⼰的依赖项

因为这个⽂件记录了 node_modules ⾥所有包的结构、层级和版本号甚⾄安装源,它也就事实上提供了 “保存” node_modules 状态的能⼒。只要有这样⼀个 lock ⽂件,不管在那⼀台 机器上执⾏ npm install 都会得到完全相同的 node_modules 结果。

package-lock 优化的场景:在从前仅仅⽤ package.json 记录依赖,由于 semver range 的机 制;⼀个⽉前由 A ⽣成的 package.json ⽂件,B 在⼀个⽉后根据它执⾏ npm install 所得到 的 node_modules 结果很可能许多包都存在不同的差异,虽然 semver 机制的限制使得同⼀ 份 package.json 不会得到⼤版本不同的依赖包,但同⼀份代码在不同环境安装出不同的依赖包,依然是可能导致意外的潜在因素。

npm 5 默认会在执⾏ npm install 后就⽣成 package-lock ⽂件,并且建议提交到代码库中。

在 npm 5.0 中,如果已有 package-lock ⽂件存在,若⼿动在 package.json ⽂件新增⼀条依赖,再执⾏ npm install, 新增的依赖并不会被安装到 node_modules 中, package-lock.json 也不会做相应的更新。这样的表现与使⽤者的⾃然期望表现不符。所以不要使⽤ 5.0。

依赖版本升级

npm 2.x/3.x 已成为过去式,在 npm 5.x 以上环境下(版本最好在 5.6 以上,因为在 5.0 ~5.6 中间对 package-lock.json 的处理逻辑更新过⼏个版本,5.6 以上才开始稳定)

当 package.json 和 package-lock.json 同时存在时,npm install 会去检测 package-lock.json 指定的依赖版本是否在 package.json 指定的范围内。如果在,则安装 package-lock.json 指定的版本。如果不在,则忽略 package-lock.json,并且⽤安装的新版本号覆盖 package-lock.json

以^版本为例

在⼤版本相同的前提下,如果⼀个模块在 package.json 中的⼩版本要⼤于 package-lock.json 中的⼩版本,则在执⾏ npm install 时,会将该模块更新到⼤版本下的最新的版本,并将版本号更新⾄ package-lock.json。如果⼩于,则被 package-lock.json 中的版本锁定。

如果⼀个模块在 package.json 和 package-lock.json 中的⼤版本不相同,则在执⾏ npminstall 时,都将根据 package.json 中⼤版本下的最新版本进⾏更新,并将版本号更新⾄ package-lock.json。

如果⼀个模块在 package.json 中有记录,⽽在 package-lock.json 中⽆记录,执⾏ npminstall 后,则会在 package-lock.json ⽣成该模块的详细记录。同理,⼀个模块在 package.json 中⽆记录,⽽在 package-lock.json 中有记录,执⾏ npm install 后,则会在 package-lock.json 删除该模块的详细记录。

如果要更新某个模块⼤版本下的最新版本(升级⼩版本号),请执⾏如下命令:

npm update packageName

如果要更新到指定版本号(升级⼤版本号),请执⾏如下命令:

npm install packageName@x.x.x

卸载某个模块,请执⾏如下命令,或者⼿⼀动删除 package.json 中记录

npm uninstall packageName

安装模块的确切版本:

npm install packageName -D/S --save-exact # 安装的版本号将会是精准的,版本号前⾯不会出现^~字符

通过上述的命令来管理依赖包,package.json 和 package-lock.json 中的版本号都将会随之更新。

我们在升级/卸载依赖包的时候,尽量通过命令来实现,避免⼿动修改 package.json 中的版本号,尤其不要⼿动修改 package-lock.json

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值