首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
lihongjie0209
V2EX  ›  程序员

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

  •  
  •   lihongjie0209 · 52 天前 · 5135 次点击
    这是一个创建于 52 天前的主题,其中的信息可能已经有所发展或是发生改变。

    这样做的好处很明显

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

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

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

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

    我的问题是:

    1. 这种模式有什么缺点吗?
    2. nodejs 为什么没有往这个方向发展
    第 1 条附言  ·  52 天前
    在 Youtube 上看到一个新的包管理工具, 和上面的想法类似
    89 回复  |  直到 2020-01-07 07:16:48 +08:00
    hantsy
        1
    hantsy   52 天前
    最新的 Yarn,NPM 好像都是全局的吧,只是项目下有一份 Copy。
    lihongjie0209
        2
    lihongjie0209   52 天前
    @hantsy #1 应该说是全局缓存吧, 安装的时候还是会复制缓存到项目下
    realkenshinji
        3
    realkenshinji   52 天前 via iPhone
    缺点就是同一个包可能在不同项目下的版本不同,所以分项目来安装依赖是必须的,我想这也是 python 下项目为什么要搞 venv 的原因吧
    charlie21
        4
    charlie21   52 天前
    明知道有大量小文件的操作,为什么要在 win 下进行呢?
    lihongjie0209
        5
    lihongjie0209   52 天前   ❤️ 5
    @charlie21 #4 我没必要因为一个包管理器换操作系统和硬件吧
    lihongjie0209
        6
    lihongjie0209   52 天前
    @realkenshinji #3 在 Java 中, 比如说你要使用 packagex 1.1 版本的包, 那么就会在启动时指定 "maven 本地仓库 /packagex/1.1", 当你在另一个项目中使用 2.1 版本的时候, 就会指定 "maven 本地仓库 /packagex/2.1", 所以不会冲突
    watzds
        7
    watzds   52 天前 via Android
    Java 要运行,也会打包复制那些吧
    watzds
        8
    watzds   52 天前 via Android
    Java 要服务器上运行,也会打包复制那些吧
    agagega
        9
    agagega   52 天前 via iPhone
    这不是 Node 一直吹的优点之一吗?像 RubyGems 这些工具就是像楼主说的一样按版本安装在系统目录的。
    另外,install -g 不行?
    lihongjie0209
        10
    lihongjie0209   52 天前
    @watzds #8 类似 webpack, 是可以配置的. 不过这里讨论的是在开发阶段, 如何避免这种不必要的依赖安装
    lihongjie0209
        11
    lihongjie0209   52 天前
    @agagega #9 估计全局安装问题很大, 因为设计上之前就没考虑过这么使用
    houzhenhong
        12
    houzhenhong   52 天前   ❤️ 2
    pnpm 和 yarn 的 pnp 不知道符不符合你的要求
    lihongjie0209
        13
    lihongjie0209   52 天前
    @houzhenhong #12 我去看看, 感谢回复
    leafdream
        14
    leafdream   52 天前
    你可以 npm i -g
    gitjavascript
        15
    gitjavascript   52 天前
    难道你们在服务器上 install 么
    zappos
        16
    zappos   52 天前 via Android
    @realkenshinji venv 还不如 docker 方便。。。而且 venv 只解决 python 的问题,docker 可以解决任何语言的这类问题。
    jybox
        17
    jybox   52 天前
    最大的好处就是每个包可以对自己使用的依赖版本有充分的控制。Node.js 里一个项目(由若干个包构成)里可以同时使用同一个包的不同版本,而不必像 Java (我不是很了解 Java,但我听说是这样)在一个项目中,只能使用一个包的特定版本,如果不同的包依赖了同一个包的不同版本,就可能出现无法解决的依赖冲突。

    作为一个 Node.js 开发者,我是非常认可这种做法的,我认为这种设计所解决的问题是要比带来的麻烦多的。
    zappos
        18
    zappos   52 天前 via Android
    ( 1 )可以选择安装在局部和全局,( 2 )局部优先

    npm 都实现了啊,没问题啊

    只不过有时候 require 全局的包需要配置个路径,我记得,配置了就好了
    optional
        19
    optional   52 天前 via iPhone
    这是优点。。不是缺点。 全局安装还要管权限之类的问题
    lihongjie0209
        20
    lihongjie0209   52 天前
    @optional #19 不需要, 类似 maven 全局安装可以指定任何目录, 每一个项目都可以配置自己的全局目录, 极端情况可以做到类似 npm 的一个项目一个本地仓库
    lihongjie0209
        21
    lihongjie0209   52 天前
    @optional #19 如果这是优点的话, 那么 pnpm 和 yarn 的 pnp 出现的意义是什么
    zhbhun
        22
    zhbhun   52 天前
    同意 jybox 的看法,现在项目依赖多了出现版本冲突的情况很正常(假设模块 A 和 B 都依赖 C,但 C 的版本不同,A 要的是 Cv1,B 要的是 Cv2)。而基于 nodejs 依赖的查找原理(从当前目录开始往上级目录查找 node_modules 下的依赖),将依赖放在本地( B 和 C 各自在其目录下放一个对应版本的 C )可以很好的解决依赖版本冲突问题。
    MoccaCafe
        23
    MoccaCafe   52 天前
    npm 最严重的问题就是安装依赖之后,有上万个细小文件,严重占据 inode 或者说复制困难。像 java 的 jar 包就非常合理了,java 虽然繁琐但是许多设计思想还是很好的
    murmur
        24
    murmur   52 天前
    所以才有了 npm3,所以才有 yarn,npm 老版本的树状结构再 windows 下简直是噩梦,以前真的就是没法用
    zhbhun
        25
    zhbhun   52 天前
    在 window 上的 nodejs 开发体验确实不好,我现在都是在 window 上用 vagrant 开个虚拟机,然后使用 vscode 的 ssh 远程开发,即使依赖再多 node_moduels 也是秒删的,node 运行时 cpu 占用率也低,风扇噪音也小了很多。
    Juszoe
        26
    Juszoe   52 天前
    Node 之父也认为 node_modules 的设计是个错误
    sessionreckon
        27
    sessionreckon   52 天前
    @jybox Java 这种情况是可解的,就是比较麻烦,需要重新用 shade 打个包
    Bromine0x23
        28
    Bromine0x23   52 天前   ❤️ 1
    个人认为 npm 的问题主要还是 node_modules
    1) 依赖数量多(普遍上千),依赖安装后产生了大量文件夹和小文件,文件系统负担很大(当然这个可能算 js 依赖本身的性质导致的)
    2) 默认将依赖解压到项目下的行为导致了重复存储,结合 1) 加剧了文件系统负担。

    不同项目依赖不同版本的问题和使用全局存储是不冲突的,只要在路径上加上版本信息就行 比如 maven 用 package/version/ 或者 gems 用 package-version/
    optional
        29
    optional   52 天前 via iPhone
    @lihongjie0209 没有银弹解决所有人的需求和爱好
    wangyzj
        30
    wangyzj   52 天前
    说实话
    我更喜欢每个项目单独管理自己的依赖
    干净
    lihongjie0209
        31
    lihongjie0209   52 天前
    @wangyzj #30 难道 maven 做不到?
    lewis89
        32
    lewis89   52 天前
    @jybox #17 maven 是有一个默认优先的 ,最短引用路径的版本优先机制 ,
    你没遇到过坑,我遇到过 那种动态反射拿出来了一个老版本的 class
    但是用新版本的方法去调用 结果 出现 class 没有这种方法的报错,这种问题在 maven 打包的 java 里面经常遇到
    lewis89
        33
    lewis89   52 天前
    @lihongjie0209 #31

    @jybox #17

    https://segmentfault.com/a/1190000014938685

    看看 maven 吧 一样有很多问题,版本依赖冲突这种问题 没什么好办法 ,只能通过人为管理的办法。
    lewis89
        34
    lewis89   52 天前
    @lihongjie0209 #31 我被 maven 坑了也不是一次两次了,最后还是想办法 excluse 掉一些引用 避免默认的最短路径优先的仲裁机制。
    autoxbc
        35
    autoxbc   52 天前
    所以 Deno 里没有包管理,依赖什么就 import 网络地址,Deno 扫描一下全局缓存着
    wangyzj
        36
    wangyzj   52 天前
    @lihongjie0209 没用过 maven
    可你问的不是全局管理包吗
    uxstone
        37
    uxstone   52 天前
    @jybox 为什么要在项目里使用同一个包的不同版本,强行增加复杂度?
    uxstone
        38
    uxstone   52 天前
    https://v2ex.com/t/334487
    三年前 我也提问过同样的问题, 慢慢习惯下来也就这样了
    lihongjie0209
        39
    lihongjie0209   52 天前
    @uxstone #37 我看到这种说法的时候也是挺懵逼的, 这种情况应该尽量避免, 避免不了就特殊处理, 从来没把这种东西当作一个正常的需求
    lihongjie0209
        40
    lihongjie0209   52 天前
    @wangyzj #36 我说的是全局存储, 动态加载, 没必要拷贝到当前项目下
    lihongjie0209
        41
    lihongjie0209   52 天前
    @lewis89 #34 每次加依赖都应该把 effective pom 导出来看看, 这样可以避免很多问题
    abcbuzhiming
        42
    abcbuzhiming   52 天前
    说 npm 已经解决了的各位,我问一个问题,一个包,怎么才能在全局安装多个版本?到目前为止,我就没发现默认的 npm 有多版本共存的解决办法
    lewis89
        43
    lewis89   52 天前
    @lihongjie0209 #41 但是 Java 有些地方是动态加载 调用的 还是没法避免..
    lihongjie0209
        44
    lihongjie0209   52 天前
    @lewis89 #43 这种问题应该会在开发和测试阶段就暴露出来, 同样的, 使用 BeanUtils 也只能在运行时报错, 只要使用了反射, 这种问题就无法避免
    MiracleKagari
        45
    MiracleKagari   52 天前 via Android
    @uxstone 不是你用,是你引入的依赖用的依赖是不同的版本
    YuTengjing
        46
    YuTengjing   52 天前
    因为 node 是基于文件系统的模块化方案,设计上没有所谓的 classpath,必然是所有包都要安装到 node_modules 下。
    jybox
        47
    jybox   52 天前
    @uxstone
    @lihongjie0209
    因为你控制不了你依赖的其他包作者用什么版本呀。举个例子 lodash 是 NPM 上被依赖最多的包,你项目依赖的很多其他包可能也依赖了 lodash,而他们依赖的版本可能和你不同。
    Kilerd
        48
    Kilerd   52 天前
    有一说一,为什么还有人觉得 java 的包依赖管理好的呢
    ashong
        49
    ashong   52 天前 via iPhone
    这正是 npm 的优点
    zhw2590582
        50
    zhw2590582   51 天前 via iPhone
    我当时也有这样的疑问
    yinzhili
        51
    yinzhili   51 天前
    nodejs 这种新生事物各种包管理工具还有点混乱,和已经成熟的 java 生态系统没法比
    lihongjie0209
        52
    lihongjie0209   51 天前
    @Kilerd #48 有什么缺点吗?
    lihongjie0209
        53
    lihongjie0209   51 天前
    @yinzhili #51 我也是这么想的, 以后的项目我们是不是应该直接照抄业界也有的实现, 然后再根据自己的需求做定制化, 这样可以避免走一些别人已经踩过的坑.
    yhxx
        54
    yhxx   51 天前
    有的
    PnP、pnpm、tink 都是这种思路
    deleteDB
        55
    deleteDB   51 天前
    别问 问就是前端不行
    一会是接口展示不行
    一会是包管理不行

    java 最棒 手动狗头保命
    lihongjie0209
        56
    lihongjie0209   51 天前
    @yhxx #54 目前来看 pnp 和 tink 都在开发中, 还没有大规模使用, 而且 pnp 和 tink 都是与 hack nodejs 的 require, 如果与 wepack 使用, pnp 还需要使用自己的 loader, 总体来说改动还是比较大的
    lihongjie0209
        57
    lihongjie0209   51 天前
    @deleteDB #55 你这种话没有任何意义, 行不行是一个很主观的东西, 但是有些东西缺点很明显, 还不能拿出来讨论了?
    hitaoguo
        58
    hitaoguo   51 天前
    1.路径会不会有问题?
    你的电脑跟我的电脑全局安装的路径可能会不一样,项目里面如何引用。
    hitaoguo
        59
    hitaoguo   51 天前
    2.移除包的时候,会不会冗余?
    比如这个包我只在一个项目里面用了,那我在项目里面 npm un 的时候会不会把全局的也删掉,那如果多个项目引用怎么判断。
    yannxia
        60
    yannxia   51 天前
    还是 Rust 的 Cargo 的包管理模式好啊,随便你怎么依赖不同的版本。Maven 的问题就是多版本依赖的问题,至于 NPM 的问题,包的质量的太低是真的。
    deleteDB
        61
    deleteDB   51 天前
    @lihongjie0209
    “有些东西缺点很明显, 还不能拿出来讨论了?”
    换做我, 我会问为什么当初这么设计,为什么没考虑到这些缺陷?有什么原因么?如何避免?

    为什么调侃你, 你想过么?有的人说话特别武断还不能调侃了? 哈哈哈
    lihongjie0209
        62
    lihongjie0209   51 天前
    @hitaoguo #59 这些都是可以配置的, 而且在 maven 中不存在一个 uninstall 的概念, 你的项目如果不需要这个依赖, 那么就直接在 pom 中移除就好了
    lihongjie0209
        63
    lihongjie0209   51 天前
    @deleteDB #61 看一下我的问题
    deleteDB
        64
    deleteDB   51 天前   ❤️ 1
    @lihongjie0209 看一下你平时留言的表达吧 哈哈 就是想怼你
    deleteDB
        65
    deleteDB   51 天前
    @lihongjie0209 其他帖子 别人的帖子里都看看
    szq8014
        66
    szq8014   51 天前   ❤️ 1
    感觉很多人都理解错了, 依赖 [存储] 和 [加载] 是两回事。。

    嵌套存储:
    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
        67
    lihongjie0209   51 天前
    @szq8014 #66 要在 Java 中实现一个包 N 个版本的话要上 OSGI, 目前我还没遇到过这种需求
    whypool
        68
    whypool   51 天前
    npm 除了质量低,没槽点了
    wangxiaoaer
        69
    wangxiaoaer   51 天前   ❤️ 1
    用 java 这么多年了,就没遇到你们说的一个项目需要依赖同一个库的不同版本这种需求。

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

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

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

    不过想想一个函数作为一个库这种**行为都能够广泛存在,也就不难理解了。
    lihongjie0209
        70
    lihongjie0209   51 天前
    @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
        71
    wangxiaoaer   51 天前
    @lihongjie0209 #70 我说的是开发过程中的普通依赖,一般通过我说的几步就解决了,还没到需要上 OSGI 的地步。
    lihongjie0209
        72
    lihongjie0209   51 天前
    @wangxiaoaer #71 嗯, 所以我觉得这个需求很奇怪
    xxdd
        73
    xxdd   51 天前
    @Livid ID:deleteDB 戾气和低效讨论
    yhxx
        74
    yhxx   51 天前
    @lihongjie0209 其实我倒是觉得目前 npm 这样是个优点
    每个项目都很独立,互不影响,我觉得挺好的
    除了 npm install 的时候慢了一点之外好像也没什么不能接受的缺点
    Curtion
        75
    Curtion   51 天前
    npm 非常复杂,该机制成本很大,就 npm3 光展平包就花了大量功夫, 而且现在也有一些好处,比可以直接拷贝文件夹实现移动项目;还有一个好处可以直接修改包的源码,而不影响其它项目,因为 npm 的包实在太多了,很多包之间会产生 bug,这时候需要手动修复,如果等作者更新可能会很慢,因为出现 bug 的包不一定是你自己项目的依赖,有可能是项目依赖的依赖
    lihongjie0209
        76
    lihongjie0209   51 天前
    @Curtion #75
    使用依赖管理的一个原因就是为了每个项目只存储依赖描述文件, 而不需要把几万个依赖上传到 git, 同时还能保证每个开发者都使用一样的依赖

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

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

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

    源码是源码, 依赖也是源码, 我们把程序运行需要的完整源码放在一起, 在结构上是非常干净整洁的. 至于 "大量小文件问题", 还是那句话, 一个项目只用 install 一次, 对开发零影响. "磁盘空间问题" 不是问题.
    lihongjie0209
        81
    lihongjie0209   51 天前
    @fox1955 #80 你不加依赖了吗? 修改 webpack 配置加 loader 和插件也要 install 的
    shyling
        82
    shyling   51 天前
    其实也很简单。。。npm 算是管理源码的,而 maven 这种是管理 jar 的。。。node 和 jvm 的区别,源码可以随便改,jar 没那么随便。js 复制到 node_modules 里就可以开始魔改了。。。(虽然发布时不建议)

    java 系列用 build 工具解决这些问题,build 的时候去改包名解决版本冲突,文件冲突。。node 系列放在了编码时。

    集中存放 jar 也会让空间一直膨胀,升级了依赖,删了一个项目之后 maven 可不知道旧的 jar 可以删掉了。。

    不过其实也没什么好不好的吧。。。又没啥解决不了的问题。
    shyling
        83
    shyling   51 天前
    @yannxia cargo 多版本的包 link 不也是会有问题
    yannxia
        84
    yannxia   51 天前
    @shyling 没有吧,A 依赖 X 的 0.1 版本和 B,B 依赖 X 的 0.2 版本。是没有干扰的,Maven 不爱处理这样的问题。至于代码要同时依赖 X 的 0.1 和 0.2,这解决不了,Java 靠 Classloader 能解决,Rust 倒是不知道怎么解决,不过这个场景用的倒是不多,上面那个很常见,所以现在 Maven 直接就搞 BOM。
    shyling
        85
    shyling   51 天前
    @yannxia 对,其实就是把类似 java 的 shade 把相同的 rename 了,a 用的 0.1 的 b 里的 Type 和 c 用的 0.2 的 b 里的 Type 不是一个
    azh7138m
        86
    azh7138m   51 天前
    如果是问支不支持,那是支持做链接的,全局保有一份,链接到项目里面。

    因为历史原因,构建工具的 symlinks 处理是存在问题的,如果你不介意 break change,也不介意自己处理,直接用 PnP 就行了。
    love
        87
    love   51 天前
    听起来你就是想节省硬盘?那你可以在工作区根目录安装所有你要用的包,工作区里所有的项目都可以直接用了
    biossun
        88
    biossun   51 天前
    @zhbhun 其实是可以的,只要模块加载器根据 package.json 中的版本声明并结合 package-lock.json 中的版本信息,就可以处理 A 和 B 模块分别依赖 C 模块的不同版本的问题。
    mgoann
        89
    mgoann   50 天前 via Android
    我也挺讨厌 node 的包管理机制的,重使使用了淘宝镜像很多包下载不下来,随便一个空 react 成千上万个小文件在 node modules 下,移动删除都不方便
    另外很多兄弟说为了加载不同版本的依赖包,实际情况这种需求我还真没遇到过
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1085 人在线   最高记录 5168   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 21:31 · PVG 05:31 · LAX 13:31 · JFK 16:31
    ♥ Do have faith in what you're doing.