
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 问题。
1 craftsmanship 17 小时 44 分钟前 via Android 受教了 我也喜欢用 barrel files 以为是 JS 社区惯例 没想到是反模式… |
2 Ketteiron OP @craftsmanship #1 因为早期编辑器没有自动导入,为了省几个字符开发人员就想了个"巧妙"的方法少写点,现在的话没必要了,而且实际上并不会少写多少。 社区里可能有一万个惯例实际上是反模式,从来如此不一定是对的。 例如有些风格指导库会让只有一个导出时使用默认导出而非具名导出,例如 Airbnb ,在 ts 下这是错的,应该尽量避免默认导出,除非不得不用 (配置文件、i18n)。 |
3 superhot 17 小时 2 分钟前 性能和循环依赖确实是个大问题,但个人倒是觉得通过 barrel modules 封装起一个模块的内部实现细节,使用起来很简洁,比方说一个模块重构调整路径,但对外接口保持不变,用了 barrel modules 的话,对使用者是零影响的,显式使用路径则需要更改所有导入的地方。 |
4 superhot 16 小时 58 分钟前 突然想起来另一点,如果 barrel modules 影响性能的理由是无法有效 tree shaking ,那 namespace import 也同样有这个缺点…? |
5 Ketteiron OP @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' 如果统一禁止掉桶文件,只有一种写法。 我能接受团队内的任何固有习惯,但我希望只能用一种习惯,不要给我第二种选择。 |
6 Ketteiron OP @superhot #4 打包工具可以正确处理 namespace import ,例如 zod 推荐统一使用 import * as z form 'zod' barrel 的缺点不是 tree shaking ,只要没有副作用,打包器是能正常 tree shaking ,但是开发阶段的首次启动变慢了,热更新变慢,各种 CI 检查都会变慢。因为必须加载完全部代码全能分析出用到了哪些代码,但是如果全路径定位到文件本身,就不需要对 n 个模块的复杂关系进行非常耗时的分析,因为桶文件经常会引用桶文件,几乎要把全部项目几千个文件都分析完才能梳理出单个文件的依赖图关系,哪怕它的直接依赖只有几个文件,如果只分析引用的实际依赖,整体计算量会指数级下降。 |
7 SanjinGG 3 小时 12 分钟前 via Android 我之前也是 export default 的,但问过 ai ,最后还是用 export ,然后 index.ts 统一 export 了,好处就是引入有自动补全,就一个引入文件 |
9 DICK23 1 小时 14 分钟前 主要还是为了代码简洁,单一来源。不然写一堆 import xx from 'x/a'; import xb from 'x/b',实在太多了 |
10 Jynxio 1 小时 3 分钟前 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` 是公有的。 |
11 TossPig 1 小时 2 分钟前 还是有点矫枉过正了,看了原文,也收集了相关资料给 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/配置表现不同,所以“必然更快”不严谨,但大项目里通常更快是成立的。 如果你想,我可以把这段话润色成一段更严谨、可直接引用的版本。 ``` |