前端基础建设与架构-工程化管理工具篇

  • 项目依赖出现问题时,删除大法好,即删除 node_modules 和 lockfile,再重新 install,这样操作是否存在风险?
  • 把所有依赖都安装到 dependencies 中,不区分 devDependencies 会有问题吗?
  • 我们的应用依赖了公共库 A 和公共库 B,同时公共库 A 也依赖了公共库 B,那么公共库 B 会被多次安装或重复打包吗?
  • 一个项目中,既有人用 npm,也有人用 Yarn,这会引发什么问题?
  • 我们是否应该提交 lockfile 文件到项目仓库呢?

01|npm 安装机制及企业及部署私服原理

npm 内部机制和核心原理

  • npm 安装机制

npm install => 检查 config => 判断有无 lock 文件 => 如下分支

1 存在 lock 文件 => 是否和 package.json 声明版本

=> 一致 则进行 检查缓存

=> 不一致 则根据 package.json 和 package-lock.json 进行安装和更新 lockfile

2 不存在 lock 文件 => 获取包信息 => 构建依赖树 => 检查缓存

检查缓存 => 无 => 下载资源包 => 检查完整性 => 添加到缓存

检查缓存 => 有 => 解压到 node_modules => 生产 lock 文件

  • npm 缓存机制

npm 不完全指南

  • 自定义 npm init
  • npm link,调试验证包的可用性
  • npx 的作用

npm 多元镜像和企业及部署私服原理

我们可以通过 npm config set 命令来设置安装源或者某个 scope 对应的安装源,很多企业也会搭建自己的 npm 源.

部署镜像后,确保高速、稳定的 npm 服务,而且使用发布的私有模块更加安全。审核机制上也可以保证私服上的 npm 模块质量和安全。

{
  "scripts": {
    "preinstall": "node ./bin/preinstall.js"
  }
}
// preinstall.js
require(' child_process').exec('npm config get registry', function(error, stdout, stderr) {
  if (!stdout.toString().match(/registry\.x\.com/)) {
    exec('npm config set @xscope:registry https://xxx.com/npm/')
  }
})

现在社区上主要有 3 种工具来搭建 npm 私服:nexusverdaccio 以及 cnpm

  • Nexus 工作原理

nexus 工作在 client 和外部 npm 之间,并通过 group repository 合并 npm 仓库以及私有仓库,起到代理转发的作用。

  • npm 配置作用优先级

命令行配置 > env 环境变量设置 > .npmrc [项目级 > 用户级 > 全局级 > 内置]

  • npm 镜像和安装问题

网络层面解决问题,正如大部分公司目前使用的科学上网环境

01 | 总结

  • npm 内部机制和核心原理
    • npm 的安装机制和背后思想
    • npm 缓存机制
  • npm 不完全指南
    • 自定义 npm init
    • npm link,调试验证包的可用性
    • npx 的作用
  • npm 多元镜像和企业级部署私服原理

02 | Yarn 的安装理念及如何破解依赖管理困境

新的 JavaScript 包管理器。为了解决历史上 npm 的某些不足。

npm 不足:对依赖的完整性和一执行保障;安装速度过慢;

不过,npm 后续版本吸收了 yarn 的优势特点(比如 一致性安装校验算法)

yarn 理念

  • 确定性:yarn.lock 机制,相同的依赖关系在任何机器和环境下,都可以以相同的方式被安装;(在 npm v5 之前,没有 package-lock.json 机制)
  • 模块扁平化安装模式:将依赖包的不同版本,按照一定策略,归结于单个版本,避免创建多个副本造成冗余
  • 网络性能更好:Yarn 采用了请求排队的理念,类似并发连接池,能够更好地利用网络资源;同时引入了更好的安装失败时的重试机制
  • 采用缓存机制,实现了离线模式

Yarn 安装机制和背后思想

Yarn 的安装过程有 5 步: 检测 checking --> 解析包 resolving packages --> 获取包 fetching packages --> 链接包 linking packages --> 构建包 building packages

检测包 Checking

检测项目中是否存在一些 npm 相关的文件;检查系统 OS、CPU 等信息;

解析包 Resolving Packages

解析依赖树中的每一个包的版本信息

  • 获取当前项目中定义的 dependencies、dveDependencies、optionalDependencies
  • 采用遍历首层依赖的方式获取依赖包的版本信息,递归查找每个依赖下的嵌套依赖的版本信息,并将解析过和正在解析的包的引用用一个 Set 数据结构来存储,保证同一版本范围内的包不会被重复解析
    • 未解析过的包 A,首次尝试从 yarn.lock 中获取到版本信息,并标记为已解析
    • 如果在 yarn.lock 中没有找到包 A,则向 Registry 发起请求获取满足版本范围的已知最高版本的包信息,获取后将当前包解析为已解析
  • 确定所有依赖的具体版本信息以及下载地址

获取包 Fetching Packages

Q: 如何判断缓存中是否存在当前的依赖包?

A: Yarn 会根据 cacheFolder + slug + node_modules + pkg.name 生成一个 path,判断系统中是否存在该 path, 如果存在则证明已经有缓存,不用重新下载,这个 path 也就是依赖包缓存的具体路径。

  • 检查缓存中是否存在当前依赖包,同时将缓存中不存在的依赖包下载到缓存目录
    • 没有命中缓存的包,Yarn 会维护一个 fetch 队列,按照规则进行网络请求。
    • 如果下载包地址是一个 file 协议,亦或是相对路径,就说明其指向一个本地目录,此时调用 Fetch From Local 从离线缓存中获取包
    • 否则调用 Fetch From External 获取包。最终获取结果使用 fs.createWriteStream 写入到缓存目录。

链接包 Linking Packages

将项目中的依赖复制到项目 node_modules 下,同时遵守扁平化规则。 在复制依赖前,Yarn 会先解析 peerDependencies,如果找不到符合 peerDependencies 的包,则进行 warning 提示,并最终拷贝依赖到项目中

扁平化原则

使用 Set 数据结构,存储同一版本的包信息。npm dedupe

构建包 Building Packages

如果依赖包中存在二进制包需要进行编译,会在这一步进行。

破解依赖管理困境

什么是嵌套地狱

项目依赖树的层级非常深,不利于调试和排查问题; 依赖树的不同分支里,可能存在同样版本的相同依赖。

02 | 总结

  • Yarn 安装机制和背后思想
    • Yarn 解决的问题
    • Yarn 安装过程
  • 破解依赖管理困境
    • 删除 node_modules 重新安装,利用 npm 的依赖分析能力,得到更清爽的结构
    • 使用 npm dedupe 命令
    • Yarn 自动执行 npm dedupe 命令

03 | CI 环境上的 npm 优化及更多工程化问题解析