We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
npm是Node.js默认的、以JavaScript编写的包管理工具,如今,它已经成为世界上最大的包管理工具,是每个前端开发者必备的工具。不知你是否遇到过下面问题:
哎?我本地明明是好的,线上的依赖怎么就报错不行了呢? 一言不合就删除整个node_modules目录然后重新npm install
哎?我本地明明是好的,线上的依赖怎么就报错不行了呢?
一言不合就删除整个node_modules目录然后重新npm install
node_modules
npm install
今天我们聊聊npm模块相关的东西。
npm 依赖管理的一个重要特性是采用了语义化版本 (semver) 规范,作为依赖版本管理方案。
semver规定的模块版本号格式为:MAJOR.MINOR.PATCH,即主版本号.次版本号.修订号。版本号递增规则如下:
MAJOR.MINOR.PATCH
主版本号.次版本号.修订号
对于npm包的引用者来说,经常会在package.json文件里面看到使用semver约定的semver range来指定所需的依赖包版本号和版本范围。常用的规则如下表:
package.json
^1.2.3
>=1.2.3 <2.0.0
~1.2.3
>=1.2.3 <1.3.0
X
x
*
>=0.0.0
1.x
>=1.0.0 <2.0.0
1.2.x
>=1.2.0 <1.3.0
1.2.3 - 2.3.4
>=1.2.3 <=2.3.4
此外,任意两条规则,用空格连接起来,表示“与”逻辑,即两条规则的交集: 如 >=2.3.1 <=2.8.0 可以解读为: >=2.3.1 且 <=2.8.0。
>=2.3.1 <=2.8.0
>=2.3.1
<=2.8.0
任意两条规则,通过 || 连接起来,表示“或”逻辑,即两条规则的并集: 如 ^2 >=2.3.1 || ^3 >3.2。
||
^2 >=2.3.1 || ^3 >3.2
在修订版本号的后面可以加上其他信息,用-连接,比如:
-
npm install命令用来安装模块到node_modules目录。npm install的具体原理是什么呢?
执行工程自身 preinstall
确定首层依赖模块
首层依赖是package.json中dependencies和devDependencies字段直接指定的模块。每一个首层依赖模块都是模块依赖树根节点下面的一颗子树。
dependencies
devDependencies
获取模块
获取模块是一个递归的过程,分为以下几步:
npm-shrinkwrap.json
package-lock.json
^1.1.0
1.x.x
模块扁平化(npm3后支持)
上一步获取到的是一颗完整的依赖树,下面会根据依赖树安装模块。模块安装机制有两种:嵌套式安装机制和 扁平式安装机制。
例如某工程下直接依赖了A和B两个包,且他们同时依赖了C包。
嵌套式
+-------------------------------------------+ | app/ | +----------+------------------------+-------+ | | | | +----------v------+ +---------v-------+ | | | | | A@1.0.0 | | B@2.0.0 | | | | | +--------+--------+ +--------+--------+ | | +-----v-----+ +-----v-----+ | C@1.0.0 | | C@1.0.0 | +-----------+ +-----------+
npm3之前使用的是嵌套式安装机制,严格按照依赖树的结构进行安装,这可能会造成相同模块大量冗余的问题。
扁平式
+-------------------------------------------+ | app/ | +-+---------------------------------------+-+ | | | | +----------v------+ +-------------+ +---------v-------+ | | | | | | | A@1.0.0. | | C@1.0.0 | | B@2.0.0 | | | | | | | +-----------------+ +-------------+ +-----------------+
npm3之后使用的扁平式安装机制,但是需要考虑一个问题:
工程同时依赖一个模块不同版本该如何解决?
npm3引入了dedupe过程来解决这个问题。它会遍历所有节点,逐个将模块放在根节点下面,也就是 node-modules 的第一层。当发现有重复模块时,则将其丢弃。
重复模块:semver兼容的相同模块。例如lodash ^1.2.0和lodash ^1.4.0。如果工程的两个模块版本范围存在交集,就可以得到一个 兼容版本,不必版本号完全一致,这可以使得更多冗余模块在dedupe过程中被去掉。
lodash ^1.2.0
lodash ^1.4.0
上例中如果A包依赖C@1.0.0,B包依赖C@2.0.0,此时两个版本并不兼容,则后面的版本仍会保留在依赖书中。如下图所示:
C@1.0.0
C@2.0.0
+-------------------------------------------+ | app/ | +-+---------------------------------------+-+ | | | | +----------v------+ +-------------+ +---------v-------+ | | | | | | | A@1.0.0 | | C@1.0.0 | | B@2.0.0 | | | | | | | +-----------------+ +-------------+ +--------+--------+ | | +--------v--------+ | | | C@2.0.0 | | | +-----------------+
实际上,npm3仍然可能出现模块冗余的情况,如下图,因为一级目录下已经有C@1.0.0,所以所有的C@2.0.0只能作为二级依赖模块被安装:
+-----------------------------------------------------------------+ | app/ | +-+---------------------------------------+---------------------+-+ | | | | | | +----------v------+ +-------------+ +---------v-------+ +-------------+ | | | | | | | | | A@1.0.0 | | C@1.0.0 | | B@2.0.0 | | D@1.0.0 | | | | | | | | | +-----------------+ +-------------+ +--------+--------+ +------+------+ | | | | +--------v--------+ +--------v--------+ | | | | | C@2.0.0 | | C@2.0.0 | | | | | +-----------------+ +-----------------+
npm提供了npm dedupe指令来优化依赖树结构。这个命令会去搜索本地的node_modules中的包,并且通过移动相同的依赖包到外层目录去尽量简化这种依赖树的结构,让公用包更加有效被引用。
npm dedupe
安装模块
将会更新工程中的 node_modules,并执行模块中的生命周期函数(按照 preinstall、install、postinstall 的顺序)
preinstall
install
postinstall
执行工程自身生命周期
当前 npm 工程如果定义了钩子此时会被执行(按照 install、postinstall、prepublish、prepare的顺序)。最后生成或者更新版本描述文件。
prepublish
prepare
你是否遇到过本地开发时一切正常,发布线上代码时因为安装依赖的错误导致服务不可用?如果是的话,你要一份版本描述文件。
简单的写死当前工程依赖模块的版本并不能真正锁定依赖版本,因为你无法控制间接依赖,如果间接依赖更新了有问题的模块,你的系统还是可能会有宕机的风险。
lock 文件是当前依赖关系树的快照,允许不同机器间的重复构建。其实npm5之前已经提供了lock文件 — npm-shrinkwrap.json。但是在npm5发布的时候创建了新的lock文件 — package-lock.json,其主要目的是希望能更好的传达一个消息,npm真正支持了locking机制。不过二者还是有一些区别点:
files
npm shrinkwrap
查阅资料得知,自npm 5.0版本发布以来,package-lock.json的规则发生了三次变化。
npm 5.0.x版本,不管package.json怎么变,npm install都会根据lock文件下载。npm/npm#16866控诉了这个问题,我明明手动改了package.json,为啥不给我升包!然后就导致5.1.0的问题(是个bug)
npm 5.1.0 - 5.4.1版本,npm insall会无视lock文件,去下载semver兼容的最新的包。导致lock文件并不能完全锁住依赖树。详情见npm/npm#17979
npm insall
npm 5.4.2版本之后,如果手动改了package.json,且package.json和lock文件不同,那么执行npm install时npm会根据package中的版本号和语义含义去下载最新的包,并更新至lock。
如果两者是同一状态,那么执行npm install都会根据lock下载,不会理会package实际包的版本是否更新。
npm install <package>
^X.Y.Z
npm update
npm install <package-name>@<version>
npm install <package-name>@<old-version>
npm uninstall <package>
svn update/git pull
npm install <some-package-name>
上述最佳实践提到了当团队中有成员提交了package.json, package-lock.json 更新后,其他成员需要执行npm install来保证本地依赖的及时性,那么能否写一个插件将这个手动的环节自动化呢?答案是可以的,我们只需要在git post-merge钩子中检查git diff files是否包含了package.json文件,如果包含了该文件,则执行npm install命令。我们暂且给这个插件取名为hawkeye。当然,这个插件能干的事情不仅于此。
不知作为读者的你听到上述场景描述后,是否有种似曾相识的感觉?没错,lint-staged。
lint-staged
lint-staged,从git staged files变化中匹配你想要的文件,再执行你配置的commands。 Hawkeye,从git diff files变化中匹配你想要的文件,再执行你配置的commands。 需要注意的是,他们都依赖于husky改造git hooks的能力。
lint-staged,从git staged files变化中匹配你想要的文件,再执行你配置的commands。
Hawkeye,从git diff files变化中匹配你想要的文件,再执行你配置的commands。
需要注意的是,他们都依赖于husky改造git hooks的能力。
假设有一个已经安装了hawkeye和husky的项目,package.json如下:
hawkeye
husky
{ "name": "My project", "version": "0.1.0", "scripts": { }, "husky": { "hooks": { "post-merge": "hawkeye" } }, "hawkeye": { "package.json": ["npm install"] } }
The text was updated successfully, but these errors were encountered:
No branches or pull requests
npm是Node.js默认的、以JavaScript编写的包管理工具,如今,它已经成为世界上最大的包管理工具,是每个前端开发者必备的工具。不知你是否遇到过下面问题:
今天我们聊聊npm模块相关的东西。
semver
npm 依赖管理的一个重要特性是采用了语义化版本 (semver) 规范,作为依赖版本管理方案。
semver规定的模块版本号格式为:
MAJOR.MINOR.PATCH
,即主版本号.次版本号.修订号
。版本号递增规则如下:对于npm包的引用者来说,经常会在
package.json
文件里面看到使用semver约定的semver range来指定所需的依赖包版本号和版本范围。常用的规则如下表:^1.2.3
:=>=1.2.3 <2.0.0
~1.2.3
:=>=1.2.3 <1.3.0
X
,x
或*
X
,x
或*
以后的版本号下,所有更新的版本*
:=>=0.0.0
(任何版本满足)1.x
:=>=1.0.0 <2.0.0
(匹配主要版本)1.2.x
:=>=1.2.0 <1.3.0
(匹配主要版本和次要版本)1.2.3 - 2.3.4
:=>=1.2.3 <=2.3.4
此外,任意两条规则,用空格连接起来,表示“与”逻辑,即两条规则的交集: 如
>=2.3.1 <=2.8.0
可以解读为:>=2.3.1
且<=2.8.0
。任意两条规则,通过
||
连接起来,表示“或”逻辑,即两条规则的并集: 如^2 >=2.3.1 || ^3 >3.2
。在修订版本号的后面可以加上其他信息,用
-
连接,比如:从npm install说起
npm install
命令用来安装模块到node_modules
目录。npm install
的具体原理是什么呢?执行工程自身 preinstall
确定首层依赖模块
首层依赖是
package.json
中dependencies
和devDependencies
字段直接指定的模块。每一个首层依赖模块都是模块依赖树根节点下面的一颗子树。获取模块
获取模块是一个递归的过程,分为以下几步:
package.json
中的模块版本往往是 semantic version。此时根据package.json
和版本描述文件(npm-shrinkwrap.json
或package-lock.json
),(不同npm版本的策略不同,后续我们会详细介绍)。如package.json
中某个包的版本是^1.1.0
,npm 就会去仓库中获取符合1.x.x
形式的最新版本。模块扁平化(npm3后支持)
上一步获取到的是一颗完整的依赖树,下面会根据依赖树安装模块。模块安装机制有两种:嵌套式安装机制和 扁平式安装机制。
例如某工程下直接依赖了A和B两个包,且他们同时依赖了C包。
嵌套式
npm3之前使用的是嵌套式安装机制,严格按照依赖树的结构进行安装,这可能会造成相同模块大量冗余的问题。
扁平式
npm3之后使用的扁平式安装机制,但是需要考虑一个问题:
npm3引入了dedupe过程来解决这个问题。它会遍历所有节点,逐个将模块放在根节点下面,也就是 node-modules 的第一层。当发现有重复模块时,则将其丢弃。
上例中如果A包依赖
C@1.0.0
,B包依赖C@2.0.0
,此时两个版本并不兼容,则后面的版本仍会保留在依赖书中。如下图所示:实际上,npm3仍然可能出现模块冗余的情况,如下图,因为一级目录下已经有
C@1.0.0
,所以所有的C@2.0.0
只能作为二级依赖模块被安装:npm提供了
npm dedupe
指令来优化依赖树结构。这个命令会去搜索本地的node_modules
中的包,并且通过移动相同的依赖包到外层目录去尽量简化这种依赖树的结构,让公用包更加有效被引用。安装模块
将会更新工程中的
node_modules
,并执行模块中的生命周期函数(按照preinstall
、install
、postinstall
的顺序)执行工程自身生命周期
当前 npm 工程如果定义了钩子此时会被执行(按照
install
、postinstall
、prepublish
、prepare
的顺序)。最后生成或者更新版本描述文件。锁定npm依赖版本
简单的写死当前工程依赖模块的版本并不能真正锁定依赖版本,因为你无法控制间接依赖,如果间接依赖更新了有问题的模块,你的系统还是可能会有宕机的风险。
lock 文件是当前依赖关系树的快照,允许不同机器间的重复构建。其实npm5之前已经提供了lock文件 —
npm-shrinkwrap.json
。但是在npm5发布的时候创建了新的lock文件 —package-lock.json
,其主要目的是希望能更好的传达一个消息,npm真正支持了locking机制。不过二者还是有一些区别点:package-lock.json
不会被发布, 即使你将其显式添加到软件包的files
属性中,它也不会是已发布软件包的一部分。npm-shrinkwrap.json
可以被发布。npm-shrinkwrap.json
向后兼容npm2、3、4版本,package-lock.json
只有npm5以上支持。npm shrinkwrap
命令将package-lock.json
转换成npm-shrinkwrap.json
, 因为文件的格式是完全一样的。曲折的package-lock.json
查阅资料得知,自npm 5.0版本发布以来,
package-lock.json
的规则发生了三次变化。npm 5.0.x版本,不管
package.json
怎么变,npm install
都会根据lock文件下载。npm/npm#16866控诉了这个问题,我明明手动改了package.json
,为啥不给我升包!然后就导致5.1.0的问题(是个bug)npm 5.1.0 - 5.4.1版本,
npm insall
会无视lock文件,去下载semver兼容的最新的包。导致lock文件并不能完全锁住依赖树。详情见npm/npm#17979npm 5.4.2版本之后,如果手动改了
package.json
,且package.json
和lock文件不同,那么执行npm install
时npm会根据package中的版本号和语义含义去下载最新的包,并更新至lock。如果两者是同一状态,那么执行
npm install
都会根据lock下载,不会理会package实际包的版本是否更新。好的依赖管理方案
package-lock.json
文件默认开启配置npm install <package>
安装依赖包, 默认保存^X.Y.Z
依赖 range 到 package.json中; 提交package.json
,package-lock.json
, 不要提交node_modules
目录npm install
安装依赖包npm update
升级到新的小版本npm install <package-name>@<version>
升级到新的大版本npm install
package.json
,package-lock.json
文件npm install <package-name>@<old-version>
验证无问题后,提交 package.json 和 package-lock.json 文件npm uninstall <package>
并提交package.json
和package-lock.json
npm install
并提交package.json
和package-lock.json
package.json
,package-lock.json
更新后,团队其他成员应在svn update/git pull
拉取更新后执行npm install
脚本安装更新后的依赖包package-lock.json
出现冲突时,这种是非常棘手的情况,最好不要手动解决冲突,如果有一处冲突解决不正确可能会导致线上事故。建议的做法:将本地的
package-lock.json
文件删除,引入远程的package-lock.json
文件,再执行npm install
命令更新package-lock.json
文件(这种做法能保证未修改的依赖不变,会存在一个风险:在执行npm install
的时候,可能有些间接依赖包升级,根据semver兼容原则导致本次安装的和开发时的package-lock.json
文件不同。这种情况就需要验证依赖包升级是否有影响)。npm install
命令。不要执行npm install <some-package-name>
命令,因为这会导致package-lock.json
文件同时被更新。问题来了
上述最佳实践提到了当团队中有成员提交了
package.json
,package-lock.json
更新后,其他成员需要执行npm install
来保证本地依赖的及时性,那么能否写一个插件将这个手动的环节自动化呢?答案是可以的,我们只需要在git post-merge钩子中检查git diff files是否包含了package.json
文件,如果包含了该文件,则执行npm install
命令。我们暂且给这个插件取名为hawkeye。当然,这个插件能干的事情不仅于此。不知作为读者的你听到上述场景描述后,是否有种似曾相识的感觉?没错,
lint-staged
。实现方案
例子
假设有一个已经安装了
hawkeye
和husky
的项目,package.json
如下:相关链接
The text was updated successfully, but these errors were encountered: