为何我从 npm 到 yarn 再到 npm?

first post on http://blog.xgheaven.com/2018/05/03/npm-to-yarn-to-npm/html

从接触到 node 环境来讲,其中一个不可或缺的一部分即是 npm 包管理,可是因为官方的 npm 有各类各样的问题,因而催生了不少不一样的版本,这其中的曲折也许只有过来人才知道。node

放弃 npm?

上古时代

在上古版本(应该是 npm3 之前的版本,具体我也记不清了),npm 的安装策略并非扁平化的,也就是说好比你安装一个 express,那么你会在 node_modules 下面只找到一个 express 的文件夹。而 express 依赖的项目都放在其文件夹下。linux

- app/
  - package.json
  - node_modules/
    - express/
      - index.js
      - package.json
      - node_modules/
        - ...
复制代码

这个带来的问题或许 windows 用户深谙其痛,由于在这种安装环境下,会致使目录的层级特别高,而对于 windows 来讲,最大的路径长度限制在 248 个字符(更多请见此),再加上 node_modules 这个单词又特别长,因此你懂得,哈哈哈。解决方案啥的本身去搜索吧,反正估计如今也没人会用上古版本了。git

除了 windows 用户出现的问题之外,还有一个更严重的问题,就是模块都是独立的,好比说位于 express 下面的 path-to-regexpconnect 下面的 path-to-regexp 的模块是两个不一样的模块。 那么这个会带来什么影响呢?其实在使用上,并无什么太大的影响,可是内存占用过大。由于不少相同模块位于不一样模块下面就会致使有多个实例的出现(为何会加载多个实例,请查看 Node 模块加载)。你想一想,都是一样的功能,为何要实例这么屡次呢?不能就加载一次,复用实例么?github

上古时代的 npm 的缺点能够说仍是不少的:express

  • 目录嵌套层级过深
  • 模块实例没法共享
  • 安装速度很慢,这其中有目录嵌套的缘由,也有安装逻辑的问题。由于 npm 是请求完一个模块以后再去请求另外一个模块,这就会致使同一个时刻,只有一个模块在下载、解析、安装。

软链时代

后面,有人为了解决目录嵌套层次太高的问题,引入的软连接的方案。npm

简单来讲,就是将全部的包都扁平化安装到一个位置,而后经过软连接(windows 快捷方式)的方式组合到 node_modules 中。json

- app/
- node_modules
  - .modules/
    - express@x.x.x/
      - node_modules
        - connect -> ../../connect@x.x.x
        - path-to-regexp -> ../../path-to-regexp@x.x.x
        - ... -> ../../package-name@x.x.x
    - connect@x.x.x/
    - path-to-regexp@x.x.x/
    - ...others
  - express -> ./.modules/express@x.x.x
复制代码

这样作的好处就是能够将总体的逻辑层级简化到不多的几层。并且对于 node 的模块解析来讲,能够很好的解决相同模块不一样位置致使的加载多个实例,进而致使内存占用的状况。windows

基于这种方案,有 npminstall 以及 pnpm 这个包实现了这种方案,其中 cnpm 使用的就是 npminstall,不过他们实现的方式和我上面讲的是有差别的,具体请看。简单来说,他们没有 .modules 这一层。更多的内容,请看 npminstall 的 README。缓存

总的来说这种解决方案有还有如下几个好处:

  • 兼容性很好
  • 在保证目录足够简洁的状况下,解决了上面的两个问题(目录嵌套和多实例加载)。
  • 安装速度很快,由于采用了软链接的方式加上多线程请求,多个模块同时下载、解析、安装。

那么缺点也是挺致命的:

  • 通常状况下都是第三方库实现这个功能,因此没法保证和 npm 彻底一致的行为,因此遇到问题只能去找做者提交一下,而后等待修复。
  • 没法和 npm 很方便的一块儿使用。最好是要么只用 npm,要么只用 cnpm/pnpm,二者混用可能会产生很奇葩的效果。

npm3 时代

最大的改变就是将目录层级从嵌套变到扁平化,能够说很好的解决了上面嵌套层级过深以及实例不共享的问题。可是,npm3 在扁平化方案下,选择的并非软链接的方式,而是说直接将全部模块都安装到 node_modules 下面。

- app/
- node_modules/
  - express/
  - connect/
  - path-to-regexp/
  - ...
复制代码

若是出现了不一样版本的依赖,好比说 package-a 依赖 package-c@0.x.x 的版本,而 package-b 依赖 package-c@1.x.x 版本,那么解决方案仍是像以前的那种嵌套模式同样。

- app/
- node_modules/
  - package-a/
  - package-c/
    - // 0.x.x
  - package-b/
    - node_modules/
      - package-c/
        - // 1.x.x
复制代码

至于那个版本在外面,那个版本在里面,彷佛是根据安装的前后顺序有关的,具体的我就不验证了。若是有人知道的话,欢迎告诉我。

在这个版本以后,解决了大部分问题,能够说 npm 跨入了一个新的世界。可是还要一个问题就是,他的安装速度依旧很慢,相比 cnpm 来讲。因此他还有不少进步的空间。

yarn 的诞生

随着 Node 社区的愈来愈大,也有愈来愈多的人将 Node 应用到企业级项目。这也让 npm 暴露出不少问题:

  • 没法保证两次安装的版本是彻底相同的。你们都知道 npm 经过语义化的版本号安装应用,你能够限制你安装模块的版本号,可是你没法限制你安装模块依赖的模块的版本号。即便有 shrinkwrap 的存在,可是不多有人会用。
  • 安装速度慢。上文已经讲过,在一些大的项目当中,可能依赖了上千个包,甚至还包括了 C++ Addon,严重的话,安装可能要耗时 10 分钟甚至到达半个小时。这很明显是没法忍受的,尤为是配合上 CI/CD。
  • 默认状况下,npm 是不支持离线模式的,可是在有些状况下,公司的网络可能不支持链接外网,这个时候利用缓存构建应用就是很方便的一件事情。并且能够大大减小网络请求。

因此,此时 yarn 诞生了,为的就是解决上面几个问题。

  • 引入 yarn.lock 文件来管理依赖版本问题,保证每次安装都是一致的。
  • 缓存加并行下载保证了安装速度

那个时候我还在使用 cnpm,我特意比较了一下,发现仍是 cnpm 比较快,因而我仍是继续使用着 cnpm,由于对于我来讲足够了。可是后面发现 yarn 真的愈来愈火,再加上 cnpm 长久不更新。我也尝试着去了用 yarn,在尝试以后,我完全放弃了 cnpm。并且直到如今,彷佛尚未加入 lock 的功能。

固然 yarn 还不仅只有这么几个好处,在用户使用方面:

  • 提供了很是简洁的命令,将相关的命令进行分组,好比说 yarn global 下面都是与全局模块相关的命令。并且提示很是彻底,一眼就能看明白是什么意思。不会像 npm 同样,npm --help 就是一坨字符串,还不讲解一下是什么用处,看着头疼。
  • 默认状况安装会保存到 dependencies,不须要像 npm 同样手动添加 -S 参数
  • 很是方便的 yarn run 命令,不只仅会自动查看 package.json 中 scripts 下面的内容,仍是查找 node_modules/.bin 下的可执行文件。这个是我用 yarn 最高的频率。好比你安装了 yarn add mocha,而后就能够经过 yarn run mocha 直接运行 mocha。而不须要 ./node_modules/.bin/mocha 运行。是我最喜欢的一个功能
  • 交互式的版本依赖更新。npm 你只能先经过 npm outdated 看看那些包须要更新,而后经过 npm update [packages] 更新指定的包。而在 yarn 当中,能够经过交互式的方式,来选择那些须要更新,那些不须要。
  • 全局模块的管理。npm 管理全局模块的方式是经过直接在 /usr/lib/node_modules 下面安装,而后经过软链接链接到 /usr/local/bin 目录下。而 yarn 的作法是选择一个目录,这个目录就是全局模块安装的地方,而后将全部的全局模块当作一个项目,从而进行管理。这个好处就是,你能够直接备份这个目录当中的 package.json 和 yarn.lock 文件,从而能够很方便的在另外一个地方还原你安装了那些全局模块。至于这个目录的问题,经过 yarn global dir 命令就能够找到,mac 下是在 ~/.config/yarn/global/,linux 我没有测试过。

能够说 yarn 用起来很是舒服,可是惟一的缺点就是否是 npm 官方出的,更新力度、兼容性都会差一些。但这也阻挡不住 yarn 在 Node 社区的火热程度。很快,你们纷纷从 npm 切换到 yarn 上面。

重拾 npm 5

在受到 yarn 的冲击以后,npm 官方也决定改进这几个缺点,因而发布了和 Yarn 对抗(这个词是我意淫的)的 npm5 版本。

  1. 引入了 package-lock.json,而且默认就会添加,和 yarn.lock 是同样的做用,而且取代以前的 npm shrinkwrap。
  2. 默认状况下,安装会自动添加 dependencies,不须要手动书写 -S 参数
  3. 提高了安装速度,和以前有了很大的进步,可是和 yarn 相比,仍是略微慢一些

至此,yarn 和 npm 的差距已经很是很是小了,更多的差距体如今用户体验层面,我使用 yarn 的功能也只剩下全局模块管理、模块交互式更新和 yarn run 这个命令了。

可是后面推出的 npx 让我放弃了使用 yarn run 这个命令。不是说 npx 比 yarn 有多好,而是说 npm 集成了这个功能,也就不必再去使用第三方的工具了。并且 npx 还支持临时安装模块,也就是那种只用一次的命令,用完就删掉了。

后面我又发现了 npm-check 这个工具,我用它来替代了 yarn 的交互式更新。

然而 npm6 的出现加入了缓存,而且又进一步提高了速度,能够说直逼 yarn。

因而 yarn 对我来讲只剩下一个全局模块管理的功能了。个人整个开发流程以及从 yarn 切换回 npm 上面了。或许后面的日子我也会让 npm 来接管全局模块管理,从而放弃使用 yarn。可是我仍是会装 yarn,毕竟有一些老项目仍是用 yarn 的。

总结

我经历了从 npm -> cnpm -> yarn -> (npm + npm-check + npx) 的一个循环,也见证了 npm 社区的一步步发展。并且 yarn 的更新频率也很是慢,可能一个月才更新一次,这也让我逐渐放弃使用 yarn。

有的时候感受,第三方的终究是第三方,仍是没有原生的好用和方便,并且用起来安心。

相关文章
相关标签/搜索