• 聊聊package.json 和 package-lock.json


    原文链接:https://www.cnblogs.com/yalong/p/15013880.html

    package.json 跟 package-lock.json 的知识点挺多的, 这里只聊一聊部分知识点

    先看下dependencies与devDependencies

    npm install 安装依赖的时候,可以通过如下方式,把依赖写入package.json

    npm install --save     或者  npm install -S
    npm install --save-dev 或者  npm install -D
    

    dependencies与devDependencies的区别:

    devDependencies下列出的模块,是我们开发时用的依赖项,像一些进行单元测试之类的包,比如jest,我们用写单元测试,它们不会被部署到生产环境。dependencies下的模块,则是我们生产环境中需要的依赖,即正常运行该包时所需要的依赖项。

    记着这句: "正常运行该包时所需要的依赖项"
    后面的package-lock.json 中的 dependencies 对应的就是package.json中的 dependencies

    Package.json 语义化版本

    使用第三方依赖时,通常需要指定依赖的版本范围,比如

    "dependencies": {
        "antd": "3.1.2",
        "react": "~16.0.1",
        "redux": "^3.7.2",
        "lodash": "*"
      }
    

    上面的 package.json 文件表明,项目中使用的 antd 的版本号是 3.1.2,但是 3.1.1 和 3.1.2、3.0.1、2.1.1 之间有什么不同呢。

    语义化版本规则规定,版本格式为:主版本号.次版本号.修订号,并且版本号的递增规则如下:

    • 主版本号:当你做了不兼容的 API 修改
    • 次版本号:当你做了向下兼容的功能性新增
    • 修订号:当你做了向下兼容的问题修正

    主版本号的更新通常意味着大的修改和更新,升级主版本后可能会使你的程序报错,因此升级主版本号需谨慎,但是这往往也会带来更好的性能和体验。

    次版本号的更新则通常意味着新增了某些特性,比如 antd 的版本从 3.1.1 升级到 3.1.2,之前的 Select 组件不支持搜索功能,升级之后支持了搜索。

    修订号的更新则往往意味着进行了一些 bug 修复。因此次版本号和修订号应该保持更新,这样能让你之前的代码不会报错还能获取到最新的功能特性。

    但是,往往我们不会指定依赖的具体版本,而是指定版本范围,比如上面的 package.json 文件里的 react、redux 以及 lodash,这三个依赖分别使用了三个符号来表明依赖的版本范围。语义化版本范围规定:

    • ~:只升级修订号
    • ^:升级次版本号和修订号
    • *:升级到最新版本

    因此,上面的 package.json 文件安装的依赖版本范围如下:

    react@~16.0.1:>=react@16.0.1 && < react@16.1.0
    redux@^3.7.2:>=redux@3.7.2 && < redux@4.0.0
    lodash@*:lodash@latest
    

    语义化版本规则定义了一种理想的版本号更新规则,希望所有的依赖更新都能遵循这个规则,但是往往会有许多依赖不是严格遵循这些规定的。
    因此,如何管理好这些依赖,尤其是这些依赖的版本就显得尤为重要,否则一不小心就会陷入因依赖版本不一致导致的各种问题中。

    npm的历史变迁

    嵌套结构

    我们都知道,执行 npm install 后,依赖包被安装到了 node_modules ,下面我们来具体了解下,npm 将依赖包安装到 node_modules 的具体机制是什么。

    在 npm 的早期版本, npm 处理依赖的方式简单粗暴,以递归的形式,严格按照 package.json 结构以及子依赖包的 package.json 结构将依赖安装到他们各自的 node_modules 中。直到有子依赖包不在依赖其他模块。

    举个例子,我们的模块 my-app 现在依赖了两个模块:buffer、ignore:

    {
      "name": "my-app",
      "dependencies": {
        "buffer": "^5.4.3",
        "ignore": "^5.1.4",
      }
    }
    

    ignore是一个纯 JS 模块,不依赖任何其他模块,而 buffer 又依赖了下面两个模块:base64-js 、 ieee754。

    {
      "name": "buffer",
      "dependencies": {
        "base64-js": "^1.0.2",
        "ieee754": "^1.1.4"
      }
    }
    

    那么,执行 npm install 后,得到的 node_modules 中模块目录结构就是下面这样的:

    这样的方式优点很明显, node_modules 的结构和 package.json 结构一一对应,层级结构明显,并且保证了每次安装目录结构都是相同的。

    但是,试想一下,如果你依赖的模块非常之多,你的 node_modules 将非常庞大,嵌套层级非常之深:

    • 在不同层级的依赖中,可能引用了同一个模块,导致大量冗余。
    • 在 Windows 系统中,文件路径最大长度为260个字符,嵌套层级过深可能导致不可预知的问题。

    扁平结构

    为了解决以上问题,NPM 在 3.x 版本做了一次较大更新。其将早期的嵌套结构改为扁平结构:

    • 安装模块时,不管其是直接依赖还是子依赖的依赖,优先将其安装在 node_modules 根目录。
      还是上面的依赖结构,我们在执行 npm install 后将得到下面的目录结构:

    此时我们若在模块中又依赖了 base64-js@1.0.1 版本:

    {
      "name": "my-app",
      "dependencies": {
        "buffer": "^5.4.3",
        "ignore": "^5.1.4",
        "base64-js": "1.0.1",
      }
    }
    
    • 当安装到相同模块时,判断已安装的模块版本是否符合新模块的版本范围,如果符合则跳过,不符合则在当前模块的 node_modules 下安装该模块。

    此时,我们在执行 npm install 后将得到下面的目录结构:

    对应的,如果我们在项目代码中引用了一个模块,模块查找流程如下:

    • 在当前模块路径下搜索
    • 在当前模块 node_modules 路径下搜素
    • 在上级模块的 node_modules 路径下搜索
    • ...
    • 直到搜索到全局路径中的 node_modules

    假设我们又依赖了一个包 buffer2@^5.4.3,而它依赖了包 base64-js@1.0.3,则此时的安装结构是下面这样的:

    所以 npm 3.x 版本并未完全解决老版本的模块冗余问题,甚至还会带来新的问题。

    另外,为了让开发者在安全的前提下使用最新的依赖包,我们在 package.json 通常只会锁定大版本,这意味着在某些依赖包小版本更新后,同样可能造成依赖结构的改动,依赖结构的不确定性可能会给程序带来不可预知的问题。

    Lock文件(package-lock.json)

    为了解决 npm install 的不确定性问题,在 npm 5.x 版本新增了 package-lock.json 文件,而安装方式还沿用了 npm 3.x 的扁平化的方式。

    package-lock.json 的作用是锁定依赖结构,即只要你目录下有 package-lock.json 文件,那么你每次执行 npm install 后生成的 node_modules 目录结构一定是完全相同的。

    例如,我们有如下的依赖结构:

    {
      "name": "my-app",
      "dependencies": {
        "buffer": "^5.4.3",
        "ignore": "^5.1.4",
        "base64-js": "1.0.1",
      }
    }
    

    在执行 npm install 后生成的 package-lock.json 如下:

    {
      "name": "my-app",
      "version": "1.0.0",
      "dependencies": {
        "base64-js": {
          "version": "1.0.1",
          "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.0.1.tgz",
          "integrity": "sha1-aSbRsZT7xze47tUTdW3i/Np+pAg="
        },
        "buffer": {
          "version": "5.4.3",
          "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz",
          "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==",
          "requires": {
            "base64-js": "^1.0.2",
            "ieee754": "^1.1.4"
          },
          "dependencies": {
            "base64-js": {
              "version": "1.3.1",
              "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
              "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
            }
          }
        },
        "ieee754": {
          "version": "1.1.13",
          "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
          "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
        },
        "ignore": {
          "version": "5.1.4",
          "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz",
          "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A=="
        }
      }
    }
    

    我们来具体看看上面的结构:

    最外面的两个属性 name 、version 同 package.json 中的 name 和 version ,用于描述当前包名称和版本。

    dependencies 是一个对象,对象和 node_modules 中的包结构一一对应,对象的 key 为包名称,值为包的一些描述信息:

    • version:包版本 —— 这个包当前安装在 node_modules 中的版本
    • resolved:包具体的安装来源
    • integrity:包 hash 值,基于 Subresource Integrity 来验证已安装的软件包是否被改动过、是否已失效
    • requires:对应子依赖的依赖,与子依赖的 package.json 中 dependencies的依赖项相同。
    • dependencies:结构和外层的 dependencies 结构相同,存储安装在子依赖 node_modules 中的依赖包。

    这里注意,并不是所有的子依赖都有 dependencies 属性,只有子依赖的依赖和当前已安装在根目录的 node_modules 中的依赖冲突之后,才会有这个属性。

    例如,回顾下上面的依赖关系:

    我们在 my-app 中依赖的 base64-js@1.0.1 版本与 buffer 中依赖的 base64-js@^1.0.2 发生冲突,所以 base64-js@1.0.1 需要安装在 buffer 包的 node_modules 中,对应了 package-lock.json 中 buffer 的 dependencies 属性。这也对应了 npm 对依赖的扁平化处理方式。

    所以,根据上面的分析, package-lock.json 文件 和 node_modules 目录结构是一一对应的,即项目目录下存在 package-lock.json 可以让每次安装生成的依赖目录结构保持相同。

    package-lock.json使用建议

    开发系统应用时,建议把 package-lock.json 文件提交到代码版本仓库,从而保证所有团队开发者以及 CI 环节可以在执行 npm install 时安装的依赖版本都是一致的。

    在开发一个 npm包 时,你的 npm包 是需要被其他仓库依赖的,由于上面我们讲到的扁平安装机制,如果你锁定了依赖包版本,你的依赖包就不能和其他依赖包共享同一 semver 范围内的依赖包,这样会造成不必要的冗余。所以我们不应该把package-lock.json 文件发布出去( npm 默认也不会把 package-lock.json 文件发布出去,当然如果非要用package-lock.json也是可以的)。

    参考链接:http://www.conardli.top/blog/article/

  • 相关阅读:
    python学习:两个py文件间的函数调用
    python学习:基础概念
    python学习:Windows 下 Python easy_install 的安装
    Python学习:python网址收集
    Nginx模块之Nginx-Ts-Module学习笔记(一)抢险体验
    PHP 文件加密Zend Guard Loader 学习和使用(如何安装ioncube扩展对PHP代码加密)
    OpenResty 扩展库(二)lua-resty-template
    Github 开源项目(二) jsmpeg-vnc
    info replication
    linux下编译make文件报错“/bin/bash^M: 坏的解释器,使用grep快速定位代码位置
  • 原文地址:https://www.cnblogs.com/yalong/p/15013880.html
Copyright © 2020-2023  润新知