12k
All articles

Cómo encontrar las reglas de ESLint que ralentizan tu linting

Detecta reglas lentas de ESLint con TIMING=1 y Config Inspector. Compara reglas con tipos, identifica plugins costosos y ajusta la configuración para acelerar el lint.

OpenReplay Team
OpenReplay Team
Cómo encontrar las reglas de ESLint que ralentizan tu linting

Cuando ESLint es lento, la causa suele estar en un número reducido de reglas: las reglas de typescript-eslint con información de tipos (type-aware), eslint-plugin-prettier, @stylistic/indent o las reglas de eslint-plugin-import que analizan varios archivos, como import/no-cycle. Puedes identificarlas con TIMING=1 y con el panel de estadísticas de ESLint Config Inspector.

Un linting lento sale caro enseguida. El hook de pre-commit tarda tanto que la gente empieza a hacer commits con --no-verify, y a partir de ese momento la configuración de lint deja de proteger nada.

Esta guía explica cómo medir el coste de cada regla, qué reglas suelen resultar problemáticas y qué cambiar en eslint.config.js en cada caso.

Puntos clave

  • Con linting basado en tipos, una ejecución completa de ESLint debería tardar aproximadamente lo mismo que tsc en el mismo proyecto. Si tarda mucho más, el problema está en una regla concreta o en la configuración.
  • TIMING=1 npx eslint . muestra las reglas más lentas. La primera regla con información de tipos casi siempre aparece como la más lenta porque TypeScript almacena en caché la información de tipos, así que conviene probar cada sospechosa por separado.
  • ESLint Config Inspector 3.4.0 añadió un flag --stats y un panel de estadísticas que ejecuta la medición de tiempos de ESLint bajo demanda. Es un flag distinto del --stats propio de ESLint.
  • La documentación de typescript-eslint recomienda ejecutar import/no-cycle, import/no-unused-modules, import/no-deprecated e import/no-named-as-default solo en CI o al hacer push, porque TypeScript no ofrece una comprobación equivalente.
  • eslint-plugin-prettier analiza cada archivo por segunda vez. Ejecutar prettier --check como paso independiente ofrece el mismo resultado de éxito o fallo.

¿Por qué ESLint es lento?

ESLint suele ser lento por culpa de unas pocas reglas costosas, no por TypeScript en sí. Con linting basado en tipos, una ejecución completa de ESLint debería durar más o menos lo mismo que la comprobación de tipos. Según la guía de rendimiento de typescript-eslint, la causa habitual de la lentitud son las reglas con información de tipos que solicitan datos al comprobador de tipos (type checker) de TypeScript. Mide primero tu referencia base:

npx tsc --noEmit

Si tsc es rápido y el lint es mucho más lento, la culpa es de una regla o de un ajuste de configuración, no de TypeScript.

¿Cómo se comprueba el tiempo de cada regla con TIMING=1?

Al definir la variable de entorno TIMING, ESLint muestra las reglas que más tiempo han consumido. No hace falta instalar nada:

TIMING=1 npx eslint .

Este comportamiento se describe en la documentación de ESLint Profile Rule Performance.

Ten cuidado al interpretar la salida. TypeScript construye la información de tipos cuando la primera regla basada en tipos la solicita y la guarda en caché para todas las reglas posteriores. Por eso, la primera regla con información de tipos de la ejecución casi siempre parece la más lenta, aunque en realidad sea barata.

Para una comparación justa, mide cada regla basada en tipos en una ejecución independiente, con las demás desactivadas. Una forma de hacerlo es mantener una configuración reducida que contenga una sola regla sospechosa y pasársela a ESLint con -c/--config:

TIMING=1 npx eslint -c eslint.timing.config.js .

Ver los tiempos en ESLint Config Inspector

ESLint Config Inspector incluye una vista de tiempos. Según sus notas de versión, la versión 3.4.0 añadió un flag --stats a los comandos dev y build, así como un panel de estadísticas que ejecuta la medición de tiempos de ESLint bajo demanda. Por tanto, la vista de tiempos requiere Config Inspector 3.4 o posterior. Usa la versión 3.x más reciente, ya que la 3.4.1 corrigió un fallo de build --stats que se producía cuando ESLint informaba de una pasada sin datos de tiempo de análisis (issue #333).

Este --stats pertenece a Config Inspector. ESLint tiene su propio flag --stats, que añade datos de tiempo al resultado de lint de cada archivo para que un formatter los muestre. No abre ningún panel.

Ejecuta estos comandos en la carpeta que contiene tu 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

Ejecutar simplemente pnpx @eslint/config-inspector inicia el inspector en http://localhost:7777. Pasa --stats al comando dev (--help muestra la forma exacta), abre el panel de estadísticas y busca las reglas y plugins que acaparan la mayor parte del tiempo. La advertencia sobre la primera regla basada en tipos también se aplica en esta vista. El build estático es una instantánea, así que puedes conservar una de antes de un cambio de configuración y compararla con otra tomada después.

¿Qué reglas de ESLint suelen ser las más lentas?

Los mismos cuatro grupos de reglas de ESLint aparecen una y otra vez en lo más alto de la salida de tiempos:

  • Reglas con información de tipos. Presets como recommendedTypeChecked añaden reglas que dependen del comprobador de tipos. La configuración se explica en linting de TypeScript con ESLint.
  • eslint-plugin-prettier. El plugin solo puede detectar una diferencia de formato ejecutando Prettier sobre el archivo completo, de modo que cada archivo analizado lo procesa una vez ESLint y otra vez Prettier. El artículo sobre ESLint y Prettier explica cómo se reparten el trabajo ambas herramientas.
  • @stylistic/indent. Calcula la indentación de cada token en cada archivo, y en una base de código grande ese trabajo se acumula.
  • Reglas de eslint-plugin-import, especialmente import/no-cycle. Estas reglas obligan al plugin a analizar archivos y seguir los imports por su cuenta, al margen de ESLint, por lo que el coste crece con el tamaño de la base de código. Consulta plugins de ESLint que mejoran la calidad del código para ver qué detecta el plugin.

Cómo corregir cada regla lenta

Cada grupo de reglas lentas tiene su propia solución. La mayoría de las recomendaciones siguientes proceden de la guía de rendimiento de typescript-eslint:

CausaPor qué es lentaSolución
Reglas con información de tiposLlaman al comprobador de tiposLimítalas a los archivos fuente con files y acota el include del tsconfig
eslint-plugin-prettierAnaliza cada archivo por segunda vezElimínalo y ejecuta prettier --check .
@stylistic/indentRecorre todos los tokensElimínala y deja la indentación en manos de Prettier
import/named, import/namespace, import/default, import/no-named-as-default-member, import/no-unresolvedDuplican comprobaciones de TypeScriptDesactívalas (no-unresolved solo si el código usa import y no require)
import/no-cycle, import/no-unused-modules, import/no-deprecated, import/no-named-as-defaultAnálisis entre archivos sin equivalente en TypeScriptEjecútalas solo en CI o al hacer push

Revisa también el tsconfig. Un tsconfig sin include, o con un glob como **/*, hace que el parser preanalice muchos más archivos de los que pretendías revisar, incluida la salida de compilación. Limita include a las carpetas que realmente quieres analizar:

{ "compilerOptions": { /* ... */ }, "include": ["src"] }

Usa --check de Prettier como paso independiente en CI en lugar del plugin. Para ejecuciones locales repetidas, el flag --cache de ESLint omite los archivos que no han cambiado desde la última ejecución.

Ejemplo: antes y después

La configuración inicial siguiente lo ejecuta todo en todos los archivos:

// @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 configuración posterior limita las reglas con información de tipos a src, elimina los plugins de formato y las reglas de import que TypeScript ya cubre, y activa import/no-cycle solo cuando se define una variable de entorno:

// @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' } : {}),
    },
  },
);

Con esta configuración, el hook de pre-commit ejecuta npx eslint ., mientras que en CI se ejecutan LINT_FULL=1 npx eslint . y npx prettier --check ..

La tabla siguiente es un ejemplo ilustrativo de cómo cambia la lista de TIMING=1 tras este cambio de configuración. No se trata de resultados medidos:

ReglaAntesDespués
prettier/prettierCerca de la cimaDesaparece (sustituida por prettier --check)
@stylistic/indentCerca de la cimaDesaparece
import/no-cycleCerca de la cimaSolo aparece en ejecuciones con LINT_FULL=1
import/named, import/no-unresolvedEn la listaDesaparecen
Primera regla con información de tiposAparece como la más lentaSigue primera; compárala con el tiempo de tsc

Conclusión

Mide antes de eliminar nada. Empieza con una referencia de tsc, ejecuta después TIMING=1 y el panel de estadísticas de Config Inspector, y mide las reglas sospechosas basadas en tipos de una en una. Cuando sepas qué reglas cuestan más, reserva el linting local y de pre-commit para reglas rápidas que trabajen con un solo archivo, y traslada a CI las comprobaciones entre archivos y el formato. Ejecuta ahora TIMING=1 npx eslint . en tu proyecto y aplica la tabla de soluciones a las reglas que aparezcan en lo más alto.

Preguntas frecuentes

¿Cómo puedo ver más de 10 reglas en la salida de TIMING de ESLint?

Asigna a TIMING un número mayor o el valor all. TIMING=1 muestra las diez reglas que más tardan, TIMING=50 muestra cincuenta y TIMING=all muestra todas. Cada fila indica el tiempo de la regla en milisegundos (creación de la regla más ejecución) y su porcentaje sobre el tiempo total de reglas. Para medir una sola regla, combina --no-config-lookup con --rule. Esto resulta adecuado para las reglas del núcleo que no necesitan configuración de parser.

¿Es seguro usar --cache de ESLint con reglas basadas en tipos?

No del todo. La caché de ESLint solo comprueba si ha cambiado un archivo o la configuración. Ignora los cambios en los demás archivos de los que depende cada archivo. Por eso, las reglas de typescript-eslint con información de tipos y las reglas de eslint-plugin-import que resuelven imports pueden devolver resultados obsoletos en ambos sentidos: pueden pasar por alto un problema nuevo o seguir notificando uno que ya está corregido. Las preguntas frecuentes de typescript-eslint no recomiendan --cache, así que ejecuta CI sin él.

¿Cómo obtengo tiempos por archivo y por regla en ESLint?

Ejecuta ESLint con su propio flag --stats y un formatter que muestre datos de estadísticas, por ejemplo npx eslint --stats -f json. Cada objeto de resultado recibe una clave stats con los tiempos de análisis, los tiempos de corrección y los tiempos de lint por regla de ese archivo, mientras que TIMING solo resume la ejecución completa. No todos los formatters muestran datos de estadísticas. La advertencia sobre la caché de la primera regla basada en tipos también se aplica a --stats.

¿parserOptions.projectService hace que el linting con tipos sea más rápido que parserOptions.project?

No de forma fiable. typescript-eslint esperaba que el Project Service, disponible desde la v8.0.0, fuera más rápido, y afirma que trata la falta de mejora de velocidad como un bug. La principal ventaja es una configuración más sencilla: ya no necesitas globs en parserOptions.project, donde los patrones recursivos ** generan E/S de disco adicional. Mantén allowDefaultProject reducido, porque cada archivo que coincide con él ralentiza el linting de forma notable, y por defecto no puede abarcar más de 8 archivos.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.