


















作为前端开发工程师,提到包、模块管理等内容,大家第一想到的应该就是 npm,npm install 、npm run 等也是日常使用频率最高的几个命令,但是我们很少去关注这些命令执行之后 npm 为我们做了那些事,以及把包安装到 node_modules 中的结构具体是怎样的,为什么会新增 package.lock文件等等? 本文将带着这些问题去了解一下 nodejs 包管理的历史以及其中的一些容易忽略的细节。 在了解 npm 、yarn 、pnpm 工具之前我们可以先认识或了解一下这几个常见的名称,对接下来的讨论也会有一些帮助,package(包)、module(模块),什么的文件可以称为一个 package(包)? 什么又是模块?
各种语言基本都有自己的包管理工具,Ruby的软件包单元为RubyGem,Python 有 PyPi ,JavaScritp 有 npm,除了语言有包管理工具之外,操作系统也有包管理器,我们从最 Linux 的包管理器说起,简单得来说 package manager "包管理器"或“软件包管理器”是一种工具,包管理器可以是一种 GUI 图形应用也可以是 cli 命令行工具,允许用户在操作系统、或特定环境上安装、升级、删除管理软件包,其实并不是只有 MacOS 和 Linux 官方才有软件包管理工具,微软官方也为 Windows 系统发布了一款名为 winget的软件包管理工具,包管理器在各种系统软件和应用软件的安装管理中均有广泛应用。
npm is the standard package manager for Node.js.
npm 是 Node Package Manager 的简称,于2010年发布,是 Node.js 社区最流行的一个包管理工具,当你安装 Node.js的同时也会默认安装 npm.
npm 主要包括两部分:
Online platform: 可以上传分享 JavaScript package 包
Command line tool:
npm 源
npm 大致的组成结构如下图所示:
主要发展历史:
cnpm 的命令基本和 npm 一样,由于 npmjs 的服务器在国外,所以国内开发者在做项目下载依赖包的时候,下载速率比较慢,在这种背景下,阿里团队推出了 cnpm,它是是一个完整的 npmjs 镜像,目前的同步频率的 10 分钟一次保证和官方服务保持一致,私有源的好处:
Safe, stable, reproducible projects
Yarn 是一个快速可靠安全的依赖管理工具,由 Facebook 联合其它几个互联网公司主导的一个开源项目,目前稳定版本 1.22.4 ,yarn 的一些特点:
在 focebook 工程团队的介绍中可以了解到为什么会创作新的包管理工具来替代 npm:
^0.1.1在安装时会出现问题,可能会拉取到不同版本的包,导致在相同的环境中有的同事能正常运行有的不能上面提到过了,不同于 npm 的安装方式,yarn的安装是同步执行任务,这样性能效率有较大提升;npm 和 Yarn 都是通过 package.json 记录项目需要拉取的依赖模块,为了防止拉取到不同的版本,yarn 有一个锁定文件 (lock file) 记录了被确切安装上的模块的版本号。每次只要新增了一个模块,yarn 就会创建(或更新)yarn.lock 这个文件。这么做就保证了,每一次拉取同一个项目依赖时,使用的都是一样的模块版本
同 npm 的一些区别:
npm 随 Node.js 自动安装,yarn 需要手动安装 npm install yarn --global
npm 安装时需要网络,yarn 可以从 cache 中安装
CLI 方面 yarn 拥有更简洁的命令
包版本方面 npm是弱绑定的,而yarn则是强绑定的,确保不同机器环境下安装的版本是一致的
PnP特征解决效率问题,在后面会提到 install 安装的几个步骤:
1.将依赖包的版本区间解析为某个具体的版本号 2.下载对应版本依赖的 tar 包到本地离线镜像 3.将依赖从离线镜像解压到本地缓存 4.将依赖从缓存拷贝到当前目录的 node_modules 目录,
在第四步中涉及到大量的 I/O 操作,PnP 的方案就是省略第四步,通过一个.pnp.js 的文件来记录依赖映射关系、这些包具体位置,这样就不再拷贝文件到具体 node_modules目录里,关于 Yarn 2.0 pnp 特征可以参考 这篇文章
Fast, disk space efficient package manager
什么是 pnpm ? 官方文档是上面这样介绍的,本质上也是一个包管理器,从介绍来看有两大优势,快和节省磁盘空间,概括下:
--offline参数可以完全禁止 HTTP 请求安装速率对比,以这个仓库为例, 主要对比一下 pnpm和 yarn PnP

图片来源(为什么现在我更推荐 pnpm 而不是 npm/yarn?)
npm cnpm yarn pnpm 均是 Node 的依赖管理工具,目前使用范围、流行度: npm > yarn > cnpm > pnpm
npm 使用 package.json 的文件,用户可以通过 npm install 命令把项目里所有的依赖项保存在这个文件里,安装依赖是从国外服务器下载,受网络影响比较大,cnpm 安装方式和命令和 npm 完全一致,但服务器在国内,所以不受网络影响,搭建私有源相对稳定性会有较大提高
Yarn 是重新设计的一个新的包管理工具,速度上有较大的提升,
pnpm 相对于其它几个包管理软件具有以下几个特别:
pnpm add A -r, 那么所有的 package 中都会被添加 A 这个依赖)下载速度对比,pnpm > yarn > npm

在版本 5 中,npm 引入了 package-lock.json 文件,这个功能其实是接受 yarn 的一个特征,核心的目的是跟踪被安装的每个软件包的确切版本,以便产品可以以相同的方式被 100% 复制(即使软件包的维护者更新了软件包)。

package-lock.json 被创建出来的原因,lock 文件会记录当前安装的每个软件包的版本,当运行 npm install时,npm 会使用这些确切的版本。npm i时 npm 会根据package中的版本号以及语义含义去下载最新的包,并更新至lock。如果两者是同一状态,那么执行npm i都会根据l ock下载,不会理会package实际包的版本是否有新。jquery 加上了,vue 则没有写入到 gitignore中 ,新增两个讨论项,相关讨论链接:
这个问题类似于“在浏览器输入 url 回车之后发生了什么” 或者“如何手动实现一个 npm install”,依赖安装管理是 npm 的核心功能,原理就是执行 npm install 从 package.json 中的 dependencies 依赖包安装到当前目录的 ./node_modules 文件夹中,我们以安装 express 为例我们来探讨一下它的安装流程
1npm install express --timing=true --loglevel=verbose
通过上面的这个命令可以在命令行及log日志里看到整个安装过程,大致的流程如下:

图片来自 ConardLi 的剖析 NPM 的包管理机制
如果工程有定义 preinstall 钩子,那在安装前会钩子内容会被执行
例如如果使用 --registry 注册源目录
Check 项目目录中的 .npmrc 文件,关于 npmrc 文件及优先级
项目级 .npmrc 文件 > 用户级 > 全局 > npm 内置
除了项目根目录下的 npmrc 文件外,在你的开发环境中应该存在多个 .npmrc 文件,例如用户配置 npmrc 文件可以通过 npm config get userconfig,如果一台电脑有多个用户,通过通过 prefix 前缀配置全局的 npmrc 文件,可以通过 npm config get prefix 来获取配置信息
内置 npmrc 文件用的不多,基本不需要关注,可以通过npm config list 来查看 npm 的配置
有两种方式设置,例如设置淘宝仓库源:
registry=https://registry.npm.taobao.org检查项目中是否有 package-lock.json 文件,存在读取 lock 文件里包的版本信息,无则跳到 4步骤构建一个依赖树。从 npm 5.x 版本开始不存在 npm 会自动生成一个 package-lock.json 文件,注意这个步骤是在最后一步处理的
检查和 package.json 版本是否冲突,冲突情况(npm v5 版本后为例):
无 lock 文件的情况下,从 npm 远程仓库获取包信息,根据 package.json 构建一个依赖树,构建依赖树并非真正意义的安装,后续安装的时候会根据这个结构去缓存中获取或根据地址去远程仓库下载,从 v3 版本开始 npm 采用扁平化结构存储包,不管是直接依赖还是子依赖优先放置在 node_modules 根目录
npm config get cache 获取到存放地址,一般会存放在 /Users/username/.npm 文件夹下上面提到了关于包的完整性校验,来看下 npm 怎么来做这个完整性校验的,在下载前是能拿到 npm 仓库包文件计算的 hash 值,拿 react 为例我们可以通过 npm info 命令查看 hash 值,如下图所示,为了确保下载后的文件一致,npm 会在下载完成后本地计算一下文件的 hash ,相同则说明下载是成功,否则会重新下载

生成一份lock文件,锁定包的版本,完成整个下载安装流程

在 jsconf.eu 大会上,Ryan Dahl 的演讲 Node.js 十大设计缺陷 - Ryan Dahl - JSConf EU 中提到了 node_modules 在设计上的问题,我们在使用 npm install 后可能要等很久,然后会发现程序下载了很大体积的 node_modules 文件,这主要是因为 Node 的 vendored-by-default 机制,会令 node_module 主动地安装所有 module 的 dependencies
早期的 npm v2 管理模块依赖的方式比较简单,它读取每个模块的依赖列表即 package.json 文件,并下载依赖到该模块目录内的 node_modules 下;如果该依赖又依赖了其他的模块,会继续下载依赖到该模块目录的 node_modules 文件夹下,如此递归执行下去,最终形成一颗庞大的依赖树,大致的结构如下:

这样安装也带来一些问题:
node_modules 文件夹内对于不同的依赖中又共同依赖某一包的不同版本,包管理工具会采取什么样的策略?
我在一个项目中用了yarn安装后,再使用npm安装后是否ok ?同理其他几种组合
假如 A 和 B 两个包,两个包都依赖了 C 这个包,在使用安装命令后npm会如何安装到 node_modules 中。这里以 antd-img-crop 和 antd-img-cropper 两个包为例子,后者fork了前者,两边的依赖基本一致,并且二者都依赖了 react-easy-crop ,会分别在 npm v7 及 npm 2 版本查看安装情况:

例如项目依赖了 A 模板,A 模块依赖了 B 模块,同时 C 模块也依赖 B ,但是是 B 的高版本,对于这种多版本情况,npm 的处理是先检查版本是否兼容,如果兼容就不会再重复安装,如果不兼容则会把依赖包安装在当前引用包的 node_modules下,以上面的例子为例:npm 会将A模板的 B包安装在根目录下, 再将 C 模块的 B 包安装在 C 自己的 node_modules下。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。