一些关于 npm
中依赖和版本号的小结。知识盲区实在是太多了!
npm
中的依赖
npm
中的依赖主要有 dependencies
、devDependencies
和 peerDependencies
。
dependencies
vs. devDependencies
顾名思义,前者是在生产环境和开发环境中都使用的包,而后者是仅在开发环境中使用的包。
比如 gulp
、webpack
就是典型的只在开发环境中使用的包,无需打包到产品中。
开发环境和生产环境,可以通过 NODE_ENV=production|development
来指定。
对于 npm
而言,安装两种不同的依赖需要指定的完整标志分别是 --save
和 --save-dev
。
peerDependencies
对于一般的 dependencies
的场合:如果我们有包 A 引用包 B 作为依赖,而我们在我们的项目中安装了包 A 作为依赖,那么实际上只有包 A 被安装到了我们的项目的 node_modules
中,包 B 实际上被安装到了包 A 安装目录下的 node_modules
中。这带来的结果是,虽然包 A 和包 B 都安装过了,我们可以使用 require
引用包 A,但是却不可以直接在我们的项目中引用包 B。
但是如果在包 A 中,包 B 是作为 peerDependencies
引入的,那么安装包 A 时,包 B 会被同样安装到我们项目的 node_modules
目录下;此时就可以直接在我们的项目中引用包 B。
综上所述,peerDepenedencies
的含义可以理解为对包管理器的要求:如果你安装了某个包,那么我建议你也安装我的 peerDependencies
。
npm2
在安装包时会强制安装包的 peerDependencies
,不需要再宿主环境中指定对于这些包的依赖;而 npm3
不再强制安装这些依赖,而是在安装结束后检查本次安装是否正确;如果发现安装不正确则打印 WARN。不正确包括未安装和版本不匹配两种情况。
而如果出现了这种不正确的问题,只能手动解决:比如手动将这些依赖增加到 package.json
或者修改它们的版本使得这些依赖可以符合要求,然后 npm install --force
…… 太蠢了!
1 | WARN using --force I sure hope you know what you are doing |
npm
版本号规则
整体来说,npm
的版本号的格式是 <major>.<minor>.<patch>
的形式。
版本号匹配规则
出现在 package.json
中的各种依赖包的版本实际上是匹配规则,又被称为 npm
语义化版本。一般来说有以下常用的匹配规则:
写法 | 含义 |
---|---|
version | 精确匹配某个特定版本 |
>version <version >=version <=version | 大于、小于、大于等于、小于等于某个特定版本;表示了一个范围 将两个规则连写也可以表示一个范围;如: >=version1 <version2 |
~version | 大致匹配某个版本。具体来说规则如下: 1)指定到 <patch> :形如 ~a.b.c ;指大于当前指定的版本,但是小于下一个次版本号的所有版本2)指定到 <minor> :形如 ~a.b ;指固定主版本号和次版本号,补丁版本号任意3)指定到 <major> :形如 ~a ;指固定主版本号,次版本号和补丁任意 |
^version | 兼容某个版本,其含义是版本号中最左的非 0 版本号的右侧版本可以任意; 如 ^a.b.c 实际上等价于 >=a.b.c 和 <a+1.0.0 同时成立;而 ^0.a.b 则等价于 >=0.a.b 和 <0.a+1.0 同时成立;如果缺省了某个低权重的版本号,那么缺失的位置可以任意(此时类似 ~version ) |
标识符 x | 标识符 x 的位置可以填入任何数字 |
标识符 * | 表示任意版本;等价于留空规则;严格的来说等价于 >=0.0.0 |
version1 - version2 | 匹配了 [version1, version2] 双闭区间的版本 |
range1 || range2 | 操作符 || 可以连接多个范围,表示匹配多个范围内的版本 |
由此可以看出 npm
语义化版本中有各种各样的模糊和范围,这为前端的工程化引入了一些问题:当某些包升级过程中没有遵循语义化版本,可能会导致每次打包生成代码都不同;所以我们需要特定项目依赖的包的版本号,为此各大包管理器都引入了 lockfile
的机制来锁定项目依赖的版本号。
开源的包一般都不包含 lockfile
,其原因可能是为了避免特定过多具体的包导致引用较多开源包重复打包某个包的不同版本使得工程体积膨胀,故只能信任其依赖的包遵循语义化版本的要求,某个小版本/大版本的功能不发生过大变化而可以兼容。
包管理器的 install
和 update
以 npm
为例,安装有 install
和 update
两种命令;它们之间的区别主要体现在两个方面:
对于已安装的模糊版本
install
会忽略模糊版本,而 update
会更新模糊版本至最新版。
对于未安装的包,两者都会直接安装
对于 devDependencies
install
会安装/更新 devDependencies
,除非指定 --production
标志。
而 update
会忽略 devDependencies
,除非指定 --dev
标志。