依赖嵌套地狱

虽然新版本的npm也和yarn一样使用了扁平化管理依赖,但是其依赖依旧取决于安装的顺序。

比如A依赖中它依赖一个公共库H 1.0版本,那么安装A时同时也会安装H,此时node_modules不存在A、H,那么A和H处于同级关系。

过段时间我们需要安装B模块,B模块依赖H的2.0版本,但是node_modules依旧存在了1.0,于是它2.0只能再B模块下的node_modules目录。

此时我们存在了两个版本的H公共库。

又过一段时间,我们需要安装C模块,C模块也需要H 2.0,那么他也只能将H存放再自己的node_modules目录下。

此时我们的H公共库存在了三个,那么还有更多呢,D模块、E模块等等,如果都要依赖于H2.0,那么2.0就只能存在多份了。

如果又过了一段时间,我们升级A模块,新版的A采用H 2.0,那么安装时就会删除同级目录下的H1.0,下载2.0放在同级,但是其他模块下的node_modules目录依旧存在H 2.0,此时同一份依赖还是会存在多份的情况。

此时我们可以删除node_modules目录重新安装以得到一个清爽的层级结构。

如果不删除,也有对应的命令:

  1. npm npm dedupe
  2. yarn yarn dedupe,yarn会在安装依赖时自动执行这个,所以用不着手动

ci 环境npm优化 >=5.7

npm在安装的时候,会读取lock文件,如果不存在,则通过package.json文件读取依赖版本,获取包的信息并构建依赖树(生成lock文件),安装对应的依赖到node_modules目录下。

如果存在lock文件,npm不同的版本会有不同的处理方式:

  1. npm v5.0.x 会根据lock里面的配置进行下载
  2. npm v5.1.0 - v5.4.2 当package.json文件中版本有符合更新的版本时,会忽略lock文件,安装包后更新lock文件
  3. npm v5.4.2 以上,当package.json声明的依赖版本规范与lock文件中安装的版本兼容,则会根据lock文件安装,否则则根据json文件安装,并更新lock文件。

对于符合版本规范和更新版本找到一个合适网友解释:

原因:package-lock.json里是会保存项目所有的依赖(包括依赖的依赖)的版本,下载地址等。 因为项目package.json中对依赖版本不同的要求,会有不同写法,比如限定最低版本,限定版本范围等等。所以在不同时间运行npm i,可能期间有些依赖会出新的版本,造成package-lock.json变动。 就算自己的项目直接依赖固定了版本号,但是你的依赖的依赖没法固定,也会出现这种现象。

如果不想变动lock文件,可以再安装命令后面加上--no-save

但是这只是不改变lock文件,你本地依赖还是会发生更新。

需要注意,npm版本一定要一致,开发时

那么在ci环境中,我们怎么避免这个问题?

使用npm ci 进行安装,它有以下优势

  1. 项目中必须存在lock文件或者是npm-shrinkwrap.json
  2. 完全根据lock文件安装依赖
  3. 也正因为完全根据lock文件安装依赖,不需要去判断依赖的依赖是否需要更新,因此安装更为迅速
  4. 安装时会完全删除现有的node_modules目录,然后重新安装

注意:

  1. ci 安装只能安装所有的依赖包,无法安装单个依赖包
  2. 如果lock文件和package.json文件出现冲突,那么就会报错,并不会更新lockfiles
  3. ci 安装永远不会改变lock文件和package.json文件

再ci环境中,推荐使用npm ci安装,以获得更加稳定,一致,迅速的安装体验

删除node_modules和lockfiles,再重新install,这样操作是否存在风险?

本质上说并不会,lock文件在现在的版本中会自动生成,并且删除node_modules有利于生成新的依赖结构,并且能解决一些依赖安装问题。

唯一的风险就是低版本npm不会有lock文件,但是现在都7.xx了,不用考虑。

把所有依赖安装到dependencies中,不区分devDependencies会有问题吗?

没什么影响。

这两个字段本意是为了加快项目下载依赖速度的。

比如可能存在单元测试、代码格式化等依赖项插件,这些依赖项对于这个项目本身而言并不是导入的模块,而是在开发时才需要用到,那么对于仅仅想运行一下项目、而非要开发项目的人来说,也必须 npm install 一遍。可你要知道 npm 早期版本可是以安装慢著称的(虽然从 npm 5.0 之后已经有很大改善了)。

所以搞出来了两个字段,如果只是想跑一遍项目,那么 npm install --production 就好了,这样只会下载安装 dependencies 里的依赖项。

但对于现在的前端来说,如果不是工具库的开发者,那么 Webpack 一类的 Bundle 工具基本就是必需品(Vue、React,三大框架中的俩依赖于此)。你说 babel 之类的依赖项,算 dependencies 里吧可你又的确没在项目里 import Babel from 'babel' 过;算 devPependencies 里吧可你真要 npm install --production 不安装它的话项目还是跑不起来。所以这俩字段就变成了一个比较鸡肋的存在。

一般来说的话,在运行阶段依赖的项,放到 dependencies 里;仅仅在开发、测试、编译阶段依赖的项,放到 devPependencies。遵循这个原则就可以了。

应用依赖了公共库A和公共库B,同时A也依赖了公共库B,那么公共库B会被多次安装或重复打包吗?

如果已经存在旧版本,那么新版本子依赖会存在于依赖目录下的node_modules中,如果没有冲突,那么则会共用一个,并不会重复安装,打包的话也是看版本,如果同一个版本不会重复。

一个项目中,即有人用npm,也有人用yarn,这会引发什么问题?

首先会重复下载依赖,yarn下载的依赖,npm会重新下载一次,两者会用,会有警告提示lock文件可能会导致不同的决策产生问题。

一般来说不建议混用,非要用,先删除node_modules再重新安装,推荐是yarn,目前又出了个pnpm,推荐用pnpm

我们是否应该提交lockfiles文件到项目仓库呢?

了解下lock文件

首先lock文件缓存了每个依赖包的具体版本和下载地址,这样就不需要再去远程仓库查询,直接下载并校验包的完整性。

lock文件锁定了依赖和安装结构,保证在任意机器上执行npm install都能得到完全相同的node_modules目录。

为什么package.json不能确定唯一的依赖树

  1. 不同的npm版本安装依赖的策略和算法不同
  2. npm install 将根据package.json中的semver-range version(半范围版本)更新依赖,某些依赖自上次安装以来,可能已发布了新版本

因此,为了能保证完整准确的还原项目依赖,lockfile才会出现。

下面是一个项目中一个依赖的lock结构展示:

"@babel/core": {
      "version": "7.15.5",
      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.15.5.tgz",
      "integrity": "sha512-pYgXxiwAgQpgM1bNkZsDEq85f0ggXMA5L7c+o3tskGMh2BunCI9QUwB9Z4jpvXUOuMdyGKiGKQiRe11VS6Jzvg==",
      "dev": true,
      "requires": {
        "@babel/code-frame": "^7.14.5",
        "@babel/generator": "^7.15.4",
        "@babel/helper-compilation-targets": "^7.15.4",
        "@babel/helper-module-transforms": "^7.15.4",
        "@babel/helpers": "^7.15.4",
        "@babel/parser": "^7.15.5",
        "@babel/template": "^7.15.4",
        "@babel/traverse": "^7.15.4",
        "@babel/types": "^7.15.4",
        "convert-source-map": "^1.7.0",
        "debug": "^4.1.0",
        "gensync": "^1.0.0-beta.2",
        "json5": "^2.1.2",
        "semver": "^6.3.0",
        "source-map": "^0.5.0"
      },
      "dependencies": {
        "semver": {
          "version": "6.3.0",
          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
          "dev": true
        }
      }
    },
  • version:依赖的版本号
  • resolved:包的下载地址
  • integrity:完整性校验的hash值
  • dev:如果包是严格的devDependencies树的一部分,那么dev将为true。如果严格来说它是optionalDependencies树的一部分,那么optional将被设置。如果它既是一个开发依赖,又是一个非开发依赖的可选依赖,那么将设置devOptional。(dev依赖项的可选依赖项将同时设置devoptionaltrue)
  • requires:该模块所需要的所有依赖项
  • dependencies:依赖包自己node_modules下的依赖

注意:dependencies并不是每个依赖都会有,它的存在取决于子依赖与当前已安装的依赖是否存在冲突,也就是最开头讲的H依赖版本不同的问题,当冲突时,才会有这个属性

要不要提交?

这个需要看项目来进行决定

  1. 如果开发一个应用,建议是上传lock文件,这样协同开发时能保持一致性的依赖结构,ci 安装时需要。
  2. 如果是开发一个外部使用的库,如koa的插件,那么前提条件一定是用户存在了koa,那么我们的依赖项一定会存在,那么就不需要上传,已减少依赖重复和体积
  3. 如果开发的库依赖了一个精确的版本号模块,上传lock文件可能会使得已下载过的依赖中存在多个相同的模块,但是版本不同(考虑dedupe)
  4. 如果作为库的开发者,真的有使用某个特定的版本依赖需要,建议是使用peerDependencies声明依赖

一些历史知识

早期npm锁定版本的方式是使用npm-shrinkwrap.json,与package-lock.json不同点在于: npm包发布的时候默认将npm-shrinkwrap.json发布,因此类库或者组件需要慎重。

使用package-lock.json是npm v5.x版本新增特性,而npm v5.6以上才逐步稳定,在5.0- 5.6中间,对package-lock.json的处理逻辑进行过几次更新。

在npm v5.0.x版本中,npm install时都会根据package-lock.json文件下载,不管package.json内容究竟是什么

npm v5.1.0版本到npm v5.4.2, npm install会无视package-lock.json文件,下载最新的npm包并且更新package-lock.json

npm 5.4.2 版本后

如果项目中只有package.json文件,npm install之后,生成一个package-lock.json文件。

如果项目中存在package.json和package-lock.json文件,同时package.json的semver-range版本和package-lock.json中版本兼容,即使此时有新的适用版本,npm install还是会根据package-lock.json下载。

如果项目中存在package.json和package-lock.json文件,同时package.json的semver-range版本和package-lock.json中版本不兼容,npm install时package-lock.json将会更新到兼容package.json的版本。

如果package-lock.json和npm-shrinkwrap.json同时存在于项目根目录,package-lock.json将会被忽略。

npm设计依赖类型声明

  1. dependencies 项目依赖
  2. devDependencies 开发依赖
  3. peerDependencies 同版本依赖
  4. bundledDependencies 捆绑依赖
  5. optionalDependencies 可选依赖

并不是只有在dependencies中的依赖才会被打包,devDependencies 中的依赖也会被打包,这取决于项目中的代码是否使用了devDependencies 中的依赖。

最佳建议

  1. 优先使用npm v5.4.2以上的npm版本,以保证npm的最基本先进性和稳定性。
  2. 项目的第一次搭建使用 npm install 安装依赖包,并提交package.json、package-lock.json,而不提交node_ modules目录。
  3. 其他项目成员首次checkout/clone项目代码后,执行一-次npm install安装依赖包

升级依赖

  • 依靠npm update命令升级到新的小版本
  • 依靠npm install @升级大版本
  • 也可以手动修改package.json中版本号,并执行npm install来升级版本
  • 本地验证升级后新版本无问题,提交新的package.json、package-lock.json 文件

降级依赖

执行npm install @命令,验证没问题后,提交新的package.json、package-lock.json 文件。

删除依赖

两种方式:

  1. 执行npm uninstall 命令,验证没问题后,提交新的package.json、package-lock.json 文件
  2. 或者手动操作package.json,删除依赖,执行npm install 命令,验证没问题后,提交新的package.json、package-lock.json 文件

团队合作

  • 任何团队成员提交package.json、 package-lock.json 更新后,其他成员应该拉取代码后,执行npm install更新依赖
  • 任何时候都不要修改package-lock.json
  • 如果package-lock.json出现冲突或问题,建议将本地的package-lock.json文件删除,引入远程的package-lock.json文件和package.json,再执行npm install命令。

npm 企业级部署私服原理

npm 中的源(registry),其实就是一个查询服务。以 npmjs.org 为例,它的查询服务网址是 https://registry.npmjs.org/ ,在这个网址后加上依赖的名字,就会得到一个 JSON 对象,里面包含了依赖所有的信息。例如:

我们可以通过 npm config set registry 命令来设置安装源。你知道我们公司为什么要部署私有的 npm 镜像吗?虽然 npm 并没有被屏蔽,但是下载第三方依赖包的速度依然较缓慢,这严重影响 CI/CD 流程或本地开发效率。通常我们认为部署 npm 私服具备以下优点:

  1. 确保高速、稳定的 npm 服务
  2. 确保发布私有模块的安全性
  3. 审核机制可以保障私服上 npm 模块质量和安全

部署企业级私服,能够获得安全、稳定、高速的保障。

分类: 前端基础建设与架构 标签: yarnnpm

评论

暂无评论数据

暂无评论数据

目录