Nodejs 为什么不使用类似 Java 的 ClassPath + Maven 的包管理模式?

2020-01-05 15:24:45 +08:00
 lihongjie0209

这样做的好处很明显

  1. 包只需要全局存在, 不同版本的包在不同的文件夹, 全局只需要下载一次就好了
  2. 使用包只需要在启动时指定相关的 PATH 就可以了

这样可以完美的避免每个项目都需要 npm install 问题, 同一个包必须在每个项目中都拷贝一份

肯定有人说现在磁盘不值钱的, 所有依赖也就几百 MB, 不值得这种优化

但是有没有想过每次开发时每次 npm install 几分钟, 出错之后 windows 下删除又是几分钟(大量小文件)对开发效率的影响.

我的问题是:

  1. 这种模式有什么缺点吗?
  2. nodejs 为什么没有往这个方向发展
8788 次点击
所在节点    程序员
89 条回复
deleteDB
2020-01-06 09:59:04 +08:00
@lihongjie0209
“有些东西缺点很明显, 还不能拿出来讨论了?”
换做我, 我会问为什么当初这么设计,为什么没考虑到这些缺陷?有什么原因么?如何避免?

为什么调侃你, 你想过么?有的人说话特别武断还不能调侃了? 哈哈哈
lihongjie0209
2020-01-06 09:59:30 +08:00
@hitaoguo #59 这些都是可以配置的, 而且在 maven 中不存在一个 uninstall 的概念, 你的项目如果不需要这个依赖, 那么就直接在 pom 中移除就好了
lihongjie0209
2020-01-06 10:00:23 +08:00
@deleteDB #61 看一下我的问题
deleteDB
2020-01-06 10:02:18 +08:00
@lihongjie0209 看一下你平时留言的表达吧 哈哈 就是想怼你
deleteDB
2020-01-06 10:02:46 +08:00
@lihongjie0209 其他帖子 别人的帖子里都看看
szq8014
2020-01-06 10:03:13 +08:00
感觉很多人都理解错了, 依赖 [存储] 和 [加载] 是两回事。。

嵌套存储:
app
-- m1-v1
-- -- m3-v1
-- m2-v1
-- -- m1-v2
-- -- -- m3-v2
扁平存储
app
-- m1-v1
-- m1-v2
-- m2-v1
-- m3-v1
-- m3-v2

扁平存储显然是并不存在什么多版本共存的问题,而且比嵌套存储更节省那一点点存储空间的,也并不限制你打包只能加载一个模块的一个版本,有能力当然可以把 commons-lang3 的所有版本全打进 fat-jar 或 war 包里,只不过 java 的默认类加载制度的限制导致 maven 只能默认选一个它约定下的最合适的一个版本,这并不是扁平化的存储导致的。


想想如果 java 需要支持多版本 jar 包存在该如何改呢?
1. 改语法 import, 在包里面加上版本? 需要源码改动不太现实~
2. 把依赖关系带到运行时告诉类加载器,有一个类似 maven 的 pom.xml 的东西,告诉这个类依赖的版本。比如打包时直接把所有的 jar 重新打,在 META-INF 里面加上依赖描述。
3. 学 nodejs 弄目录嵌套

在 java 里面多版本共存还有许多问题,比如两个版本的同一个类在 jvm 看来是两个完全没关系的东西,假如一个接口返回这个类实例传再传给另一个接口时,虽然参数是需要这个类,传的也是,但是仅仅是因为不是同一个类加载器加载的,会报 class cast exception 的。

这是 java 的问题,不是扁平化包管理的问题。

所以大家别吐槽 maven 了,锅不在 maven~
lihongjie0209
2020-01-06 10:09:39 +08:00
@szq8014 #66 要在 Java 中实现一个包 N 个版本的话要上 OSGI, 目前我还没遇到过这种需求
whypool
2020-01-06 10:22:56 +08:00
npm 除了质量低,没槽点了
wangxiaoaer
2020-01-06 10:25:36 +08:00
用 java 这么多年了,就没遇到你们说的一个项目需要依赖同一个库的不同版本这种需求。

但是碰到过因为多个第三方库(假设是 A 和 B )的传递依赖导致依赖了同一个库的不同版本(假设是 C ),maven 和 gradle 都提供了排除某个版本的办法。如果 A 和 B 在运行时必须用不同的 C 版本,那就对 A 和 B 的版本进行调整(弄不好某个一低版本的 A 或者 B 可以共用某个版本的 C ),确保能够兼容。如果还是不行,那就 A 和 B 里面换一个。

但即使这样,上述情况也不多见,基本上 spring boot 一把梭,自己手工添加的第三方依赖很少有冲突的情况。

所以 Node 为了所谓的”项目依赖同一个库的不同版本“这种小众需求而采用嵌套式的依赖存储,不惜放弃存储空间和安装效率,不敢苟同。

不过想想一个函数作为一个库这种**行为都能够广泛存在,也就不难理解了。
lihongjie0209
2020-01-06 10:29:21 +08:00
@wangxiaoaer #69 这个需求应该是类库\中间件的需求, 他们需要保证兼容性, 作为开发者真的很少用到
https://www.baeldung.com/osgi
```
Several Java mission-critical and middleware applications have some hard technological requirements.

Some have to support hot deploy, so as not to disrupt the running services – and others have to be able to work with different versions of the same package for the sake of supporting external legacy systems.

The OSGi platforms represent a viable solution to support this kind of requirements.
```
wangxiaoaer
2020-01-06 10:51:45 +08:00
@lihongjie0209 #70 我说的是开发过程中的普通依赖,一般通过我说的几步就解决了,还没到需要上 OSGI 的地步。
lihongjie0209
2020-01-06 10:54:25 +08:00
@wangxiaoaer #71 嗯, 所以我觉得这个需求很奇怪
xxdd
2020-01-06 10:58:07 +08:00
@Livid ID:deleteDB 戾气和低效讨论
yhxx
2020-01-06 11:04:12 +08:00
@lihongjie0209 其实我倒是觉得目前 npm 这样是个优点
每个项目都很独立,互不影响,我觉得挺好的
除了 npm install 的时候慢了一点之外好像也没什么不能接受的缺点
Curtion
2020-01-06 11:19:29 +08:00
npm 非常复杂,该机制成本很大,就 npm3 光展平包就花了大量功夫, 而且现在也有一些好处,比可以直接拷贝文件夹实现移动项目;还有一个好处可以直接修改包的源码,而不影响其它项目,因为 npm 的包实在太多了,很多包之间会产生 bug,这时候需要手动修复,如果等作者更新可能会很慢,因为出现 bug 的包不一定是你自己项目的依赖,有可能是项目依赖的依赖
lihongjie0209
2020-01-06 11:27:21 +08:00
@Curtion #75
使用依赖管理的一个原因就是为了每个项目只存储依赖描述文件, 而不需要把几万个依赖上传到 git, 同时还能保证每个开发者都使用一样的依赖

如果我们一旦修改由依赖管理软件下载的源码, 那么别的开发者使用 npm install 的时候就无法和你的依赖版本保持一致了, 正确的做法是把需要修改源码的依赖拷贝到你的项目中, 作为你的源码维护, 同时在依赖管理中去掉这个依赖.
Curtion
2020-01-06 11:44:52 +08:00
@lihongjie0209 #76 移动项目确实毕竟鸡肋,没啥用就不谈了。 至于第二个问题,你说的对,但是这确实又是一个解决办法,我实际遇见过,我使用了一个库 A,它依赖于另一个库 B,而 B 在某些情况下会触发 bug,在 B 已经解决 BUG 的情况下,但是 A 的维护者更新落后,A 中的依赖还是比较落后的版本。
此时修改库 B 的源代码,或者修改 A 的依赖版本是最简单的方式,等到 A 更新后只需要重新 install 就行,如果库是全局作用的,势必会影响其它项目。

当然这只是我一厢情愿找的理由,或许实际上或者全局作用更好。不过 npm 有 cache 机制,一定情况下缓解了下载时间过长的问题。
Curtion
2020-01-06 11:51:34 +08:00
@lihongjie0209 #76 如果把源代码拷贝出来不可行,一是以后有修复就必须手动更新,二是依赖项的层次可能很深入,我也不太了解如果把依赖提升的话,下层的代码还能不能正确找到包
szq8014
2020-01-06 11:56:20 +08:00
@lihongjie0209 +1 目前依赖冲突就是通过选一个特定版本就解决了,觉得最麻烦的冲突也就是日志库的冲突了,有些库没有用 slf4j,直接依赖了 log4j 或 commons-logging,而项目用的 log4j2/logback,而解决方式就是用现有的 log4j 接口的实现再转回 slf4j 里面。log4j-to-slf4j, log4j-slf4j-impl 能让人迷糊,别的真没这么费脑子
fox1955
2020-01-06 12:03:24 +08:00
"每次开发时每次 npm install 几分钟"

- clone 下来后 install 一次, 后面开发时不用 install.

源码是源码, 依赖也是源码, 我们把程序运行需要的完整源码放在一起, 在结构上是非常干净整洁的. 至于 "大量小文件问题", 还是那句话, 一个项目只用 install 一次, 对开发零影响. "磁盘空间问题" 不是问题.

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/635217

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX