Die ESLint-Regeln finden, die Ihr Linting ausbremsen
Finden Sie langsame ESLint-Regeln mit TIMING=1 und Config Inspector. Vergleichen Sie typisierte Regeln, erkennen Sie kostspielige Plugins und beschleunigen Sie das Linting.
Wenn ESLint langsam ist, liegt das meist an wenigen Regeln: typbasierte typescript-eslint-Regeln, eslint-plugin-prettier, @stylistic/indent oder dateiübergreifende eslint-plugin-import-Regeln wie import/no-cycle. Mit TIMING=1 und dem Stats-Panel des ESLint Config Inspector lassen sie sich aufspüren.
Ein langsamer Lint-Lauf wird schnell teuer. Der Pre-Commit-Hook dauert so lange, dass das Team anfängt, mit --no-verify zu committen, und ab diesem Punkt schützt die Lint-Konfiguration gar nichts mehr.
Dieser Leitfaden zeigt, wie Sie die Kosten jeder einzelnen Regel messen, welche Regeln sich typischerweise als Problem herausstellen und was Sie in der eslint.config.js jeweils ändern sollten.
Das Wichtigste in Kürze
- Mit typbasiertem Linting sollte ein vollständiger ESLint-Lauf etwa so lange dauern wie
tscfür dasselbe Projekt. Dauert er deutlich länger, deutet das auf eine bestimmte Regel oder ein Konfigurationsproblem hin. TIMING=1 npx eslint .gibt die langsamsten Regeln aus. Die erste typbasierte Regel erscheint fast immer als die langsamste, weil TypeScript Typinformationen cacht. Testen Sie Verdächtige daher einzeln.- ESLint Config Inspector 3.4.0 hat ein
--stats-Flag und ein Stats-Panel eingeführt, das ESLint-Timings auf Anforderung ausführt. Dieses Flag ist nicht identisch mit dem--stats-Flag von ESLint selbst. - Die typescript-eslint-Dokumentation empfiehlt,
import/no-cycle,import/no-unused-modules,import/no-deprecatedundimport/no-named-as-defaultnur in der CI oder beim Push auszuführen, da TypeScript keine entsprechende Prüfung bietet. eslint-plugin-prettierparst jede Datei ein zweites Mal. Ein separater Schritt mitprettier --checkliefert dasselbe Bestanden/Nicht-bestanden-Ergebnis.
Warum ist ESLint langsam?
ESLint ist in der Regel wegen einiger weniger aufwendiger Regeln langsam, nicht wegen TypeScript an sich. Mit typbasiertem Linting sollte ein vollständiger ESLint-Lauf ungefähr so lange dauern wie die Typprüfung. Laut dem Performance-Leitfaden von typescript-eslint ist die übliche Ursache für Verlangsamungen, dass typbasierte Regeln Informationen vom Type Checker von TypeScript abfragen. Messen Sie zuerst Ihre Ausgangsbasis:
npx tsc --noEmit
Wenn tsc schnell ist und das Linting deutlich langsamer, liegt es an einer Regel oder einer Konfigurationseinstellung, nicht an TypeScript selbst.
Wie prüft man das Timing der Regeln mit TIMING=1?
Wenn Sie die Umgebungsvariable TIMING setzen, gibt ESLint die Regeln aus, die am meisten Zeit benötigt haben. Sie müssen dafür nichts installieren:
TIMING=1 npx eslint .
Dieses Verhalten ist in der ESLint-Dokumentation unter Profile Rule Performance beschrieben.
Interpretieren Sie die Ausgabe mit Vorsicht. TypeScript erzeugt die Typinformationen, sobald die erste typbasierte Regel sie anfordert, und cacht sie für alle nachfolgenden Regeln. Deshalb erscheint die erste typbasierte Regel im Lauf fast immer als die langsamste, selbst wenn sie eigentlich günstig ist.
Für einen fairen Vergleich sollten Sie jede typbasierte Regel in einem eigenen Lauf messen, bei dem die anderen deaktiviert sind. Eine Möglichkeit ist, eine abgespeckte Konfiguration mit nur einer verdächtigen Regel anzulegen und ESLint per -c/--config darauf zu verweisen:
TIMING=1 npx eslint -c eslint.timing.config.js .
Timing im ESLint Config Inspector anzeigen
Der ESLint Config Inspector enthält eine Timing-Ansicht. Laut den Release Notes hat Version 3.4.0 den Befehlen dev und build ein --stats-Flag hinzugefügt und ein Stats-Panel eingeführt, das ESLint-Timings auf Anforderung ausführt. Die Timing-Ansicht setzt also Config Inspector 3.4 oder neuer voraus. Verwenden Sie die neueste 3.x-Version, denn 3.4.1 hat einen Absturz von build --stats behoben, der auftrat, wenn ESLint einen Durchlauf ohne Parse-Timing meldete (Issue #333).
Dieses --stats gehört zum Config Inspector. ESLint hat ein eigenes --stats-Flag, das Timing-Werte in das Lint-Ergebnis jeder Datei schreibt, damit ein Formatter sie anzeigen kann. Es öffnet kein Panel.
Führen Sie die folgenden Befehle in dem Verzeichnis aus, in dem Ihre eslint.config.js liegt:
# 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
Ein einfaches pnpx @eslint/config-inspector startet den Inspector unter http://localhost:7777. Übergeben Sie dem dev-Befehl --stats (die genaue Syntax zeigt --help), öffnen Sie das Stats-Panel und suchen Sie nach den Regeln und Plugins, die den Großteil der Zeit beanspruchen. Der Vorbehalt bezüglich der ersten typbasierten Regel gilt auch in dieser Ansicht. Der statische Build ist ein Snapshot, sodass Sie einen Stand vor einer Konfigurationsänderung aufbewahren und mit einem danach erstellten vergleichen können.
Welche ESLint-Regeln sind meist die langsamsten?
Immer wieder landen dieselben vier Gruppen von ESLint-Regeln ganz oben in der Timing-Ausgabe:
- Typbasierte Regeln. Presets wie
recommendedTypeCheckedfügen Regeln hinzu, die auf den Type Checker angewiesen sind. Die Einrichtung wird in TypeScript mit ESLint linten behandelt. eslint-plugin-prettier. Das Plugin kann Formatierungsabweichungen nur erkennen, indem es Prettier über die gesamte Datei laufen lässt. Jede gelintete Datei wird also einmal von ESLint und ein weiteres Mal von Prettier geparst. Der Beitrag ESLint und Prettier erklärt, wie sich die beiden Tools die Arbeit aufteilen.@stylistic/indent. Die Regel berechnet die Einrückung für jedes Token in jeder Datei, und in einer großen Codebasis summiert sich dieser Aufwand.eslint-plugin-import-Regeln, insbesondereimport/no-cycle. Diese Regeln veranlassen das Plugin, Dateien selbst zu parsen und Imports eigenständig zu verfolgen, unabhängig von ESLint. Der Aufwand wächst daher mit der Größe der Codebasis. Was das Plugin erkennt, lesen Sie in ESLint-Plugins, die die Codequalität verbessern.
So beheben Sie jede langsame Regel
Für jede Gruppe langsamer ESLint-Regeln gibt es eine eigene Lösung. Die meisten der folgenden Empfehlungen stammen aus dem Performance-Leitfaden von typescript-eslint:
| Verursacher | Warum langsam | Lösung |
|---|---|---|
| Typbasierte Regeln | Rufen den Type Checker auf | Mit files auf Quelldateien beschränken und include in der tsconfig eingrenzen |
eslint-plugin-prettier | Parst jede Datei ein zweites Mal | Entfernen und prettier --check . ausführen |
@stylistic/indent | Verarbeitet jedes einzelne Token | Entfernen und die Einrückung Prettier überlassen |
import/named, import/namespace, import/default, import/no-named-as-default-member, import/no-unresolved | Duplizieren Prüfungen von TypeScript | Deaktivieren (no-unresolved nur, wenn der Code import und nicht require verwendet) |
import/no-cycle, import/no-unused-modules, import/no-deprecated, import/no-named-as-default | Dateiübergreifende Analyse ohne TypeScript-Äquivalent | Nur in der CI oder beim Push ausführen |
Prüfen Sie auch die tsconfig. Eine tsconfig ohne include oder mit einem Glob wie **/* führt dazu, dass der Parser weit mehr Dateien vorab parst, als Sie linten wollten, einschließlich Build-Ausgaben. Beschränken Sie include auf die Verzeichnisse, die tatsächlich gelintet werden sollen:
{ "compilerOptions": { /* ... */ }, "include": ["src"] }
Verwenden Sie statt des Plugins Prettiers --check als separaten CI-Schritt. Für wiederholte lokale Läufe überspringt das ESLint-Flag --cache Dateien, die sich seit dem letzten Lauf nicht geändert haben.
Beispiel: Vorher und nachher
Die folgende Vorher-Konfiguration führt alles auf jeder Datei aus:
// @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',
},
});
Die Nachher-Konfiguration beschränkt typbasierte Regeln auf src, entfernt die Formatierungs-Plugins sowie die Import-Regeln, die TypeScript bereits abdeckt, und aktiviert import/no-cycle nur, wenn eine Umgebungsvariable gesetzt ist:
// @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' } : {}),
},
},
);
Mit dieser Konfiguration führt der Pre-Commit-Hook npx eslint . aus, während die CI LINT_FULL=1 npx eslint . und npx prettier --check . ausführt.
Die folgende Tabelle ist ein veranschaulichendes Beispiel dafür, wie sich die TIMING=1-Liste nach dieser Konfigurationsänderung verändert. Es handelt sich nicht um gemessene Ergebnisse:
| Regel | Vorher | Nachher |
|---|---|---|
prettier/prettier | Weit oben | Entfernt (ersetzt durch prettier --check) |
@stylistic/indent | Weit oben | Entfernt |
import/no-cycle | Weit oben | Erscheint nur in Läufen mit LINT_FULL=1 |
import/named, import/no-unresolved | Aufgeführt | Entfernt |
| Erste typbasierte Regel | Als langsamste aufgeführt | Weiterhin ganz oben; mit der tsc-Zeit vergleichen |
Fazit
Messen Sie, bevor Sie etwas entfernen. Beginnen Sie mit einer tsc-Ausgangsbasis, nutzen Sie dann TIMING=1 und das Stats-Panel des Config Inspector und messen Sie verdächtige typbasierte Regeln einzeln. Sobald Sie wissen, welche Regeln am teuersten sind, beschränken Sie das lokale Linting und den Pre-Commit-Hook auf schnelle, dateibezogene Regeln und verlagern dateiübergreifende Prüfungen sowie die Formatierung in die CI. Führen Sie jetzt TIMING=1 npx eslint . in Ihrem Projekt aus und arbeiten Sie die Lösungstabelle für die Regeln ab, die ganz oben landen.
FAQs
Wie sehe ich mehr als 10 Regeln in der TIMING-Ausgabe von ESLint?
Setzen Sie TIMING auf eine größere Zahl oder auf all. TIMING=1 zeigt die zehn Regeln mit der längsten Laufzeit, TIMING=50 zeigt fünfzig und TIMING=all zeigt alle Regeln. Jede Zeile enthält die Zeit der Regel in Millisekunden (Erstellung plus Ausführung der Regel) und ihren Anteil an der gesamten Regelzeit. Um eine einzelne Regel zu messen, kombinieren Sie --no-config-lookup mit --rule. Das eignet sich für Core-Regeln, die keine Parser-Konfiguration benötigen.
Kann man ESLints --cache bedenkenlos mit typbasierten Regeln verwenden?
Nicht uneingeschränkt. Der Cache von ESLint prüft nur, ob sich eine Datei oder die Konfiguration geändert hat. Änderungen an anderen Dateien, von denen eine Datei abhängt, werden ignoriert. Typbasierte typescript-eslint-Regeln und importauflösende Regeln aus eslint-plugin-import können daher in beide Richtungen veraltete Ergebnisse liefern: Sie können ein neues Problem übersehen oder ein bereits behobenes weiterhin melden. Die typescript-eslint-FAQ rät von --cache ab, führen Sie die CI daher ohne aus.
Wie erhalte ich Timings pro Datei und pro Regel von ESLint?
Führen Sie ESLint mit seinem eigenen --stats-Flag und einem Formatter aus, der Stats-Daten ausgibt, zum Beispiel npx eslint --stats -f json. Jedes Ergebnisobjekt erhält einen stats-Schlüssel mit Parse-Zeiten, Fix-Zeiten und Lint-Zeiten pro Regel für die jeweilige Datei, während TIMING nur den gesamten Lauf zusammenfasst. Nicht jeder Formatter gibt Stats-Daten aus. Der Caching-Vorbehalt bezüglich der ersten typbasierten Regel gilt auch für --stats.
Macht parserOptions.projectService typbasiertes Linting schneller als parserOptions.project?
Nicht zuverlässig. typescript-eslint ging davon aus, dass der Project Service, verfügbar ab v8.0.0, schneller ist, und behandelt den ausbleibenden Geschwindigkeitsgewinn als Bug. Der Hauptvorteil ist die einfachere Konfiguration: Sie benötigen keine Globs mehr für parserOptions.project, bei denen rekursive **-Muster zusätzliche Festplatten-I/O verursachen. Halten Sie allowDefaultProject klein, denn jede darüber erfasste Datei verlangsamt das Linting spürbar, und standardmäßig darf es höchstens 8 Dateien erfassen.