为什么“npm install”会重写package-lock.json?

我最近刚刚升级到npm@5。我现在有一个package-lock.json文件,其中包含package.json的所有内容。我希望,当我运行npm install时,依赖版本将从锁文件中提取,以确定应该在我的node_modules目录中安装什么。奇怪的是,它实际上最终会修改和重写我的package-lock.json文件。

例如,锁文件的打字稿指定为版本2.1.6。然后,在npm install命令之后,版本更改为2.4.1。这似乎违背了锁文件的全部目的。

我错过了什么?我如何让npm真正尊重我的锁文件?

379053 次浏览

你可能有这样的东西:

"typescript":"~2.1.6"

在您的package.json中,npm更新到最新的次要版本,在您的情况下为2.4.1

编辑:来自OP的问题

但这并不能解释为什么“npm install”会更改锁文件。锁文件不是为了创建可重复的构建吗?如果是这样,不管semver值如何,它应该仍然使用相同的2.1.6版本。

答复:

这是为了锁定你的完整依赖树。假设typescript v2.4.1需要widget ~v1.0.0。当你npm安装它时Grabswidget v1.0.0。稍后在您的开发人员(或CI构建)上执行npm安装并获得typescript v2.4.1widget已被更新到widget v1.0.1。现在您的节点模块不同步。这package-lock.json阻止了什么。

或者更一般地说:

作为一个例子,考虑

套餐A:

{"name":"A","version":"0.1.0","B":"<0.1.0" } }

套餐B:

"name":"B","version":"0.0.1","C":"<0.1.0" } }

和包C:

{"name":"C","version":"0.0.1"}请求参数

如果这些是唯一的版本注册表中可用的A、B和C,然后是正常的npm安装A将安装:

A@0.1.0B@0.0.1C@0.0.1

但是,如果B@0.0.2发布,则新的npm安装A将安装:

A@0.1.0B@0.0.2--C@0.0.1假设新版本没有修改B的依赖项。当然,新版本的B可以包括一个新的版本的C和任意数量的新依赖项。如果此类更改是不可取的是,A的作者可以指定对B@0.0.1的依赖。然而,如果A的作者和B的作者不是同一个人,那么A的作者没有办法说他或她不想加入新发布的C版本,当B根本没有改变时。


OP问题2:让我看看我是否理解正确。你是什么也就是说锁文件指定了辅助文件的版本依赖关系,但仍依赖于package.json的模糊匹配来确定顶级依赖关系。这准确吗?

答案:不,包锁锁定整个包树,包括package.json中描述的根包。如果typescript被锁定在你的package-lock.json中的2.4.1,它应该保持这种方式,直到它是改变了。假设明天typescript发布版本2.4.2。如果我签出你的分支并运行npm install,npm将尊重锁文件并安装2.4.1

更多关于package-lock.json

对于任何npm修改node_modules树或package.json.的操作,都会自动生成package-lock.json它描述了生成的确切树,以便后续安装能够生成相同的树,而不管中间依赖项更新如何。

此文件旨在提交到源存储库中,并用于各种目的:

描述依赖树的单个表示,以便保证队友、部署和持续集成安装完全相同的依赖项。

为用户提供“时间旅行”到以前的node_modules状态的工具,而无需提交目录本身。

通过可读的源代码控制差异来提高树更改的可见性。

并通过允许npm跳过先前安装的包的重复元数据解析来优化安装过程。

https://docs.npmjs.com/files/package-lock.json

更新3:正如其他答案所指出的那样,npm ci命令在npm 5.7.0中被引入,作为在CI上下文中实现快速和可再现构建的额外方法。有关更多信息,请参阅留档npm博客


更新2:更新和澄清留档的问题是GitHub问题#18103


更新1:下面描述的行为在npm 5.4.2中得到了修复:当前预期的行为在GitHub问题#17979中概述。


原答复(5.4.2之前):package-lock.json的行为在npm5.1.0项目名称中发生了变化,如问题#16866所述。您观察到的行为显然是npm从5.1.0版开始的意图。

这意味着只要在package.json中找到较新版本的依赖项,package.json就可以覆盖package-lock.json。如果你想有效地固定你的依赖项,你现在必须指定不带前缀的版本,例如,你需要将它们写成1.2.0而不是~1.2.0^1.2.0。然后package.jsonpackage-lock.json的组合将产生可重现的构建。需要明确的是:仅package-lock.json不再锁定根级别的依赖项!

这个设计决定是否好是有争议的,在问题#17979中,GitHub上的这种混乱导致了持续的讨论。(在我看来,这是一个值得怀疑的决定;至少lock这个名字不再适用了。)

还有一点需要注意:对于不支持不可变包的注册表也有限制,例如当您直接从GitHub提取包而不是npmjs.org.请参阅这留档的包裹锁以获得进一步的解释。

看来这个问题在npm v5.4.2中得到了修复

https://github.com/npm/npm/issues/17979

(向下滚动到线程中的最后一条评论)

更新

实际上是在5.6.0中修复的。5.4.2中的跨平台bug导致问题仍然发生。

https://github.com/npm/npm/issues/18712

更新2

点击这里查看答案:https://stackoverflow.com/a/53680257/1611058

npm ci是您现在安装现有项目时应该使用的命令。

将来,您将能够使用--from-lock-file(或类似)标志从package-lock.json安装只有而无需修改它。

这对于CI等环境非常有用,其中可重现的构建很重要。

请参阅https://github.com/npm/npm/issues/18286以跟踪该功能。

编辑:名称“lock”是一个棘手的名字,它的NPM试图赶上Yarn。它根本不是一个锁定的文件。package.json是一个用户固定的文件,一旦“安装”将生成node_modules文件夹树,然后该树将被写入package-lock.json。所以你看,它是相反的-依赖版本将一如既往地从package.json中拉出来,package-lock.json应该被称为package-tree.json

(希望这能让我的回答更清晰,经过这么多的否决)


一个简单的答案:package.json像往常一样有你的依赖关系,而package-lock.json是“一个精确的,更重要的是可重复的node_modules树”(取自npm docs本身)。

至于棘手的名字,它的NPM试图赶上Yarn。

在他们的github页面上有一个开放的问题:https://github.com/npm/npm/issues/18712

当开发人员使用不同的操作系统时,这个问题最严重。

我发现会有一个新版本的npm5.7.1,带有新命令npm ci,只能从package-lock.json安装

新的npm ci命令仅从您的锁文件安装。如果您的package.json和锁文件不同步,那么它将报告错误。

它的工作原理是扔掉你的node_modules,从头开始重新创建它。

除了保证你只得到锁文件中的内容之外,它也比不从node_modules开始的npm install快得多(2x-10x!)。

正如您可能从名称中看出的那样,我们预计它将成为持续集成环境的一大福音。我们还希望从git标签进行生产部署的人将看到重大收益。

使用新引入的

npm ci

npm ci有望为大型团队带来最大的好处。让开发人员能够在包锁上“签名”,可以促进大型团队之间更有效的协作,而准确安装锁文件中的内容的能力有可能每月节省数十甚至数百个开发小时,让团队有更多时间花在构建和交付惊人的东西上。

引入npm ci以实现更快、更可靠的构建

简短回答:

  • npm install荣誉package-lock.json只有当它满足package.json.的要求
  • 如果它不满足这些要求,则更新包并覆盖包锁。
  • 如果您希望安装失败而不是在发生这种情况时覆盖包锁,请使用npm ci

这是一个可能解释事情的场景(使用NPM 6.3.0验证)

您可以在package.json中声明依赖项,例如:

"depA": "^1.0.0"

然后你做,npm install将生成一个package-lock.json:

"depA": "1.0.0"

几天后,一个较新的次要版本“DepA”发布,说“1.1.0”,那么以下情况成立:

npm ci       # respects only package-lock.json and installs 1.0.0
npm install  # also, respects the package-lock version and keeps 1.0.0 installed# (i.e. when package-lock.json exists, it overrules package.json)

接下来,手动将package.json更新为:

"depA": "^1.1.0"

然后重新运行:

npm ci      # will try to honor package-lock which says 1.0.0# but that does not satisfy package.json requirement of "^1.1.0"# so it would throw an error
npm install # installs "1.1.0" (as required by the updated package.json)# also rewrites package-lock.json version to "1.1.0"# (i.e. when package.json is modified, it overrules the package-lock.json)

使用npm ci命令而不是npm install

“ci”代表“干净安装”。

它将基于package-lock.json文件安装项目依赖项,而不是package.json文件依赖项。

它将生成与您的队友相同的构建,并且速度也快得多。

您可以在这篇博客文章中阅读更多信息:https://blog.npmjs.org/post/171556855892/introducing-npm-ci-for-faster-more-reliable

也许你应该用这样的东西

npm ci

而不是使用npm install如果您不想更改包的版本。

根据官方留档,npm installnpm ci都安装了项目所需的依赖项。

主要区别在于,npm install确实安装了以packge.json为参考的软件包。在npm ci的情况下,它确实安装了以package-lock.json为参考的软件包,确保每次安装确切的软件包。

npm install检测对package.json文件所做的任何更改,以相应地反映依赖列表。

例如。如果用户添加或删除了新的依赖项,构建将下载或删除本地计算机中的依赖项。我们可以将其与java中的. m2存储库进行比较,其中maven不断跟踪pom.xml文件以更新依赖项。

package-lock.json是内部进程在运行时使用的package.json的副本,唯一的区别是package-lock.json对用户是只读的。