Arrêter d'utiliser des barel files
Cet article est une traduction de Please stop using barrel files
L'organisation « correcte » des fichiers est un sujet brûlant et controversé parmi les développeurs frontend. Déplacer les fichiers jusqu'à ce que cela semble correct est un mème commun, mais ce n'est pas non plus une blague selon l'auteur Dan Abramov. C'est probablement une bonne chose si vous travaillez seul, mais au sein d'une équipe, il est probable que des personnes différentes se sentiront à l'aise. Ces conseils sont donc assez subjectifs et ne sont donc pas vraiment utiles dans un environnement professionnel.
D'après mon expérience, la plupart des développeurs sont heureux de s'adapter à la structure qu'un projet utilise déjà. Avoir une base de code homogène et facile à comprendre - même si vous n'êtes pas d'accord avec une structure - est bien mieux que de voir chacun faire ce qu'il préfère.
J'ai mes préférences, et vous aussi (probablement). Je suis pour la cohérence, et éviter autant que possible les discussions veines. La règle des cas de licorne/filename est un très bon exemple à cet égard. Choisir un cas, l'appliquer partout, et en finir.
Cela dit, il y a une chose sur laquelle j'ai beaucoup insisté ces derniers temps, et c'est d'éviter à tout prix les fichiers tonneaux ou barrel files.
Qu'est ce qu'un barrel files ou fichier tonneau ?
Un barrel est un fichier qui ne fait rien d'autre que réexporter des éléments d'autres fichiers. En général, ces fichiers sont nommés index.js ou index.ts. Il est destiné à cacher la structure « interne » d'un répertoire à ses consommateurs. A titre d'exemple, considérons le répertoire suivant dans votre dépôt :
- tab
- tab-list.tsx
- tab-panel.tsx
Supposons maintenant que chaque fichier exporte un seul composant. Cela signifie que nous pouvons importer ces modules individuellement comme suit :
import { TabList } from '@/tab/tab-list'
import { TabPanel } from '@/tab/tab-panel'
Comme le fichier porte le nom d'index, qui peut être omis lors de l'importation, nous obtenons un site d'utilisation apparemment nettoyé :
import { TabList, TabPanel } from '@/tab'
Non seulement cela semble beaucoup plus propre, mais cela signifie également que nous pouvons déplacer des fichiers internes sans avoir à mettre à jour tous les côtés consommateurs. Où est le problème ?
Importations circulaires
Le fait d'avoir un fichier barrel dans un répertoire modifie la façon dont nous importons en fonction de notre emplacement actuel. Étendons notre exemple en ajoutant un util use-tab-state.ts
. Il s'agit d'un crochet personnalisé que nous allons utiliser à l'intérieur de notre composant TabPanel. De plus, nous voulons l'exporter pour que tout le monde puisse l'utiliser, c'est pourquoi nous l'ajoutons à notre fichier barrel :
export { TabList } from './tab-list'
export { TabPanel } from './tab-panel'
export { useTabState } from './use-tab-state'
Jusque là, tout va bien - les consommateurs peuvent importer useTabState
directement depuis le fichier barrel. Cependant, que se passe-t-il si nous sommes à l'intérieur de TabPanel
et que nous importons useTabState
? Cela dépend de la façon dont nous écrivons notre déclaration d'importation
// ✅ good: importations du module use-tab-state
import { useTabState } from './use-tab-state'
// ❌ bad: ils importent tous deux le fichier des tonneaux
import { useTabState } from '@/tab'
import { useTabState } from './'
Si nous faisons ce que nous faisons toujours - importer à partir du fichier barrel - nous créerons une importation circulaire, où tab-panel.ts importera index.ts, qui importera tab-panel.ts (parce qu'il le réexporte).
JavaScript est assez tolérant en ce qui concerne les importations circulaires, mais j'ai vu des bundlers se planter avec les messages d'erreur les plus bizarres à cause de cela. Ces importations peuvent également se produire accidentellement, car il faut bien l'admettre : la plupart du temps, nous nous contentons d'une importation automatique et nous la laissons à ce que notre éditeur (ou notre copilote) décide. En tout cas, je n'ai pas écrit de déclaration d'importation manuelle depuis longtemps.
La règle de lint import/no-cycle permet d'attraper beaucoup de ces cercles, donc je peux recommander de l'activer.
Vitesse de développement
Le deuxième problème avec les barrels apparaît lorsque nous réfléchissons à ce qui se passe sous le capot lorsqu'un fichier barrel est importé. Si nous faisons import { useTabState } from '@/tab'
, JavaScript va parcourir le fichier index.ts et charger tous les modules qu'il contient, de manière synchrone. Cela n'a probablement pas beaucoup d'importance si notre barrel ne contient que trois fichiers, mais cela peut vite devenir incontrôlable, par exemple, si l'un de nos autres imports dont nous n'avons pas besoin provient d'un autre barrel, ou d'une librairie tierce qui, elle aussi, importe de nombreux modules.
Dans notre projet NextJs, j'ai vu des pages qui chargeaient plus de 11k modules, ce qui prenait 5 à 10 secondes pour démarrer la page. Après avoir commencé à nous débarrasser de la plupart de nos fichiers barrel internes, nous avons réduit le nombre de modules à environ 3,5k, soit une réduction de 68%. Il s'avère que ce n'est pas si bon si vous avez un paquet partagé qui exporte une tonne de choses via un baril et que vous n'avez besoin que d'un seul module à partir de celui-ci. 😅
L'équipe NextJs a également réalisé que les fichiers barrel sont un vrai problème en mode développement et a commencé à livrer la fonctionnalité optimizePackageImports pour transformer automatiquement les importations de fichiers barrel en leurs chemins de modules réels. L'article de blog Comment nous avons optimisé les importations de paquets dans Next.js par Shu Ding explique en détail comment cela fonctionne. Le plus intéressant est que ces optimisations ne peuvent être appliquées que si le barrel est un « vrai » barrel - ce qui signifie qu'il ne fait rien d'autre qu'exporter d'autres choses. Dès que vous ajoutez une ligne qui n'est pas une réexportation, comme :
export { TabList } from './tab-list'
export { TabPanel } from './tab-panel'
export { useTabState } from './use-tab-state'
// ❌ bad: this makes the whole file non-optimizable
export const foo = 5
A quoi servent les barils ?
A mon avis, les barrels ne sont pas faits pour regrouper le contenu des répertoires dans votre application produit. À moins d'ajouter des règles de lint encore plus strictes, rien n'oblige les autres développeurs à n'importer que depuis les barrels, de sorte que vous ne pouvez pas rendre certains modules « privés » avec eux.
Les barils sont nécessaires lorsque vous écrivez une bibliothèque. Les bibliothèques comme @tanstack/react-query
ont besoin d'un fichier de point d'entrée unique que nous pouvons mettre dans le champ principal de package.json. C'est l'interface publique de ce que les consommateurs peuvent utiliser. Pour moi, c'est le seul endroit où un baril a du sens. Mais si vous écrivez le code de l'application, vous vous compliquez la vie en plaçant des fichiers index.ts dans des répertoires arbitraires. Cessez donc d'utiliser les fichiers barrel.
Photo par Vince Veras
Questions fréquentes
Qu'est-ce qu'un barrel file ?
Un barrel file est un fichier d'index qui permet de regrouper et d'exporter plusieurs modules depuis un seul fichier, simplifiant ainsi les imports dans d'autres parties du projet.
Pourquoi les barrel files sont-ils souvent utilisés ?
Les barrel files permettent de réduire le nombre d'importations explicites dans votre code. Au lieu d'importer des fichiers individuellement, vous pouvez tout regrouper sous une seule importation, ce qui est perçu comme une manière de simplifier l'organisation du projet.
Quels sont les inconvénients des barrel files ?
Bien qu'ils simplifient les imports à première vue, les barrel files peuvent causer des problèmes tels que des imports circulaires, un manque de visibilité sur les dépendances réelles, et peuvent alourdir le bundle final en incluant des modules inutilisés. Ils masquent souvent la hiérarchie du code, ce qui peut rendre la maintenance plus difficile.
Quelles sont les alternatives aux barrel files ?
Au lieu d'utiliser des barrel files, il est conseillé d'importer les modules directement là où ils sont nécessaires. Cela permet d'avoir des imports plus explicites et d'éviter des problèmes d'importation circulaire ou de dépendances non contrôlées.
Dans quel cas pourrait-on quand même utiliser des barrel files ?
Ils peuvent être utiles dans des cas où un projet est très grand et où la gestion manuelle des imports devient complexe, mais cela doit se faire en toute conscience des risques. Il est recommandé de bien structurer les fichiers pour éviter les effets indésirables.
Comment puis-je détecter des problèmes liés à l'utilisation de barrel files ?
Vous pouvez utiliser des outils d'analyse de dépendances ou des analyseurs statiques pour repérer les imports circulaires ou les modules inutiles qui augmentent le poids du bundle. Il est également conseillé de surveiller la taille de votre bundle après chaque modification.