关于node.js:为什么“npm install”重写package-lock.json?

Why does “npm install” rewrite package-lock.json?

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

例如,锁定文件的typescript被指定为2.1.6版本。然后,在执行npm install命令后,版本更改为2.4.1。这似乎破坏了锁文件的全部用途。

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


更新3:正如其他答案所指出的那样,在NPM5.7.0中引入了npm ci命令,作为在CI环境中实现快速和可复制构建的额外方法。更多信息请参见文档和NPM博客。

更新2:更新和澄清文件的问题是Github问题18103。

更新1:下面描述的行为在NPM 5.4.2中得到了修正:目前预期的行为在Github发行17979中概述。

原始答案:如16866号问题所述,在NPM 5.1.0中,package-lock.json的行为发生了变化。从5.1.0版开始,您所观察到的行为显然是由NPM计划的。

这意味着,只要在package.json中发现依赖项的更新版本,package.json就可以胜过package-lock.json。如果要有效地固定依赖项,现在必须指定没有前缀的版本,例如,您需要将它们写为1.2.0,而不是~1.2.0^1.2.0。然后,package.jsonpackage-lock.json的组合将产生可复制的构建。要清楚的是:只有EDOCX1[2]不再锁定根级别的依赖项!

无论这个设计决策是好是坏,都是有争议的,因为在17979年的问题中对Github的混淆,有一个持续的讨论。(在我看来,这是一个值得怀疑的决定;至少lock这个名字已经不再适用了。)

另一个注意事项:对于不支持不可变包的注册中心也有一个限制,例如当您直接从github而不是npmjs.org中提取包时。有关进一步的说明,请参阅此软件包锁文档。


我发现将有一个新版本的NPM 5.7.1和新的命令npm ci,它将只从package-lock.json安装。

The new npm ci command installs from your lock-file ONLY. If your package.json and your lock-file are out of sync then it will report an error.

It works by throwing away your node_modules and recreating it from scratch.

Beyond guaranteeing you that you'll only get what is in your lock-file it's also much faster (2x-10x!) than npm install when you don't start with a node_modules.

As you may take from the name, we expect it to be a big boon to continuous integration environments. We also expect that folks who do production deploys from git tags will see major gains.


使用新引进的

1
npm ci

npm ci promises the most benefit to large teams. Giving developers the ability to"sign off" on a package lock promotes more efficient collaboration across large teams, and the ability to install exactly what is in a lockfile has the potential to save tens if not hundreds of developer hours a month, freeing teams up to spend more time building and shipping amazing things.

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


简短回答:

  • 当package-lock.json存在时,它会否决package.json
  • 当package.json被修改时,它会否决package-lock.json
  • 小精灵

    这里有一个场景可以解释问题(用NPM 6.3.0验证)

    在package.json中声明依赖项,如下所示:

    1
    "depA":"^1.0.0"

    然后,您可以这样做,npm install将生成一个package-lock.json,其中包括:

    1
    "depA":"1.0.0"

    几天后,"DEPA"的一个较新的次要版本发布了,比如说"1.1.0",那么下面的内容是正确的:

    1
    2
    3
    4
    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更新为:

    1
    "depA":"^1.1.0"

    然后重新运行:

    1
    2
    3
    4
    5
    6
    7
    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文件安装项目依赖项,而不是基于lenient package.json文件依赖项。

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


    你可能有这样的东西:

    1
    "typescript":"~2.1.6"

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

    Edit: Question from OP

    But that doesn't explain why"npm install" would change the lock file. Isn't the lock file meant to create a reproducible build? If so,
    regardless of the semver value, it should still use the same 2.1.6
    version.

    Answer:

    This is intended to lock down your full dependency tree. Let's say typescript v2.4.1 requires widget ~v1.0.0. When you npm install it
    grabs widget v1.0.0. Later on your fellow developer (or CI build)
    does an npm install and gets typescript v2.4.1 but widget has been
    updated to widget v1.0.1. Now your node module are out of sync. This
    is what package-lock.json prevents.

    Or more generally:

    As an example, consider

    package A:

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

    package B:

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

    and package C:

    { "name":"C", "version":"0.0.1" }

    If these are the only versions
    of A, B, and C available in the registry, then a normal npm install A
    will install:

    [email protected] -- [email protected]
    -- [email protected]

    However, if [email protected] is published, then a fresh npm install A will install:

    [email protected] -- [email protected]
    -- [email protected] assuming the new version did not modify B's dependencies. Of course, the new version of B could include a new
    version of C and any number of new dependencies. If such changes are
    undesirable, the author of A could specify a dependency on [email protected].
    However, if A's author and B's author are not the same person, there's
    no way for A's author to say that he or she does not want to pull in
    newly published versions of C when B hasn't changed at all.

    OP Question 2: So let me see if I understand correctly. What you're
    saying is that the lock file specifies the versions of the secondary
    dependencies, but still relies on the fuzzy matching of package.json
    to determine the top-level dependencies. Is that accurate?

    Answer: No. package-lock locks the entire package tree, including the
    root packages described in package.json. If typescript is locked
    at 2.4.1 in your package-lock.json, it should remain that way until it is
    changed. And lets say tomorrow typescript releases version 2.4.2.
    If I checkout your branch and run npm install, npm will respect the
    lockfile and install 2.4.1.

    关于package-lock.json的更多信息:

    package-lock.json会自动为npm修改node_modules树或package.json的任何操作生成。它描述了生成的确切树,这样以后的安装就能够生成相同的树,而不管中间的依赖更新是什么。

    此文件旨在提交到源存储库中,并具有多种用途:

    Describe a single representation of a dependency tree such that teammates, deployments, and continuous integration are guaranteed to install exactly the same dependencies.

    Provide a facility for users to"time-travel" to previous states of node_modules without having to commit the directory itself.

    To facilitate greater visibility of tree changes through readable source control diffs.

    And optimize the installation process by allowing npm to skip repeated metadata resolutions for previously-installed packages.

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


    将来,您将能够使用EDOCX1(或类似的)标志仅从EDOCX1(或2)安装,而无需修改它。

    这对CI等环境非常有用,因为在这些环境中,可复制的构建非常重要。

    请参阅https://github.com/npm/npm/issues/18286了解功能的跟踪。


    这个问题似乎在NPM v5.4.2中得到了解决。

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

    (向下滚动到线程中的最后一条注释)

    更新

    实际固定在5.6.0中。5.4.2中存在跨平台错误,导致问题仍然存在。

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

    更新2

    请看我的答案:https://stackoverflow.com/a/53680257/1611058

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


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

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


    编辑:名字"锁"是一个棘手的一个,它的NPM试图赶上纱。它不是一个锁定的文件。package.json是一个用户固定的文件,一旦"安装",将生成节点模块文件夹树,然后该树将写入package-lock.json中。所以你看,它的另一种方式是依赖版本将像往常一样从package.json中提取出来,package-lock.json应该称为package-tree.json

    (希望经过这么多的否决后,我的回答更清楚)

    一个简单的答案是:package.json和平常一样有依赖关系,而package-lock.json是"一个精确的、更重要的、可复制的节点模块树"(取自NPM文档本身)。

    至于这个棘手的名字,它的新产品管理试图赶上纱。