Small decisions can also have big impacts on applications. When developers start using barrel files they might seem like a harmless pattern, but they aren't.
[UPDATE Mar, 23th of 2024]:
BEFORE YOU KEEP READING: this article and its metrics were created thinking on final front-end applications. Those that final users navigate such as dev.to and my source project. So, if you have been using Barrel files in any other context, such as packages, it (Barrel files) might fit you as expected. Take a look at this amazing comment from
@kasper573.
What are barrel files?
Barrel files are an easy exportation pattern that allows developers to import different files from a unique path.
Let's see an example:
// ./barrelFiles/used.ts
export const HEY = 'hey used';
// ./barrelFiles/notUsed.ts
export const HEY_NOT_USED = 'hey NOT used';
// ./barrelFiles/index.ts
export { HEY_NOT_USED } from './notUsed';
export { HEY } from './used';
/*
This is a barrel file, which will be used as a unique path to import any files into barrelFiles folder (such as ./used).
*/
Using it:
// ./Component.tsx
import { HEY } from './barrelFiles'; // instead of use './barrelFiles/used'
In the first touch, that looks amazing, doesn't it? They are in terms of Dev Experience, but it costs a lot!
What is the Barrel files cost into the app bundle?
One crucial question you must keep in mind about barrel files: What is imported when I use something from them? Does it import only what I use?
Let's see it with examples. Right, Let's add two more files to our barrel file and add some console.log to see how they work:
// now there are 4 files in barrelFiles folder
// ./barrelFiles/used.ts
export const HEY = 'hey used';
console.log('%c Im used xD', 'color: green;');
// ./barrelFiles/notUsed.ts
export const HEY_NOT_USED = 'hey NOT used';
console.log('%c WAIT! Im NOT used', 'color: red');
// ./barrelFiles/notUsed1.ts
export const HEY_NOT_USED_1 = 'hey NOT used 1';
console.log('%c WAIT! Im NOT used 1', 'color: red');
// ./barrelFiles/notUsed2.ts
export const HEY_NOT_USED_2 = 'hey NOT used 2';
console.log('%c WAIT! Im NOT used 2', 'color: red');
And the barrel file:
// ./barrelFiles/index.ts
export { HEY_NOT_USED } from './notUsed';
export { HEY_NOT_USED_2 } from './notUsed2';
export { HEY_NOT_USED_1 } from './notUsed1';
export { HEY } from './used';
Well, getting back to the questions, it's expected if I import HEY, I only import ./used file content. So let's do this by importing it into my personal project where you find more articles like this:
// ./Component.tsx
import { HEY } from './barrelFiles'; // I wanna use HEY, only
Do you see? Even though only HEY is used, it shows all console.log which means all files are imported.
Now, not importing from the barrel file.
// ./Component.tsx
import { HEY } from './barrelFiles/used'; // I wanna use HEY, only
Importing directly one console.log is shown.
You might be wondering: why would there be files not used in my project?
That's a good question. Well, some points about it:
- In this example, there are few files, but how about a huge project using dozen barrel files? Likely there would be not used files. If you work on a monorepo, it might have shared folders used for many apps. Creating patterns that avoid team-mattes (and you) doing things wrong is the best thing you can do as a developer;
- Even though tools such as Vite and Webpack optimizing bundle and avoid to use re-import chunks, the better way to make sure you opmitizing as much as possible is understanding things work on the application. To understand it, see the next screenshots.
Let's see the bundle difference now (please, as it's a teaching example, the difference is short. Scale it up to understand the impact). See this comment with real numbers
Using the barrel file (189,83kb):
As you can see, there is a final bundle size difference. By not using barrel file, it's smaller.
Circular dependency is another problem with barrel files.
Enjoying it? If so, don't forget to give a ❤️_ and follow me to keep updated. Then, I'll continue creating more content like this_
What is the Barrel files cost for the unit tests?
The barrel file on unit tests is even worse, once unit tests engines such as Vitest and Jest don't care about bundle optimization as Vite and Webpack. Let's see by creating two helpers that only use HEY.
// ./helper.ts
import { HEY } from './barrelFiles';
export const helper = () => {
console.log(HEY);
};
// ./helper2.ts
import { HEY } from './barrelFiles';
export const helper2 = () => {
console.log(HEY);
};
And now import them into the test files
// ./helper.epec.ts
import { describe, it } from 'vitest';
import { helper } from './helper';
describe('helper', () => {
it('Should only use HEY', () => {
helper();
});
});
// ./helper2.epec.ts
import { describe, it } from 'vitest';
import { helper2 } from './helper2';
describe('helper2', () => {
it('Should only use HEY', () => {
helper2();
});
});
using barrel file on tests time: 2.61s
As you can see, there is a bunch of not-used console.log, and that gonna happen for EACH test file on the project that uses the barrel file or even use a file that uses the barrel file. Can you imagine the performance impact of it? Well... terrible.
Now, removing the barrel files
not using barrel file on tests time: 1.32s
Buy me a coffee ☕. Hope I have helped you somehow. 🤗
BONUS (Script to change import statements)
[UPDATE Jul, 4th of 2024]:
Check out this script to change import statements automatically. It will help you to remove barrel files.
[UPDATE Jul, 17th of 2024]:
And also, check out the Eslint plugin eslint-plugin-no-barrel-files to avoid, well, barrel files.
Conclusion
[UPDATE Mar, 23th of 2024]:
In this article, you learned more about the barrel file pattern and its impact on final front-end applications.
As I have said before: creating patterns that avoid team-mattes (and you) doing things wrong is the best thing you can do as a developer. That is valid to barrel files once not using them:
- Reducing the final bundle and avoiding importing stuff are not used;
- Improve the unit test performance;
Expensive.
Top comments (13)
Hace unos meses cuando descubrí este patrón, me pareció genial la forma en la que da una mejora claridad al leer los imports, pero usando react router 6 y un sistema de autenticación, me di cuenta de estos problemas que nombras, ya que la app no funcionaba como yo esperaba, debito a esto.
Gracias por compartir, Hernan! Feliz por clarar 🚀
Acabo de llegar hasta aquí por esto mismo. Estoy refactorizando mi router y no podía entender que, a pesar de estar usando Lazy imports, habían muchos componentes siendo cargados de igual manera.
wow... that's fascinating. in hindsight, it makes perfect sense, but I honestly never thought about this.
I just tested this with an Angular project, created a production build as I had it (using barrel files), then went through and removed them, adjusting my imports accordingly, and the production build I made afterwards is substantially smaller! like..
752.86 kB
before down to186.11 kB
afterwards! honestly did not expect that much of a difference.my work laptop is horrifically underpowered, to the point that it doesn't even handle ESlint checking things in the background while I code.. it seems like Biome doesn't kill it as badly though, so I'm definitely going to tell it to throw things at me if I try use barrel files again!
thanks for sharing this.. :)
quick follow up here: the e18e website "Resources" page conveniently provides a direct link to Biome's
noBarrelFile
lint rule.Woow!! What an extraordinary result, @zalithka!! 😮 We are also getting nice results where I have been working.
How about the unit tests, have you noticed any improvement?
Thanks for sharing your amazing results and Biome (I have heard about it before, I will explore)!
This is not necessarily true. You can have side effects free packages, which allow bundlers to properly tree shake packages that use barrel files: webpack.js.org/guides/tree-shaking...
Hey, man! Thanks for your comment!
have you validated it? To be honest, I tried it a long time ago and it didn't work as expected. It might be my mistake... Anyway, I would not use Barrel Files due to its test impact. I don't believe Barrel files have so much value for this risk.
Yes, I use side effects free packages every day. Third party and my own packages. Three.js and ramda on npm are both side effects free to name a few popular ones.
For tests I definitely agree you shouldn’t be importing via barrel files. A unit test should depend on only its unit, regardless of tree shaking capabilities. I almost exclusively only use barrel files as a definition for what my packages export. Everything else I split up into separate modules so that my codebase can have a proper dependency tree.
I think your article demonstrates important concerns. If one wants a no-build architecture, then being aware of how modules work is very useful, and avoiding barrel files becomes more important. But if one has a build system already then you do have the option to tree shake. I don’t think it’s wise to pass judgement on a whole and say it’s either right or wrong. There are upsides and downsides, and everything depends on context of the team and project.
Nice!! Thanks for the amazing explanation, man!
I will update my article to explain the context. Btw, do you know a nice article about no-build architecture? It is a new definition to me.
Another thing: if you want to create a topic regarding build system (on the barrel files context), I would love to put it in my article and give you the whole credit or if you have an article already, I can add the link here.
Please, find me on LinkedIn to talk if want to. Have a nice day!
linkedin.com/in/tassio-front-end/
The post’s title advocates for moving away from barrel files, but it's essential to recognize that their use can still be beneficial depending on the context. Barrel files can be particularly effective for single imports, allowing developers to streamline their code by simplifying import paths. Instead of referencing long or complex file paths for individual components, developers can use a barrel file to create a cleaner, more manageable import structure.
Additionally, when a project requires importing multiple related components at once, barrel files provide a practical solution. For instance, if you have several components grouped together, such as a set of page components that need to be included in a router, using a barrel file can facilitate a more organized import process. This approach not only reduces clutter in your import statements but also improves overall code readability.
Ultimately, barrel files remain a viable option for developers. When used for single imports or when needing to import a collection of related components, they can enhance the clarity and maintainability of the codebase. It's important for developers to assess their specific needs and determine when the use of barrel files can provide the most value.
Sounds like it's in the context of a web app source and not libraries. In libraries, I think it's okay. Actually, how else would it be? you always have an entry file that exports everything.
Exactly, @adi518, the focus here is final projects as said in the BEFORE YOU KEEP READING section. Although I also got diff numbers on Pako lib example, so I prefer avoid to import from entry points files when possible (but again, final projects).