V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
Ketteiron
0.2D
0.03D
V2EX  ›  程序员

发现一个自动优化 js/ts 桶文件的工具

  •  
  •   Ketteiron · 1 天前 · 966 次点击

    https://github.com/webpro/unbarrelify

    这个工具可以自动删除桶文件并更新正确路径,里面的文章也值得一读。 不过试用了一下在 windows 上路径处理有问题。

    桶文件是 js/ts 社区大范围错误使用的一种重新导出写法。

    你会发现很多开源项目都会大量使用 index.ts 然后重新导出子模块

    export * from './xx'
    

    这是错误的做法,但使用人太多了,大家都会以为大家都在用那么我也应该这样用,最后像病毒一样传染了整个生态。

    如果你的项目作为一个库发行,那么有使用桶文件的正当理由,但其他场景下应该尽量避免。

    这种写法的优点是可以在一行里 import 多个函数/类型,当然它实际上不是"优点",缺点是 ESM 打包优化困难、启动慢、热更新慢、CI 慢,调用不清晰,潜在的循环依赖。 人们以为随着工具链迭代这些缺陷都会慢慢消失,但遗憾的是,理论上不存在真正的解决办法,能够做的就是不断加缓存,但收效甚微。

    你使用重新导出写法去引用一个十万行模块里面的十行左右函数,打包器、开发服务器必须遍历完十万行代码才能分析好依赖关系把这十行函数打包进去。

    如果用代码说话,你不应该使用这样的写法

    import { a, type B } from '@/x'
    

    而是删掉你的 index.ts ,换成

    import { a } from '@/x/a'
    import type B from '@/x/b'
    

    第一天这么使用,你可能会觉得难以接受,但是忍耐几天后,你大概会接受这种写法,并且感受到在一个大型屎山项目中启动速度、打包速度、CI 检查速度的飞跃提升。如果你的项目桶文件足够多,这个提升很可能是几十倍。 当然工具链、CI 的速度只是其中一部分原因,更重要的是它消除了不必要的隐性封装,如果你直接依赖 x/a ,你就应该只 import x/a ,而非整个 x 。

    在发现这个工具之前,我一直使用 eslint 插件来避免创建任何桶文件,因此我用不上这个工具,但应该有不少人需要它,希望有一天能彻底终结 barrel-files 问题。

    19 条回复    2026-01-27 19:10:59 +08:00
    craftsmanship
        1
    craftsmanship  
       1 天前 via Android   ❤️ 1
    受教了 我也喜欢用 barrel files 以为是 JS 社区惯例 没想到是反模式…
    Ketteiron
        2
    Ketteiron  
    OP
       1 天前
    @craftsmanship #1 因为早期编辑器没有自动导入,为了省几个字符开发人员就想了个"巧妙"的方法少写点,现在的话没必要了,而且实际上并不会少写多少。

    社区里可能有一万个惯例实际上是反模式,从来如此不一定是对的。
    例如有些风格指导库会让只有一个导出时使用默认导出而非具名导出,例如 Airbnb ,在 ts 下这是错的,应该尽量避免默认导出,除非不得不用 (配置文件、i18n)。
    superhot
        3
    superhot  
       1 天前
    性能和循环依赖确实是个大问题,但个人倒是觉得通过 barrel modules 封装起一个模块的内部实现细节,使用起来很简洁,比方说一个模块重构调整路径,但对外接口保持不变,用了 barrel modules 的话,对使用者是零影响的,显式使用路径则需要更改所有导入的地方。
    superhot
        4
    superhot  
       1 天前
    突然想起来另一点,如果 barrel modules 影响性能的理由是无法有效 tree shaking ,那 namespace import 也同样有这个缺点…?
    Ketteiron
        5
    Ketteiron  
    OP
       1 天前   ❤️ 1
    @superhot #3 IDE 会自动更新路径,除了 PR 难看点也没什么影响。
    我曾经抱有相同的想法,模块内的改动不会影响到外部,这被称为封装性好。
    但反过来说,它隐藏了一定的信息量
    import {a} from '@/a'
    import {a} from '@/a/b/c'
    第二种写法是很长,但一眼就能看出这是第几层嵌套的模块,它可以培养开发者把文件当成模块的习惯,而不需要人为构造一个模块

    还有一个是统一写法的问题,如果 eslint 不进行限制,同样一个导入有 n 种写法
    import {a} from '@/a'
    import {a} from '@/a/b'
    import {a} from '@/a/b/c'
    如果统一禁止掉桶文件,只有一种写法。
    我能接受团队内的任何固有习惯,但我希望只能用一种习惯,不要给我第二种选择。
    Ketteiron
        6
    Ketteiron  
    OP
       1 天前   ❤️ 1
    @superhot #4 打包工具可以正确处理 namespace import ,例如 zod 推荐统一使用 import * as z form 'zod'
    barrel 的缺点不是 tree shaking ,只要没有副作用,打包器是能正常 tree shaking ,但是开发阶段的首次启动变慢了,热更新变慢,各种 CI 检查都会变慢。因为必须加载完全部代码全能分析出用到了哪些代码,但是如果全路径定位到文件本身,就不需要对 n 个模块的复杂关系进行非常耗时的分析,因为桶文件经常会引用桶文件,几乎要把全部项目几千个文件都分析完才能梳理出单个文件的依赖图关系,哪怕它的直接依赖只有几个文件,如果只分析引用的实际依赖,整体计算量会指数级下降。
    SanjinGG
        7
    SanjinGG  
       15 小时 19 分钟前 via Android
    我之前也是 export default 的,但问过 ai ,最后还是用 export ,然后 index.ts 统一 export 了,好处就是引入有自动补全,就一个引入文件
    Ketteiron
        8
    Ketteiron  
    OP
       13 小时 24 分钟前
    @SanjinGG #7 自动补全跟 index.ts 没有关系,而且 index.ts 也会减慢编辑器构建自动导入缓存的速度,项目越大越明显。
    DICK23
        9
    DICK23  
       13 小时 21 分钟前
    主要还是为了代码简洁,单一来源。不然写一堆 import xx from 'x/a'; import xb from 'x/b',实在太多了
    Jynxio
        10
    Jynxio  
       13 小时 9 分钟前
    1. Barrel Files 不是反模式
    2. 社区不推荐它,是因为 Bundler 的性能问题
    3. 拖慢 Bundler 的 Barrel Files 主要来自库,而非业务代码
    4. 业务代码的 Barrel Files 可能会引入更多的库,然后重复 3
    5. 如果明确是小项目,那么随便用 Barrel Files ,否则别用

    - - -

    另外,Barrel Files 最有用的地方是标明代码的访问权限 ———— 那些被重导出的代码才是共享的,其余都是私有的。这可以避免某个功能的私有代码被另一个功能调用。

    如果想实现这种效果,Barrel Files 是比较经济实惠的方案,而且 Bundler 对 Barrel Files 的优化还行,所以我才推荐「小项目随便用 Barrel Files 」。

    我现在的确不用 Barrel Files 了,因为想避免项目膨胀成大项目后,要返工撤销 Barrel Files 。我现在用下划线前缀(`_`)来区分私有代码和公有代码,比如 `_private.js` 是私有的,`public.js` 是公有的。
    TossPig
        11
    TossPig  
       13 小时 8 分钟前
    还是有点矫枉过正了,看了原文,也收集了相关资料给 AI 评估,AI 的结论和我的想法是一致的
    不能一概而论,日常使用 angular 作为基础框架,它里面就有各种黑魔法

    这段评论没有测试数据支持,也只能算空对空的讨论

    下面贴一段 AI 答复,和我结论是一致的,但比我表述的更清楚

    ```
    所以,我的核心问题,下面的表述到底对不对?
    ```

    ```
    如果用代码说话,你不应该使用这样的写法

    import { a, type B } from '@/x'
    而是删掉你的 index.ts ,换成
    import { a } from '@/x/a'
    import type B from '@/x/b'
    ```

    ```
    你的这段表述方向上正确,但不是“放之四海皆准”。更准确的说法是:
    - 在大型应用/仓库里:删掉 index.ts 、改成直达路径导入,通常更快、更清晰、依赖更可控。
    - 在库( library )对外 API 聚合场景:保留 index.ts 作为入口更合理(对外稳定 API 面)。
    - 在小项目或强缓存工具链下:差异可能不明显,但直达导入依然更透明。
    所以你的写法更像强烈建议,而不是“唯一正确”。建议改成:
    > “在应用代码中尽量避免 barrel ,改成直达导入;库的公共入口除外。”
    另外两个细节:
    - import type 也会参与模块解析,并不一定避免模块图膨胀。
    - 不同 bundler/配置表现不同,所以“必然更快”不严谨,但大项目里通常更快是成立的。
    如果你想,我可以把这段话润色成一段更严谨、可直接引用的版本。
    ```
    SanjinGG
        12
    SanjinGG  
       12 小时 2 分钟前 via Android
    @Ketteiron 如果 export default 的话,你导入任何名字都可以啊,除非你先写组件,然后靠 ide 的提示引入
    SanjinGG
        13
    SanjinGG  
       12 小时 0 分钟前 via Android
    @Ketteiron 需要 index 文件的也是因为组件还要细分组件才这样,如何单个文件就能实现,那完全不需要,直接 export default 就行了
    Ketteiron
        14
    Ketteiron  
    OP
       11 小时 7 分钟前
    @Jynxio #10
    > 标明代码的访问权限
    对于模块内,不 export 就等于私有
    对于 monorepo 可以使用 package.json 的 exports 来声明外部访问列表
    如果是想禁止跨模块引入,可以用 eslint
    https://github.com/javierbrea/eslint-plugin-boundaries

    重新导出的问题在于它只提供了一个大家约定好的入口,不能真正禁止外部引用,即使没有在入口导出,也可以全路径导入
    Ketteiron
        15
    Ketteiron  
    OP
       11 小时 0 分钟前
    @SanjinGG #13 我的建议是不要 export default ,使用具名导出 export { xxx },这样可以随时重构变量名,而默认导出做不到
    编辑器会收集全部 export 对象作为快速导入的候选列表,因此只要导出一次就够了,重复导出+默认导出一起用经常会遇到奇奇怪怪的 bug 。
    Ketteiron
        16
    Ketteiron  
    OP
       10 小时 48 分钟前
    @TossPig #11 看不出这段 AI 废话跟我说的哪里有冲突,表达的观点是一模一样的,库有使用索引文件的正当理由,其他场景应该尽量避免。
    所有 bundler 下都是必然更快,AI 总是会更保守谨慎,不敢下结论
    Ketteiron
        17
    Ketteiron  
    OP
       10 小时 21 分钟前
    @Jynxio #10 > 拖慢 Bundler 的 Barrel Files 主要来自库,而非业务代码
    如果是库,只有第一次预构建有开销,之后全部走缓存
    业务代码无法缓存,每一次变更都要重建依赖图
    Jynxio
        18
    Jynxio  
       5 小时 7 分钟前
    @Ketteiron

    赞同,约定确实是脆弱的。

    我意识到这个问题后,就编写了 ESLint 插件来把「约定」变成「规定」。

    谢谢你推荐的 ESLint 插件,我会仔细看它。
    Jynxio
        19
    Jynxio  
       5 小时 3 分钟前
    @Ketteiron

    🤔 这是有道理的,我也赞同。

    我再叠甲一下:我也不鼓励用 Barrel Files ,但一两万来行代码的项目可以不讲究。
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   Solana   ·   1883 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 16:14 · PVG 00:14 · LAX 08:14 · JFK 11:14
    ♥ Do have faith in what you're doing.