杜绝使用 npm

管理器

  • npm,太慢了,总会有莫名的bug,需要删除 node_modules,在次安装以后才能解决。
  • cnpm,已经淘汰。
  • yarn / yarnPnP,比 npm 快很多,比 npm 做了很多的优化,但是还存有和 npm 一样的其他问题。
  • pnpm,更快,且解决了 npm/yarn 内部潜在的 bug,并且极大了地优化了性能,扩展了使用场景。

npm

node 自带

为什么 node 要选择 npm?
在远古时代,前端是通过网址来共享代码,比如你想使用 jQuery,那么你点击 jQuery 网站上提供的链接就可以下载 jQuery,放到自己的网站上使用。
但是当项目的依赖越来越多的时候,这是一件很麻烦的事情,去 jQuery 官网下载 jQuery,去 BootStrap 官网下载 BootStrap...等等。
程序员 Isaac Z. Schlueter 给出一个解决方案:用一个工具把这些代码集中到一起来管理吧!毕竟懒才是程序员的第一生产力。

NPM 的思路大概是这样的:
  • 买个服务器作为代码仓库(registry),在里面放所有需要被共享的代码
  • 发邮件通知 jQuery、Bootstrap、Underscore 作者使用 npm publish 把代码提交到 registry 上,分别取名 jquery、bootstrap 和 underscore(注意大小写)
  • 社区里的其他人如果想使用这些代码,就把 jquery、bootstrap 和 underscore 写到 package.json 里,然后运行 npm install ,npm 就会帮他们下载代码
  • 下载完的代码出现在 node_modules 目录里,可以随意使用了。
发展
  • Isaaz 通知 jQuery 作者 John Resig,他会答应吗?这事儿不一定啊,对不对。
  • 只有社区里的人都觉得 「npm 是个宝」的时候,John Resig 才会考虑使用 npm。
  • 那么 npm 是怎么火的呢?
  • npm 的发展是跟 Node.js 的发展相辅相成的。
  • Node.js 是由一个在德国工作的美国程序员 Ryan Dahl 写的。他写了 Node.js,但是 Node.js 缺少一个包管理器,于是他和 npm 的作者一拍即合、抱团取暖,最终 Node.js 内置了 npm(现在来看为什么 npm 这么烂,node 还要选择它,当时的 node 觉得自己 i/o 很快,且当时的程序还没有这么复杂)。后来的事情大家都知道,Node.js 火了。
  • 所以说一门技术想要流行就得攀附、组合,NPM 全称 node package manager。(类似 LAMP,之前在编程历史中讲过)

yarn

安装 yarn

首先不推荐使用 npm 安装
为什么?

  • Yarn 团队认为 npm 不安全且不可靠,根据Yarn项目维护者的说法,通过npm安装Yarn违反了项目目标,可能会引起问题,并且通常比特定于平台的安装方法更糟糕。
  • 一般不推荐通过 npm 安装 Yarn。使用 npm 安装 Yarn 是不确定的,包没有签名,唯一执行的完整性检查是基本的 SHA1 哈希,这在安装系统范围的应用程序时是一个安全风险。
  • 通过 npm 运行 Yarn,它是一个单独的包管理器实用程序,可能会导致边缘问题(请参阅issue 2072
  • 通过系统包管理器安装将 Yarn 与 npm 分离,允许您在没有 npm 的情况下运行 Yarn
  • 系统包管理器通常会定期运行,保持 Yarn 更新
  • 通过 npm 安装 Yarn 很

但是从 Yarn 2.x 开始,Yarn 团队改变了他们的建议,现在建议通过 npm 安装该工具。此建议围绕锁定每个项目使用的 Yarn 版本的优势。这使项目能够适应不同版本的 Yarn 的变化。

pnpm(performant npm)

安装 pnpm

npm i -g pnpm

详解

安装时

执行命令后,首先会构建依赖树,然后针对每个节点下的包,会经历下面四个步骤:

  1. 将依赖包的版本区间解析为某个具体的版本号
  2. 下载对应版本依赖的 tar 包到本地离线镜像(能够在无网环境下安装,npm 5+ 才抄袭过来)
  3. 将依赖从离线镜像解压到本地缓存
  4. 将依赖从缓存拷贝到当前目录的 node_modules 目录

然后,对应的包就会到达项目的node_modules当中。

速度

很明显 npm 是最慢的。
为什么慢,因为 node_modules,文件小而多,磁盘 I/O 的特别慢,而且重复下载的文件也会在有多。

包管理方式

依赖版本

  • npm包管理工具都是通过 package.json 中对各个依赖包的描述去下载对应的依赖包的。
  • 但 package.json 只能规定大版本号。这样就会导致每个时期下载的依赖包都是不一样的,很容易出现兼容性等各种问题。例如:
"dependencies": {
  "vue": "^3.2.6"
}
  • 字符 ^ 告诉 NPM 检查在 3.X.X 范围内是否有较新版本,如果有,则进行安装。类似地,~ 字符只会出现在热修复程序或 3.2.X 上。
  • 这样导致项目每次安装的时候版本不一致,可能引起一些相关错误。
  • 而 yarn 率先发明了 lockfiles(已被 npm 5+ 抄袭)。
  • 规定了具体每个依赖包的版本号和对应的下载路径,保证我们下次在重新安装依赖时,能跟上次一模一样。
// package-lock.json,npm 是 json 文件,方便看得懂
"vue": {
  "version": "3.2.6",   
  "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.6.tgz",   //下载路径
  "integrity": "sha512-Zlb3LMemQS3Xxa6xPsecu45bNjr1hxO8Bh5FUmE0Dr6Ot0znZBKiM47rK6O7FTcakxOnvVN+NTXWJF6u8ajpCQ==",   
  "requires": {
    "@vue/compiler-dom": "3.2.6",
    "@vue/runtime-dom": "3.2.6",
    "@vue/shared": "3.2.6"
  }
}

目录结构

  • 如果一个项目有 100 个依赖,并且这些依赖的依赖都有 lodash。
  • 在 npm 中 lodash 很可能就被安装了 100 次,磁盘中就有 100 个地方写入了这部分代码。所以这能不能优化呢?
  • 而在 yarn 中会实行的是扁平结构(已被 npm 3+ 抄袭)。
# ① 假设项目依赖a,b,c三个模块,依赖树为:
#  +- a
#    +- react@15
#  +- b
#    +- react@16
#  +- c
#    +- react@16
# yarn安装时会按照项目被依赖的次数作为权重,将依赖提升(hoisting),
# 安装后的node_modules结构为:
  .
  └── node_modules
      ├── a
      │   ├── index.js
      │   ├── node_modules
      │   │   └── react  # @15
      │   └── package.json
      ├── b
      │   ├── index.js
      │   └── package.json
      ├── c
      │   ├── index.js
      │   └── package.json
      └── react  # @16 被依赖了两次,所以进行提升
  • 这样一来,重复的包将会大量减少,但是由于“提升”,当你只安装一个依赖的时候,会发现 node_modules 下多了很多的你没有安装的目录(因为重复的被提升了),node_modules 目录将会变得很丑。
  • Q:为什么不一起把 react15 也一起提升了?
  • A:因为 node_modules 不能有效地处理重复的包. 两个名称相同但是不同版本的包是不能在一个目录下共存的。
    -而且这里还有一个潜在的问题,即:如果 A 依赖 B, B 依赖 C,由于提升了那么 A 当中是可以直接使用 C 的,但问题是 A 当中并没有声明 C 这个依赖,因此会出现这种非法访问的情况(其中也有因为Node没有包的概念的关系,我猜测可能和 js 是一门运行时的语言有关)。
  • 接下来在看两种情况:
# ② 现在假设在①的基础上,根项目依赖了react@15,对于项目自己的依赖肯定是要放在node_modules根目录的,
# 由于一个目录下不能存在同名目录,所以react@16没有的提升机会. 
# 安装后node_moduels结构为
  .
  └── node_modules
      ├── a
      │   ├── index.js
      │   └── package.json # react@15 提升
      ├── b
      │   ├── index.js
      │   ├── node_modules
      │   │   └── react  # @16
      │   └── package.json
      ├── c
      │   ├── index.js
      │   ├── node_modules
      │   │   └── react  # @16
      │   └── package.json
      └── react  # @15
# 上面的结果可以看出,react@16出现了重复
  .
  └── node_modules
      ├── a
      │   ├── index.js
      │   ├── node_modules
      │   │   └── react  # @16
      │   └── package.json
      ├── b
      │   ├── index.js
      │   ├── node_modules
      │   │   └── react  # @15
      │   └── package.json
      └── react  # @15 or 16?
# 答案是: 都有可能。取决于 a 和 b 在 package.json中的位置,如果 a 声明在前面,那么就是提升的就是 react@16,否则是react@15。
  • 所以这种扁平化结构存在的缺点有:一、依赖结构的不确定性。二:扁平化算法本身的复杂性很高,耗时较长。三:项目中仍然可以非法访问没有声明过依赖的包。四:在某些情况下没有很好的解决重复问题
  • 因此 yarn 又做出了改进,加入的 PnP(Plug'n'Play) 功能,1.12 版本开始默认包含,2.0 版本开始默认开启。
  • 基本原理:Yarn 作为一个包管理器,它知道你的项目的依赖树. 那能不能让 Yarn 告诉 Node? 让它直接到某个目录去加载模块。这样即可以提高 Node 模块的查找效率,也可以减少 node_modules 文件的拷贝。
  • 在 pnp 模式下,Yarn 不会创建 node_modules 目录,取而代之的是 .yarn 目录.pnp.js文件。
  • .pnp.js 文件,这个文件包含了项目的依赖树信息,模块查找算法,也包含了模块查找器的 patch 代码(在 Node 环境,覆盖 Module._load 方法),简单来说就是项目的npm模块解析规则。
  • .yarn 目录存放了项目中下载的所有依赖的zip包。
  • 使用 pnp 机制的以下优点:
    1. 摆脱了 node_modules:
      • 时间上: 相比较在热缓存(hot cache)环境下运行yarn install节省 70%的时间。
      • 空间上: pnp 模式下,所有 npm 模块都会存放在全局的缓存目录下,依赖树扁平化,避免拷贝和重复。
    2. 提高模块加载效率,Node 为了查找模块,需要调用大量的 stat 和 readdir 系统调用。. pnp 通过 Yarn 获取或者模块信息,直接定位模块。
    3. 不再受限于 node_modules 同名模块不同版本不能在同一目录。
  • 使用 pnp 机制的以下缺点
    1. 因为 node 依赖解析的目录 node_modules 没了,不能直接使用 node xxx.js。
    2. 由于还是不够成熟(2018.9面世),前端社区其他工具链支持度还不够,从官方看已有下列的工具有条件支持(某版本起或插件支持)
  • 但 pnpm 内部使用基于内容寻址的文件系统来存储磁盘上所有的文件,他只会安装一次,磁盘中只有一个地方写入(rails 也类似这样),后面再次使用都会直接使用 hardlink(硬链接),这种方法几乎就解决了上面的所有问题。
  • 例如我们安装一个依赖:pnpm init -y && pnmp i react
  .
  └── node_modules
  │    ├── .pnpm
  │    ├── react
  │    ├── .modules.yaml
  └── package.json
  └── pnpm-lock.yaml # lockfiles
  • 如此整洁、干净,我们直接就看到了 react,但值得注意的是,这里仅仅只是一个软链接,那么它真正的位置在哪呢?
  • .pnpm 当中寻找:
  .
  └── node_modules
        ├── .pnpm
              ├ node_modules
              ├ registry.npmjs.org+@js+tokens@4.0.0
              ├ .......
              ├ registry.npmjs.org+react@17.0.2
                  ├ node_modules
                      ├ loose-envify (软链接)
                      ├ ... (软链接)
                      ├ react
                        ├ cjs
                        ├ umd
                        ├ ......
  • 好家伙!竟然在 .pnpm/egistry.npmjs.org+react@17.0.2/node_modules/react 下面找到了!同级其他的依赖也都是软链接。
  • 再看看.pnpm,.pnpm目录下虽然呈现的是扁平的目录结构,但仔细想想,顺着软链接慢慢展开,其实就是嵌套的结构!
  • 将包本身和依赖放在同一个node_module下面,与原生 Node 完全兼容,又能将 package 与相关的依赖很好地组织到一起,设计十分精妙,也解决了 yarn PnP 没有 node_modules 的问题。
  • 这么好的东西为什么没有人用呢?
    • 兼容问题,像 hard link 和 symlink 这种方式在所有的系统上都是兼容的吗?实际上 hard link 在主流系统上(Unix/Win)使用都是没有问题的,但是 symlink 即软连接的方式可能会在 windows 存在一些兼容的问题,但是针对这个问题,pnpm 也提供了对应的解决方案:在 win 系统上使用一个叫做 junctions 的特性来替代软连接,这个方案在 win 上的兼容性要好于 symlink。
    • 或许你也会好奇为啥 pnpm 要使用 hard links 而不是全都用 symlink 来去实现。
    • 实际上存在 store 目录里面的依赖也是可以通过软连接去找到的,nodejs 本身有提供一个叫做 --preserve-symlinks 的参数来支持 symlink,但实际上这个参数实际上对于 symlink 的支持并不好导致作者放弃了该方案从而采用 hard links 的方式。具体可以参考该 issue

monorepo

  • 只有 pnpm 支持。
  • npm(npm 7+ 抄袭)/yarn workspace
  • 随着前端工程的日益复杂,越来越多的项目开始使用 monorepo。之前对于多个项目的管理,我们一般都是使用多个 git 仓库,但 monorepo 的宗旨就是用一个 git 仓库来管理多个子项目,所有的子项目都存放在根目录的packages目录下,那么一个子项目就代表一个package。如果你之前没接触过 monorepo 的概念,建议仔细看看这篇文章(https://www.perforce.com/blog/vcs/what-monorepo)以及开源的 monorepo 管理工具lerna(https://github.com/lerna/lerna#readme),项目目录结构可以参考一下 babel 仓库(https://github.com/babel/babel)。
  • pnpm 体现在各个子命令的功能上,比如在根目录下 pnpm add A -r,那么所有的 package 中都会被添加 A 这个依赖,当然也支持 --filter 字段来对 package 进行过滤。

目前我们为什么使用 yarn

  • 因为 npm 太慢了。
  • 从目前主流的开源项目来看,几乎没有使用 npm 的仓库,一般都是 yarn。
  • 团队内需要统一,如果不一致可能会出现依赖问题(npm 和 yarn 的 lock 文件不同)。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容