NPM
Node Package Manager
npm 的核心目标:
Bring the best of open source to you, your team and your company.
给你和你的团队、你的公司带来最好的开源库和依赖
[[常见 npm 工具包]]
npm install 的安装机制
npm install 的安装机制
- 首先会检查和获取 npm 的配置文件,
.npmrc- 这里的优先级为
项目级的.npmrc 文件 > 用户级的 .npmrc 文件 > 全局级的 .npmrc > npm 内置的 .npmrc 文件
- 这里的优先级为
- 然后检查项目中是否存在
package-lock.json文件- 如果有,检查锁文件和 package.json 文件声明的依赖是否一致
- 一致,直接使用 package.json 中的信息,从网络或者缓存中加载依赖
- 不一致,根据上述流程的不同版本进行处理 如何进行版本比较
- 如果没有,则根据 package.json 获取包信息,递归构建依赖树,根据构建好的依赖去下载完整的依赖资源,在下载之前,会检查有没有相关的资源缓存
- 存在缓存,直接从缓存中取文件并解压到
node.modules文件夹中 - 不存在,从 npm 远端仓库下载包,校验包的完整性,同事添加到缓存中,解压到
node_modules中
- 存在缓存,直接从缓存中取文件并解压到
- 如果有,检查锁文件和 package.json 文件声明的依赖是否一致
- 最后,生成
package.json文件
yarn
yarn 是一个由 Facebook、Google、Exponent 和 Tilde 构建的新的 JavaScript 包管理器
它的出现是为了解决 历史 上 npm 的某些不足 (比如 npm 对于依赖的完整性和一致性的保证,以及 npm 安装过程中速度很慢的问题)
对比 npm
确定性: 通过 yarn.lock 等机制,即使是不同的安装顺序,相同的依赖关系在任何的环境和容器中,都可以以相同的方式安装。(那么,此时的 npm v5 之前,并没有 package-lock.json 机制,只有默认并不会使用 npm-shrinkwrap.json)
采用模块扁平化的安装模式: 将不同版本的依赖包,按照一定的策略,归结为单个版本; 以避免创建多个版本造成工程的冗余 (目前版本的 npm 也有相同的优化)
网络性能更好: yarn 采用了请求排队的理念,类似于并发池连接,能够更好的利用网络资源; 同时也引入了一种安装失败的重试机制
采用缓存机制,实现了离线模式 (目前的 npm 也有类似的实现)
yarn 的安装机制
检测包
检查是否存在 npm 相关的文件,如 package-lock.json
如果存在,则会提醒用户这些文件可能会有冲突
解析包
解析依赖中每一个包的信息 -> 包的具体版本信息和包的下载地址
- 首先拿到首层依赖,也就是 dependencies、devDependencis 和 optionalDependencies 的内容
- 采用遍历首层依赖的方式获取包的依赖信息,递归每个依赖下嵌套的版本信息,并将解析过的包和正在解析的包做标记 (使用 Set 数据结构进行存储,保证同一版本范围内的包不会进行重复解析的操作)
获取包
会检查缓存中是否有当前依赖的包,同时将缓存中不存在的包下载到缓存的目录中
Fetch From External —— 从网络下载
Fetch From Local —— 从离线缓存
链接包
将缓存中的依赖复制到 node_modules 目录下
复制依赖前,先判断 peerDependecies 的存在
构建包
如果依赖包中存在二进制需要进行编译
依赖声明的几种类型
npm 设计了以下的几种依赖类型声明:
- dependencies 项目依赖
- devDependencies 开发依赖
- peerDependencies 同版本的依赖
- bundledDependencies 捆绑依赖
- optionalDependencies 可选依赖
dependencies
dependencies 表示项目依赖,这些依赖都会成为你的线上生产环境中的代码组成的部分。当 它关联到 npm 包被下载的时候, dependencies 下的模块也会作为依赖, 一起被下载。
devDependencies
devDependencies 表示开发依赖, 不会被自动下载的。因为 devDependencies 一般是用于开发阶段起作用或是只能用于开发环境中被用到的。 比如说我们用到的 Webpack,预处理器 babel-loader、scss-loader,测试工具 E2E 等, 这些都相当于是辅助的工具包, 无需在生产环境被使用到的。
并不是只有在 dependencies 中的模块才会被一起打包, 而是在 devDependencies 中的依赖一定不会被打包的。 实际上, 依赖是否是被打包,完全是取决你的项目里的是否是被引入了该模块。
peerDependencies
peerDependencies 表示同版本的依赖, 简单一点说就是: 如果你已经安装我了, 那么你最好也安装我对应的依赖。 这里举个小例子: 加入我们需要开发一个 react-ui 就是一个基于 react 开发的 UI 组件库, 它本身是会需要一个宿主环境去运行的, 这个宿主环境还需要指定的 react 版本来搭配使用的, 所以需要我们去 package.json 中去配置
bundledDependencies
bundledDependencies 和 npm pack 打包命令有关。假设我们在 package.json 中有如下的配置:
{
"name": "test",
"version": "1.0.0",
"dependencies": {
"dep": "^0.0.2",
...
},
"devDependencies": {
...
"devD1": "^1.0.0"
},
"bundledDependencies": [
"bundleD1",
"bundleD2"
]
}那我们此时执行 npm pack 的时候, 就会生成一个 test-1.0.0.tgz 的压缩包, 在该压缩包中还包含了 bundleD1 和 bundleD2 两个安装包。 实际使用到 这个压缩包的时候
npm install test-1.0.0.tgz 的命令时, bundleD1 和 bundleD2 也会被安装的。
这里其实也有需要注意的是: 在 bundledDependencies 中指定的依赖包, 必须先在 dependencies 和 devDependencies 声明过, 否则 npm pack 阶段是会报错的。
optionalDependencies
optionalDependencies 表示可选依赖,就是说当你安装对应的依赖项安装失败了, 也不会对整个安装过程有影响的。一般我们很少会用到它
开发中困惑已久的问题
你在实际的开发会不会出现这样的一些情况:
- 当你项目依赖出现问题的时候, 我们会不会是直接删除 node_modules 和 lockfiles 依赖, 再重新 npm install,删除大法是否真的好用?这样的使用方案会不会带来什么问题?
- 把所有的依赖包都安装到 dependencies 中,对 devDependencies 不区分会不会有问题?
- 一个项目中, 你使用 yarn, 我使用 npm,会不会有问题呢
- 还有一个问题, lockfiles 文件 我们提交代码的时候需不需要提交到仓库中呢?
解答
- 不要轻易删除锁文件 lockfiles,这会导致原本的依赖出现版本更新,可能会导致项目崩了。直接 npm install 即可,不好使可以手动重新安装或更新报错的具体依赖,当然有些包需要特定的 node 版本,也需要对应改 node 版本。
- 所有依赖装 dependencies 和 devDependencies 得看项目,如果是前端 spa 应用 或者一次性的 ssg 项目可以这样做,但是如果是开后端或者 ssr 以及开库给别人用就需要特别注意到底是通用环境下的依赖 dependencies 还是仅生产环境下的依赖 devDependencies。
- yarn 和 npm 混用在部分特别依赖包管理器的项目中是有问题的,例如 antfu 的 vitesse 需要通过包的锁文件去判断具体用到那个包管理器然后用这个包管理器去自动安装具体的图标集依赖。当然除此之外还有两个包管理器的网络机制以及缓存机制和下载后的依赖分布也不同,如果特别依赖这些的项目也需要注意一下。推荐统一一个包管理器。
- 需要上传 lockfiles 文件到仓库中,因为这个文件主要用来锁默认的依赖版本,最好让别人的依赖和自己的依赖保持统一,这样的错误率最小。