本文做者对比了当前主流的包管理工具npm、yarn、pnpm之间的区别,并提出了合适的使用建议,如下为译文:html
npm是Node.js可以如此成功的主要缘由之一。npm团队作了不少的工做,以确保npm保持向后兼容,并在不一样的环境中保持一致。node
npm是围绕着语义版本控制(semver)的思想而设计的,下面是从他们的网站摘抄过来的:react
给定一个版本号:主版本号.次版本号.补丁版本号, 如下这三种状况须要增长相应的版本号:git
npm使用一个名为package.json的文件,用户能够经过npm install --save命令把项目里全部的依赖项保存在这个文件里。github
例如,运行npm install --save lodash会将如下几行添加到package.json文件中。npm
"dependencies": {
"lodash": "^4.17.4"
}
复制代码
请注意,在版本号lodash以前有个^字符。这个字符告诉npm,安装主版本等于4的任意一个版本便可。因此若是我如今运行npm进行安装,npm将安装lodash的主版本为4的最新版,多是 lodash@4.25.5(@是npm约定用来肯定包名的指定版本的)。你能够在此处查看全部支持的字符:docs.npmjs.com/misc/semver。json
理论上,次版本号的变化并不会影响向后兼容性。所以,安装最新版的依赖库应该是能正常工做的,并且能引入自4.17.4版本之后的重要错误和安全方面的修复。react-native
可是,另外一方面,即便不一样的开发人员使用了相同的package.json文件,在他们本身的机器上也可能会安装同一个库的不一样种版本,这样就会存在潜在的难以调试的错误和“在个人电脑上…”的情形。缓存
大多数npm库都严重依赖于其余npm库,这会致使嵌套依赖关系,并增长没法匹配相应版本的概率。安全
虽然能够经过npm config set save-exact true命令关闭在版本号前面使用^的默认行为,但这个只会影响顶级依赖关系。因为每一个依赖的库都有本身的package.json文件,而在它们本身的依赖关系前面可能会有^符号,因此没法经过package.json文件为嵌套依赖的内容提供保证。
为了解决这个问题,npm提供了shrinkwrap命令。此命令将生成一个npm-shrinkwrap.json文件,为全部库和全部嵌套依赖的库记录确切的版本。
然而,即便存在npm-shrinkwrap.json这个文件,npm也只会锁定库的版本,而不是库的内容。即使npm如今也能阻止用户屡次重复发布库的同一版本,可是npm管理员仍然具备强制更新某些库的权力。
这是引用自shrinkwrap文档的内容:
若是你但愿锁定包中的特定字节,好比是为了保证能正确地从新部署或构建,那么你应该在源代码控制中检查依赖关系,或者采起一些其余的机制来校验内容,而不是靠校验版本。
npm 2会安装每个包所依赖的全部依赖项。若是咱们有这么一个项目,它依赖项目A,项目A依赖项目B,项目B依赖项目C,那么依赖树将以下所示:
node_modules
- package-A
-- node_modules
--- package-B
----- node_modules
------ package-C
-------- some-really-really-really-long-file-name-in-package-c.js
复制代码
这个结构可能会很长。这对于基于Unix的操做系统来讲只不过是一个小烦恼,但对于Windows来讲倒是个破坏性的东西,由于有不少程序没法处理超过260个字符的文件路径名。
npm 3采用了扁平依赖关系树来解决这个问题,因此咱们的3个项目结构如今看起来以下所示:
node_modules
- package-A
- package-B
- package-C
-- some-file-name-in-package-c.js
复制代码
这样,一个原来很长的文件路径名就从./node_modules/package-A/node_modules/package-B/node-modules/some-file-name-in-package-c.js变成了/node_modules/some-file-name-in-package-c.js。
你能够在这里阅读到更多有关NPM 3依赖解析的工做原理。
这种方法的缺点是,npm必须首先遍历全部的项目依赖关系,而后再决定如何生成扁平的node_modules目录结构。npm必须为全部使用到的模块构建一个完整的依赖关系树,这是一个耗时的操做,是npm安装速度慢的一个很重要的缘由。
因为我没有详细了解npm的变化,因此我想固然的觉得每次运行npm install命令时,NPM都得从互联网上下载全部内容。
可是,我错了,npm是有本地缓存的,它保存了已经下载的每一个版本的压缩包。本地缓存的内容能够经过npm cache ls命令进行查看。本地缓存的设计有助于减小安装时间。
总而言之,npm是一个成熟、稳定、而且有趣的包管理器。
Yarn发布于2016年10月,并在Github上迅速拥有了2.4万个Star。而npm只有1.2万个Star。这个项目由一些高级开发人员维护,包括了Sebastian McKenzie(Babel.js)和Yehuda Katz(Ember.js、Rust、Bundler等)。
从我搜集到的状况来看,Yarn一开始的主要目标是解决上一节中描述的因为语义版本控制而致使的npm安装的不肯定性问题。虽然可使用npm shrinkwrap来实现可预测的依赖关系树,但它并非默认选项,而是取决于全部的开发人员知道而且启用这个选项。
Yarn采起了不一样的作法。每一个yarn安装都会生成一个相似于npm-shrinkwrap.json的yarn.lock文件,并且它是默认建立的。除了常规信息以外,yarn.lock文件还包含要安装的内容的校验和,以确保使用的库的版本相同。
因为yarn是崭新的通过从新设计的npm客户端,它能让开发人员并行化处理全部必须的操做,并添加了一些其余改进,这使得运行速度获得了显著的提高,整个安装时间也变得更少。我估计,速度提高是yarn受欢迎的主要缘由。
像npm同样,yarn使用本地缓存。与npm不一样的是,yarn无需互联网链接就能安装本地缓存的依赖项,它提供了离线模式。这个功能在2012年的npm项目中就被提出来过,但一直没有实现。
yarn还提供了一些其余改进,例如,它容许合并项目中使用到的全部的包的许可证,这一点让人很高兴。
一个有趣的事情是,yarn文档的态度开始针对npm发生改变,由于yarn项目变得流行起来。
最开始的yarn公告是这么介绍yarn的安装的:
*最简单的入门方法是运行:
npm install -g yarn
yarn*
复制代码
如今的yarn安装页面是这么说的:
注意:一般状况下不建议经过npm进行安装。npm安装是非肯定性的,程序包没有签名,而且npm除了作了基本的SHA1哈希以外不执行任何完整性检查,这给安装系统程序带来了安全风险。
基于这些缘由,强烈建议你经过最适合于你的操做系统的安装方法来安装yarn。
以这种速度发展下去的话,若是yarn要宣布他们本身的registry,让开发者慢慢淘汰npm的话,咱们一点都不会感到惊讶。
看起来彷佛要感谢yarn,npm终于意识到他们须要更加关注一些你们强烈要求的问题了。当我在审核我以前提到的强烈要求的“离线”功能时,我注意到这个需求正在被积极地修复之中。
正如我所提到的,在pnpm的做者Zoltan Kochan发表了“为何要用pnpm?”以后,我才知道pnpm。
我不会介绍太多的细节(由于这篇文章已经发布好久了),可是你能够查看个人最初的帖子来寻找更多的内容,同时在Twitter上加入讨论。
可是
我想指出的是,pnpm运行起来很是的快,甚至超过了npm和yarn。
为何这么快呢? 由于它采用了一种巧妙的方法,利用硬连接和符号连接来避免复制全部本地缓存源文件,这是yarn的最大的性能弱点之一。
使用连接并不容易,会带来一堆问题须要考虑。
正如Sebastian在Twitter上指出的那样,他最初是打算在yarn中使用符号连接的,可是因为其余一些缘由放弃了它。
同时,正如在Github上拥有2000多个Star那样,pnpm可以为许多人所用。
此外,截至2017年3月,它继承了yarn的全部优势,包括离线模式和肯定性安装。
我认为yarn和pnpm的开发人员作了一个惊人的工做。我我的喜欢的是肯定性安装,由于我喜欢控制,我不喜欢惊喜。
不管这场竞争的结果是什么,我很感谢yarn在npm的脚下点了一把火,提供了另一个选择。
我确信yarn是一个更安全的选择,可是pnpm多是一些测试用例的更好的选择。例如,它能够在运行大量集成测试并但愿尽量快地安装依赖关系的中小型团队中发挥做用。
最后,我认为,npm仍然提供了一个很是有用的解决方案,支持大量的测试用例。大多数开发人员使用原始npm客户端仍然能够作得很好。