Identifier les règles ESLint qui ralentissent votre linting
Repérez les règles ESLint lentes avec TIMING=1 et Config Inspector. Comparez les règles typées, ciblez les plugins coûteux et optimisez votre configuration.
Lorsqu’ESLint est lent, la cause se résume généralement à un petit nombre de règles : les règles typées (type-aware) de typescript-eslint, eslint-plugin-prettier, @stylistic/indent ou les règles inter-fichiers d’eslint-plugin-import, comme import/no-cycle. Vous pouvez les repérer grâce à TIMING=1 et au panneau de statistiques d’ESLint Config Inspector.
Un linting lent devient vite coûteux. Le hook de pre-commit prend tellement de temps que les développeurs se mettent à committer avec --no-verify, et à partir de là, la configuration de lint ne protège plus rien.
Ce guide explique comment mesurer le coût de chaque règle, quelles règles posent généralement problème et quelles modifications apporter à eslint.config.js dans chaque cas.
Points clés
- Avec le linting typé, une exécution complète d’ESLint devrait prendre à peu près autant de temps que
tscsur le même projet. Une exécution nettement plus longue révèle un problème lié à une règle précise ou à la configuration. TIMING=1 npx eslint .affiche les règles les plus lentes. La première règle typée paraît presque toujours la plus lente, car TypeScript met en cache les informations de type : testez donc les suspectes une par une.- ESLint Config Inspector 3.4.0 a ajouté une option
--statset un panneau de statistiques qui lance le chronométrage d’ESLint à la demande. Cette option est distincte de l’option--statspropre à ESLint. - La documentation de typescript-eslint recommande de n’exécuter
import/no-cycle,import/no-unused-modules,import/no-deprecatedetimport/no-named-as-defaultqu’en CI ou au moment du push, car TypeScript ne propose aucune vérification équivalente. eslint-plugin-prettieranalyse chaque fichier une seconde fois. Exécuterprettier --checkcomme étape à part entière donne le même résultat (succès ou échec).
Pourquoi ESLint est-il lent ?
La lenteur d’ESLint provient généralement de quelques règles coûteuses, et non de TypeScript lui-même. Avec le linting typé, une exécution complète d’ESLint devrait durer à peu près autant que la vérification de types. Selon le guide de performance de typescript-eslint, un ralentissement s’explique le plus souvent par des règles typées qui sollicitent le vérificateur de types de TypeScript. Commencez par mesurer votre référence :
npx tsc --noEmit
Si tsc est rapide et que le lint est beaucoup plus lent, c’est une règle ou un paramètre de configuration qui est en cause, et non TypeScript.
Comment vérifier le temps d’exécution des règles avec TIMING=1 ?
Définir la variable d’environnement TIMING amène ESLint à afficher les règles qui ont pris le plus de temps. Aucune installation n’est nécessaire :
TIMING=1 npx eslint .
Ce comportement est décrit dans la documentation d’ESLint, section Profile Rule Performance.
Interprétez la sortie avec prudence. TypeScript construit les informations de type lorsque la première règle typée les demande, puis les met en cache pour toutes les règles suivantes. Par conséquent, la première règle typée de l’exécution paraît presque toujours la plus lente, même lorsqu’elle est peu coûteuse.
Pour une comparaison équitable, chronométrez chaque règle typée lors d’une exécution distincte, en désactivant les autres. Une solution consiste à conserver une configuration allégée ne contenant qu’une seule règle suspecte et à la transmettre à ESLint via -c/--config :
TIMING=1 npx eslint -c eslint.timing.config.js .
Visualiser les temps d’exécution dans ESLint Config Inspector
ESLint Config Inspector propose une vue de chronométrage. D’après ses notes de version, la version 3.4.0 a doté les commandes dev et build d’une option --stats et ajouté un panneau de statistiques qui lance le chronométrage d’ESLint à la demande. La vue de chronométrage nécessite donc Config Inspector 3.4 ou une version ultérieure. Utilisez la version 3.x la plus récente, car la 3.4.1 a corrigé un plantage de build --stats qui survenait lorsqu’ESLint renvoyait un passage sans temps d’analyse syntaxique (issue #333).
Cette option --stats appartient à Config Inspector. ESLint possède sa propre option --stats, qui ajoute des données de chronométrage au résultat de lint de chaque fichier afin qu’un formateur puisse les afficher. Elle n’ouvre aucun panneau.
Exécutez ces commandes dans le dossier qui contient votre eslint.config.js :
# List the options available in your installed version
pnpx @eslint/config-inspector --help
# Static snapshot with timing data, written to dist/__eslint-config-inspector
pnpx @eslint/config-inspector build --stats
La simple commande pnpx @eslint/config-inspector lance l’inspecteur sur http://localhost:7777. Passez --stats à la commande dev (--help indique la syntaxe exacte), ouvrez le panneau de statistiques et repérez les règles et plugins qui concentrent l’essentiel du temps. La mise en garde concernant la première règle typée s’applique également à cette vue. Le build statique étant un instantané, vous pouvez en conserver un avant une modification de configuration et le comparer à un autre réalisé après.
Quelles règles ESLint sont généralement les plus lentes ?
Les quatre mêmes groupes de règles ESLint reviennent sans cesse en tête des résultats de chronométrage :
- Les règles typées. Les presets tels que
recommendedTypeCheckedajoutent des règles qui dépendent du vérificateur de types. Leur mise en place est détaillée dans l’article linter du TypeScript avec ESLint. eslint-plugin-prettier. Le plugin ne peut détecter un écart de formatage qu’en exécutant Prettier sur l’ensemble du fichier : chaque fichier linté est donc analysé une première fois par ESLint, puis une seconde fois par Prettier. L’article ESLint et Prettier explique comment les deux outils se répartissent le travail.@stylistic/indent. Cette règle calcule l’indentation de chaque token dans chaque fichier, et sur une base de code volumineuse, ce travail s’accumule.- Les règles d’
eslint-plugin-import, en particulierimport/no-cycle. Ces règles obligent le plugin à analyser les fichiers et à suivre les imports lui-même, indépendamment d’ESLint, si bien que leur coût augmente avec la taille de la base de code. Consultez les plugins ESLint qui améliorent la qualité du code pour découvrir ce que détecte ce plugin.
Comment corriger chaque règle lente
Chaque groupe de règles ESLint lentes a sa propre solution. La plupart des recommandations ci-dessous proviennent du guide de performance de typescript-eslint :
| Coupable | Cause de la lenteur | Solution |
|---|---|---|
| Règles typées | Font appel au vérificateur de types | Les limiter aux fichiers source via files et restreindre le include du tsconfig |
eslint-plugin-prettier | Analyse chaque fichier une seconde fois | Le supprimer et exécuter prettier --check . |
@stylistic/indent | Traite chaque token | La supprimer et laisser Prettier gérer l’indentation |
import/named, import/namespace, import/default, import/no-named-as-default-member, import/no-unresolved | Font doublon avec les vérifications de TypeScript | Les désactiver (no-unresolved uniquement si le code utilise import et non require) |
import/no-cycle, import/no-unused-modules, import/no-deprecated, import/no-named-as-default | Analyse inter-fichiers sans équivalent TypeScript | Ne les exécuter qu’en CI ou au moment du push |
Vérifiez également le tsconfig. Un tsconfig sans include, ou avec un glob tel que **/*, amène le parser à pré-analyser bien plus de fichiers que vous ne souhaitiez linter, y compris les fichiers de build. Limitez include aux dossiers que vous voulez réellement linter :
{ "compilerOptions": { /* ... */ }, "include": ["src"] }
Utilisez l’option --check de Prettier comme étape de CI distincte, à la place du plugin. Pour les exécutions locales répétées, l’option --cache d’ESLint ignore les fichiers qui n’ont pas changé depuis la dernière exécution.
Exemple : avant et après
La configuration « avant » ci-dessous exécute tout sur chaque fichier :
// @ts-check
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
import importPlugin from 'eslint-plugin-import';
import stylistic from '@stylistic/eslint-plugin';
import prettierPlugin from 'eslint-plugin-prettier';
export default defineConfig({
files: ['**/*.{js,ts}'],
extends: [tseslint.configs.recommendedTypeChecked],
languageOptions: {
parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname },
},
plugins: { import: importPlugin, '@stylistic': stylistic, prettier: prettierPlugin },
rules: {
'prettier/prettier': 'error',
'@stylistic/indent': ['error', 2],
'import/named': 'error',
'import/no-unresolved': 'error',
'import/no-cycle': 'error',
},
});
La configuration « après » limite les règles typées à src, supprime les plugins de formatage ainsi que les règles d’import déjà couvertes par TypeScript, et n’active import/no-cycle que lorsqu’une variable d’environnement est définie :
// @ts-check
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
import importPlugin from 'eslint-plugin-import';
const full = process.env.LINT_FULL === '1';
export default defineConfig(
{
files: ['src/**/*.ts'],
extends: [tseslint.configs.recommendedTypeChecked],
languageOptions: {
parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname },
},
},
{
files: ['src/**/*.ts'],
plugins: { import: importPlugin },
rules: {
...(full ? { 'import/no-cycle': 'error' } : {}),
},
},
);
Avec cette configuration, le hook de pre-commit exécute npx eslint ., tandis que la CI exécute LINT_FULL=1 npx eslint . et npx prettier --check ..
Le tableau ci-dessous illustre, à titre d’exemple, l’évolution de la liste TIMING=1 après ce changement de configuration. Il ne s’agit pas de résultats mesurés :
| Règle | Avant | Après |
|---|---|---|
prettier/prettier | En tête de liste | Disparue (remplacée par prettier --check) |
@stylistic/indent | En tête de liste | Disparue |
import/no-cycle | En tête de liste | N’apparaît que lors des exécutions LINT_FULL=1 |
import/named, import/no-unresolved | Présentes | Disparues |
| Première règle typée | Indiquée comme la plus lente | Toujours en tête ; à comparer avec le temps de tsc |
Conclusion
Mesurez avant de supprimer quoi que ce soit. Commencez par établir une référence avec tsc, puis exécutez TIMING=1 et le panneau de statistiques de Config Inspector, et chronométrez les règles typées suspectes une par une. Une fois les règles les plus coûteuses identifiées, réservez le linting local et de pre-commit aux règles rapides portant sur un seul fichier, et déplacez les vérifications inter-fichiers et le formatage vers la CI. Lancez dès maintenant TIMING=1 npx eslint . sur votre projet et appliquez le tableau de solutions aux règles qui arrivent en tête.
FAQ
Comment afficher plus de 10 règles dans la sortie TIMING d'ESLint ?
Attribuez à TIMING un nombre plus élevé ou la valeur all. TIMING=1 affiche les dix règles les plus longues à s'exécuter, TIMING=50 en affiche cinquante et TIMING=all les affiche toutes. Chaque ligne indique le temps de la règle en millisecondes (création de la règle plus exécution) et sa part dans le temps total consacré aux règles. Pour chronométrer une seule règle, combinez --no-config-lookup avec --rule. Cette méthode convient aux règles natives qui ne nécessitent aucune configuration de parser.
L'option --cache d'ESLint est-elle fiable avec les règles typées ?
Pas entièrement. Le cache d'ESLint vérifie uniquement si un fichier ou la configuration a changé. Il ignore les modifications apportées aux autres fichiers dont dépend un fichier. Les règles typées de typescript-eslint et les règles de résolution d'imports d'eslint-plugin-import peuvent donc renvoyer des résultats obsolètes dans les deux sens : elles peuvent manquer un nouveau problème ou continuer à signaler un problème déjà corrigé. La FAQ de typescript-eslint déconseille --cache : exécutez donc la CI sans cette option.
Comment obtenir d'ESLint des temps d'exécution par fichier et par règle ?
Exécutez ESLint avec sa propre option --stats et un formateur qui restitue les données de statistiques, par exemple npx eslint --stats -f json. Chaque objet de résultat reçoit une clé stats contenant les temps d'analyse syntaxique, les temps de correction et les temps de lint par règle pour ce fichier, alors que TIMING se contente de résumer l'exécution globale. Tous les formateurs ne restituent pas les données de statistiques. La mise en garde concernant la mise en cache par la première règle typée s'applique également à --stats.
parserOptions.projectService rend-il le linting typé plus rapide que parserOptions.project ?
Pas de manière fiable. typescript-eslint s'attendait à ce que le Project Service, disponible depuis la v8.0.0, soit plus rapide, et indique traiter l'absence de gain de vitesse comme un bug. Son principal avantage réside dans une configuration simplifiée : vous n'avez plus besoin des globs de parserOptions.project, dans lesquels les motifs récursifs ** génèrent des E/S disque supplémentaires. Limitez au maximum allowDefaultProject, car chaque fichier qu'il cible ralentit sensiblement le linting ; par défaut, il ne peut cibler que 8 fichiers au plus.