第一篇发于http://blog.xgheaven.com/2018...从node环境来看,不可或缺的一个部分就是npm包管理,但是因为官方的npm存在各种问题,很多产生了不同的版本,其中的曲折也许只有来过的人才知道。放弃npm?在古代,在古代的版本中(应该是npm3之前的版本,具体记不清了),npm的安装策略不是flat的,也就是说,如果你安装一个express,那么你就在node_modules下只找到一个快递文件夹。express依赖的项目放在它的文件夹下。-app/-package.json-node_modules/-express/-index.js-package.json-node_modules/-...由此产生的问题,windows用户可能很好理解,因为在这个安装环境下,会有目录级别很高,对于windows,最大路径长度限制在248个字符(更多见这里),再加上node_modules这个词很长,你懂的,哈哈哈。自己去搜解决办法吧,反正现在估计没人用古版了。除了Windows用户的问题,还有一个更严重的问题,就是模块相互独立。比如express下的path-to-regexp模块和connect下的path-to-regexp模块是两个不同的模块。模块。那么这会产生什么影响呢?其实在使用中,并没有太大的影响,只是内存占用太大了。因为很多相同的模块位于不同的模块下,所以会出现多个实例(为什么要加载多个实例,请参见Node模块加载)。你想想,同一个功能,为什么要实例化那么多次呢?我们不能只加载一次并重用实例吗?古代npm的缺点可以说很多:目录嵌套层次太深,模块实例无法共享,安装速度很慢。有目录嵌套和安装逻辑问题的原因。因为npm请求一个模块后又请求另一个模块,这样会导致同时下载、解析、安装的只有一个模块。软链接时代之后,有人为了解决目录嵌套层次过高的问题,推出了软链接方案。简单点说就是把所有的包压扁到一个位置,然后通过软链接(windows快捷方式)组合成node_modules。-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的模块分析,可以很好的解决同一个模块在不同位置加载多个实例,进而导致内存占用的问题。基于这个解决方案,有npminstall和pnpm包实现了这个解决方案。其中cnpm使用的是npminstall,但是它们的实现方式和我上面说的不一样。详情请看。简而言之,它们没有.modules层。有关更多信息,请参阅npminstall的自述文件。总的来说,这个方案有以下优点:兼容性好,在保证目录足够简洁的同时,解决了上面两个问题(目录嵌套和多实例加载)。安装速度非常快,因为采用了软连接和多线程请求的方式,多个模块同时下载、解析、安装。缺点也是相当致命:一般情况下,第三方库都实现了这个功能,所以不能保证和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。而且到现在为止好像还没有加锁功能。当然,纱线不仅有这几个优点。在用户使用方面:它提供了非常简洁的命令和分组相关命令。例如,以下yarn全局命令与全局模块相关。而且提示很完整,一看就明白是什么意思。不像npm,npm--help就是一堆字符串,也不解释是干什么用的,很头疼。默认情况下,安装将保存到dependencies。你不需要像npm那样手动添加-S参数。非常方便的yarnrun命令,不仅会自动查看package.json中scripts下的内容,还会自动查找node_modules/.bin下的可执行文件。这是我使用纱线的最高频率。比如你安装了yarnaddmocha,那么你就可以直接通过yarnrunmocha运行mocha。没有./node_modules/.bin/mocha运行。是我最喜欢的功能之一交互式版本依赖更新。npm只能先通过npmoutdated查看需要更新哪些包,再通过npmupdate[packages]更新指定的包。在yarn中,您可以交互式地选择那些需要更新的和不需要更新的。全局模块管理。npm管理全局模块的方式是直接安装在/usr/lib/node_modules下,然后通过软链接连接到/usr/local/bin目录下。yarn的方法是选择一个目录,也就是安装全局模块的地方,然后把所有的全局模块都当作一个项目来管理。好处是可以直接备份这个目录下的package.json和yarn.lock文件,这样就可以很方便的在另外一个地方恢复你安装的那些全局模块。至于这个目录的问题,可以通过yarnglobaldir命令找到。在mac下的~/.config/yarn/global/中。我还没有在linux上测试过它。可以说yarn用起来很舒服,唯一的缺点就是不是npm官方发布的,更新和兼容性会差一些。但这并不能阻止yarn在Node社区的流行。很快,大家就从npm切换到了yarn。重拾npm5在受到yarn的冲击之后,npm官方也决定改进这些缺点,于是发布了对抗Yarn的npm5版本(这个词是我的执念)。引入package-lock.json,默认添加,功能与yarn.lock相同,替代之前的npmshrinkwrap。默认安装会自动添加依赖,无需手动编写。-S参数提高了安装速度,比起之前有了很大的进步,但还是比yarn稍微慢一些。在这一点上,yarn和npm的差距已经非常小了,更多的差距体现在用户体验层面。我使用yarn的唯一功能是全局模块管理、模块交互式更新和命令yarnrun。但是后来推出的npx让我放弃了使用yarnrun命令。不是说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。有时候觉得第三方终归是第三方,还是不如原来的好用方便,用起来安全。
