简述前端包管理工具机制和相关实践

更新日期: 2022-06-02阅读: 1.6k标签: 机制

npm 依赖管理机制

区别于 Python 的包管理工具 pip 的全局安装,npm 会安装依赖包到当前项目目录,使不同项目的依赖更成体系,这样做的好处是减轻了包作者的 api 兼容性压力;但是缺陷是如果两个项目依赖了一个相同的库,一般这个库会在这两个项目中各安装一次,即相同的依赖包会被多次安装。

我们先通过一张流程图(源自掘金)来了解下 npm install 的整体流程


可以看到执行 npm install 后依次会进行以下流程

  • 检查 package-lock.json

  • 通过和 package.json 对比确定是否远程获取包信息

  • 扁平化构建依赖树

  • 添加缓存

  • 下载包并解压到 node_modules

  • 生成新的 lock 文件 值得注意的是,早期 npm 版本(v5.0 - v5.4)发现 package.json 和 package-lock.json 不一致时,对依赖的安装方式是不一样的。 所以对于团队而言,最佳实践应该是保持 npm 版本的一致性!

缓存机制

我们可以从流程图中看到,npm install 的流程中会查找和使用缓存,以及下载包后会添加缓存的环节。由于依赖嵌套机制,项目中 node_moudles 占用的磁盘空间无疑是最大的,如果安装时每次都通过网络下载获取,那么时间成本是巨大的。常见的优化方式是“空间换时间”,npm 也通过缓存机制来解决这个问题。

简单了解下缓存的目录的和清除机制。

通过 npm config get cache 命令可以查询到缓存目录:默认是用户主目录下的 .npm/_cacache 目录。

npm cache clean --force 即可强制清除缓存。

yarn 带来了什么?

yarn 是于 2016 年诞生的,它的出现解决了历史上 npm 的很多问题,比如缺乏对于依赖的完整性和一致性保障(npm v3 版本还没有 package-lock.json),以及 npm 安装速度过慢的问题等。npm 目前已经迭代到 v8 版本,在很多方面已经借鉴了 yarn 的优点,但是我们不妨了解下 yarn 诞生时带来的理念。

  1. 确定性。通过 yarn.lock 等机制,保证了确定性,这里的确定性包括但不限于明确的依赖版本、明确的依赖安装结构等。即在任何机器和环境下,都可以以相同的方式被安装。

  2. 模块扁平化安装。将依赖包的不同版本,按照一定策略,归结为单个版本,以避免创建多个副本造成冗余。

  3. 更快的速度。yarn 采取并行安装的机制进行包的安装任务,提高了性能;yarn 引入的缓存机制使二次安装的速度更快。

  4. 更好的语义化。yarn 的命令更加简洁。 解决早期 npm 的依赖管理问题

文章的开始提到 npm 是将依赖放到项目的 node_modules 中,同时如果 node_modules 中的依赖 A 还依赖了其他依赖 B,那么 B 也会被安装到 A 的 node_modules 文件夹,依次递归最终形成非常复杂和庞大的依赖树。

这种依赖管理方式会随着项目的迭代,node_moudles 会变得越来越复杂,从而造成:

  • 非常深的项目依赖层级,难以排查问题

  • 依赖被重复安装,浪费磁盘,网络等资源,安装速度慢 那么 yarn 是如何解决这个问题的呢?那就是模块扁平化安装机制。假如我们有这样一个文件依赖结构。

App
-a@2.0
-b@2.0
-b@2.0
-c@1.0
-b@2.0

yarn 在安装依赖时会打平依赖,并对重复依赖进行提升,最终形成的依赖结构如下:

App
-a@2.0
-b@2.0
-c@1.0

但是需要注意的是: 模块的安装顺序可能影响 node_modules 内的文件结构。 在 npm v3 版本中,假如 项目一开始依赖了 a@1.0,此时 a@1.0 会被安装在顶层目录;随着迭代,又引入了模块 b@1.0,而 b@1.0 又依赖了 a@2.0,此时 a@2.0 会被安装在 b@1.0 下,因为顶层已经有一个 a@1.0 了。

pnpm: 最先进的包管理工具?


在各个场景下,pnpm 相比较于 npm(v8)和 yarn(v3)在性能上都有不错的提升。

pnpm 之所以有如此大的性能提升,简单来说 pnpm 是通过全局 store(目录 ${os.homedir}/.pnpm-store)来存储 node_modules 依赖的 hard-links,当在项目文件中引用依赖的时候则是通过 symlink 去找到对应虚拟磁盘目录下(.pnpm 目录)的依赖地址。相比于 npm 和 yarn 会在每个项目中都安装一份 node_moudles, pnpm 的全局 store 则实现了“安装一次,所有项目复用”,这样避免了二次安装带来的时间消耗。

除此之外,pnpm 本身的设计机制解决了 monorepo 的很多痛点,比如 ”幽灵依赖“ 和 ”依赖重复安装“ 的问题。如图:

下面两小节内容源自: pnpm: 最先进的包管理工具

幽灵依赖

Phantom dependencies 被称之为幽灵依赖,解释起来很简单,即某个包没有被安装(package.json 中并没有,但是用户却能够引用到这个包)。

引发这个现象的原因一般是因为 node_modules 结构所导致的,例如使用 yarn 对项目安装依赖,依赖里面有个依赖叫做 foo,foo 这个依赖同时依赖了 bar,yarn 会对安装的 node_modules 做一个扁平化结构的处理(npm v3 之后也是这么做的),会把依赖在 node_modules 下打平,这样相当于 foo 和 bar 出现在同一层级下面。那么根据 nodejs 的寻径原理,用户能 require 到 foo,同样也能 require 到 bar。

package.json -> foo(bar 为 foo 依赖)
node_modules
/foo
/bar -> :ghost:依赖

那么这里这个 bar 就成了一个幽灵依赖,如果某天某个版本的 foo 依赖不再依赖 bar 或者 foo 的版本发生了变化,那么 require bar 的模块部分就会抛错。

依赖重复安装

这个问题其实也可以说是 hoist 导致的,这个问题可能会导致有大量的依赖的被重复安装,举个例子:

例如有个 package,下面依赖有 lib_a、lib_b、lib_c、lib_d,其中 a 和 b 依赖 util_e@1.0.0,而 c 和 d 依赖 util_e@2.0.0。

那么早期 npm 的依赖结构应该是这样的:

- package
- package.json
- node_modules
- lib_a
- node_modules <- util_e@1.0.0
- lib_b
- node_modules <- util_e@1.0.0
_ lib_c
- node_modules <- util_e@2.0.0
- lib_d
- node_modules <- util_e@2.0.0

这样必然会导致很多依赖被重复安装,于是就有了 hoist 和打平依赖的操作:

- package
- package.json
- node_modules
- util_e@1.0.0
- lib_a
- lib_b
_ lib_c
- node_modules <- util_e@2.0.0
- lib_d
- node_modules <- util_e@2.0.0

但是这样也只能提升一个依赖,如果两个依赖都提升了会导致冲突,这样同样会导致一些不同版本的依赖被重复安装多次,这里就会导致使用 npm 和 yarn 的性能损失。

如果是 pnpm 的话,这里因为依赖始终都是存在 store 目录下的 hard links ,一份不同的依赖始终都只会被安装一次,因此这个是能够被彻彻底底的消除的。

项目中的相关场景实践和常见问题

npm link

适用场景:本地调试 npm 模块,将模块链接到对应的业务项目中运行 使用方法:假如我们需要把模块 pkg-a 链接到主项目 App 中,首先在 pkg-a 根目录中执行 npm link,然后在 App 根目录中执行 npm link pkg-a 即可。调试完可以使用 npm unlink 取消关联。原理:npm link 通过软连接将 pkg-a 链接到 node 模块的全局目录和可执行文件中,实现 npm 包命令的全局可执行。

npx

适用场景:在 npm 5.2.0 版本之后,npm 内置了 npx 的包。npx 是一个简单的 cli 工具,可以帮助我们快速的调试,还可以让我们在不通过 npm 安装包的前提下执行一些 npm 包。

使用方法:

Before:一般情况下,如果我们想使用 es-lint, 会先通过 npm install es-lint, 然后在项目根目录执行 ./node_modules/.bin/es-lint your_file.js 或者 通过 package.json 的 npm scripts 调用 eslint。

After:npx es-lint your_file.js

原理:npx 在运行时会自动去 ./node_moudles/.bin 和 环境变量 寻找命令

是否提交 lock.json 到代码仓库

前面我们提到 yarn 带来了 .lock 文件的机制,使得在任何环境下执行 install,都能得到一致的 node_modules 安装结果。但是是否需要提交 lockfiles(package-lock.json/yarn.lock) 到代码仓库呢?

npm 官方文档是建议把 package-lock.json 文件提交到代码仓库的。在多人协作的项目中,这样做确实没有问题。但是如果开发的是库,在 npm publish 的时候最好忽略 lockfiles。因为库一般是被其他项目依赖的,在不使用 lockfiles 的情况下,由于新版 npm 和 yarn 的 hoist 机制,可以复用住项目已经加载过的包,减少依赖重复和体积。

但是存在这样一种现象:即使在一些发布时忽略 lockfiles 的库中,在主项目顶层存在相关依赖包的前提下,最终生成的 lockfile 仍然没复用主项目的包。这是为什么呢?原因是库的依赖包版本和主项目存在的依赖包版本不一致。具体看下图:主项目的 yarn.lock 中显示 browser 这个包依赖了 @babel/runtime@7.0.0


主项目 node_modules 顶层的 @babel/runtime 版本为 7.10.1


知道了原因,那么如何减少库项目的依赖项呢。到这里,解决方案也就呼之欲出了:

  1. 库项目尽量使用和主项目版本一致的依赖包

  2. 在库项目 package.json 的 “peerDevpendencies” 字段中声明主项目已有的依赖包

合入其他分支代码后编译报错

相信很多同学都遇到过和我一样的问题:当自己的 feat 分支代码合入 master 或者业务班车分支的代码时,重新 yarn 时,有时候会编译失败,报大量 "can't resolve module xxx"的错误。这种错误有很多情况是依赖版本不一致的问题,但是又极其难以定位,令人头痛。那么此时有另外一个思路,那就是从 master 拉一个最新的分支再进行合入。

但更好的解决方式是:建议在日常开发过程中,定时合入 master 代码,一方面可以合入最新的 feat,另一方面可以避免长时间不合入,最后在上线阶段合入代码,可能出现大量冲突,解决不当或遗漏而造成的编译问题。同时也可以考虑将工具升级为 pnpm,以解决潜在的“幽灵依赖”和“依赖嵌套”问题,同时带来性能上的提升。

原文 https://mp.weixin.qq.com/s/BK3rTPCGCkY-xII13Y8Aqg

链接: https://fly63.com/article/detial/11638

浅析前端页面渲染机制

作为一个前端开发,最常见的运行环境应该是浏览器吧,为了更好的通过浏览器把优秀的产品带给用户,也为了更好的发展自己的前端职业之路,有必要了解从我们在浏览器地址栏输入网址到看到页面这期间浏览器是如何进行工作的

这一次,彻底弄懂 JavaScript 执行机制

javascript是一门单线程语言,Event Loop是javascript的执行机制.牢牢把握两个基本点,以认真学习javascript为中心,早日实现成为前端高手的伟大梦想!

创建js hook钩子_js中的钩子机制与实现

钩子机制也叫hook机制,或者你可以把它理解成一种匹配机制,就是我们在代码中设置一些钩子,然后程序执行时自动去匹配这些钩子;这样做的好处就是提高了程序的执行效率,减少了if else 的使用同事优化代码结构

小程序的更新机制_如何实现强制更新?

在讲小程序的更新机制之前,我们需要先了解小程序的2种启动模式,分别为:冷启动和热启动。小程序不同的启动方式,对应的更新情况不不一样的。无论冷启动,还是热启动。小程序都不会马上更新的,如果我们需要强制更新,需要如何实现呢?

基于JWT的Token认证机制实现及安全问题

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。其JWT的组成:一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

web前端-JavaScript的运行机制

本文介绍JavaScript运行机制,JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。

轮询机制解决后端任务回调问题

现在有一个需求,前端有一个按钮,点击以后会调用后端一个接口,这个接口会根据用户的筛选条件去hadoop上跑任务,将图片的base64转为img然后打包成zip,生成一个下载连接返回给前端,弹出下载框。hadoop上的这个任务耗时比较久

JavaScript预解释是一种毫无节操的机制

js代码执行之前,浏览器首先会默认的把所有带var和function的进行提前的声明或者定义:1.理解声明和定义、2.对于带var和function关键字的在预解释的时候操作不一样的、3.预解释只发生在当前的作用域下

js对代码解析机制

脚本执行js引擎都做了什么呢?1.语法分析 2.预编译 3.解释执行。在执行代码前,还有两个步骤;语法分析很简单,就是引擎检查你的代码有没有什么低级的语法错误 ,查找全局变量声明(包括隐式全局变量声明,省略var声明),变量名作全局对象的属性,值为undefined

web认证机制

以前对认证这方面的认识一直不太深刻,不清楚为什么需要token这种认证,为什么不简单使用session存储用户登录信息等。最近读了几篇大牛的博客才对认证机制方面有了进一步了解。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!