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