前端基础建设与架构-工程化管理工具篇
- 项目依赖出现问题时,删除大法好,即删除 node_modules 和 lockfile,再重新 install,这样操作是否存在风险?
- 把所有依赖都安装到 dependencies 中,不区分 devDependencies 会有问题吗?
- 我们的应用依赖了公共库 A 和公共库 B,同时公共库 A 也依赖了公共库 B,那么公共库 B 会被多次安装或重复打包吗?
- 一个项目中,既有人用 npm,也有人用 Yarn,这会引发什么问题?
- 我们是否应该提交 lockfile 文件到项目仓库呢?
01|npm 安装机制及企业及部署私服原理
npm 内部机制和核心原理
npm install => 检查 config => 判断有无 lock 文件 => 如下分支
1 存在 lock 文件 => 是否和 package.json 声明版本
=> 一致 则进行 检查缓存
=> 不一致 则根据 package.json 和 package-lock.json 进行安装和更新 lockfile
2 不存在 lock 文件 => 获取包信息 => 构建依赖树 => 检查缓存
检查缓存 => 无 => 下载资源包 => 检查完整性 => 添加到缓存
检查缓存 => 有 => 解压到 node_modules => 生产 lock 文件
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 私服:nexus
、verdaccio
以及 cnpm
。
nexus 工作在 client 和外部 npm 之间,并通过 group repository 合并 npm 仓库以及私有仓库,起到代理转发的作用。
命令行配置 > env 环境变量设置 > .npmrc [项目级 > 用户级 > 全局级 > 内置]
网络层面解决问题,正如大部分公司目前使用的科学上网环境
01 | 总结
- 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 安装机制和背后思想
- 破解依赖管理困境
- 删除 node_modules 重新安装,利用 npm 的依赖分析能力,得到更清爽的结构
- 使用 npm dedupe 命令
- Yarn 自动执行 npm dedupe 命令
03 | CI 环境上的 npm 优化及更多工程化问题解析