前言
近期在管理 Monorepo 当中的文件时才发现一个有趣的输出管理模式:桶文件(Barrel File),因此研究一下这种方法的理念与优缺点以架构更好的项目。
什么是桶文件(Barrel File)模式?
桶文件在 TypeScript 或 JavaScript 当中是指群组化多个输出的模块于单一文件当中,方便有个统一的接口去引入。
举例来说有三个模块(moduleA.js
、moduleB.js
、moduleC.js
),想要运用桶文件的方式统一管理它们,就可以额外创建一个 index.js
并在里面引入这三个模块,然后再将 index.js
当作这三个模块的接口。
export { default as moduleA } from './moduleA.js';export { default as moduleB } from './moduleB.js';export { default as moduleC } from './moduleC.js';
于是可以直接引入这个桶文件来一次性取得以上三个模块:
import { value1, value2, value3 } from './foobar';
这样的模式好处显而易见,可以有效的管理模块的输出,增进模块引入的可读性,打造干净一致的接口供外部使用,特别是在 Icon 库或组件库都很常见到这样的模式。
潜在问题
- 开发体验疑虑:桶文件可能会让开发者没办法清楚的直接看见模块的来源,特别是在大型项目有多层次的桶文件时。并不是所有开发环境都支持轻松跳转到引入文件的来源,多层次的输出引入可能造成困扰。
- 构建与分析开销:如果使用的 bundler 不支持或没有正确设置 Tree Shake(移除没用到的代码),所有没有真正用到的模块仍会被打包进去,增加大量的无用代码。就算有支持的 bundler 也会需要分析桶文件的结构,才能正确移除没用到的模块。
- 测试与 Linter 开销:测试会如果每次访问通过桶文件访问模块会需要加载每个模块,增加测试的时间。import 检测相关的 Linter 也会因为桶文件而增加检测的复杂度。
总结
有的人认为桶文件是个 Anti-Pattern 没有任何好处,但也有人认为这么做可以更好的组织模块、统一接口并增进可读性。
我的想法是如果你的 Bundler 是强大支持 Tree Shake 并且团队成员都认可这种模式,那么桶文件是个可行的选项。举例:Vercel 有写一篇文章描述 Next 如何克服桶文件造成的问题:How we optimized package imports in Next.js - Vercel
我之所以调查这个主题,是因为目前所在的团队大量使用这种模式,但 Vite 并不会在开发中进行 Tree Shake 导致每次开发环境都有大量无用代码被打包,严重影响开发体验。
延伸阅读
- Barrel files in JavaScript - flaming.codes
- Why you should avoid Barrel Files in JavaScript Modules? - Bart
- Please Stop Using Barrel Files - TkDodo’s blog