React 19 и ESLint 10: как я форкнул eslint-plugin-react

#react#eslint#biome#linting

Когда поднимаешь ESLint до 10-й версии в проектах с React 19, старый eslint-plugin-react просто ломается. Ошибка contextOrFilename.getFilename is not a function — это не баг, а фича нового API ESLint. И вот ты сидишь с открытым issue в upstream-репозитории, который висит уже полгода, а миграцию делать надо сейчас.

Почему форк — это не failure, а contingency plan

Изначально я думал, что потерплю пару недель, пока мержится PR. Но когда стало ясно, что реакция сообщества измеряется в геологических эпохах, родился @ternaus/eslint-plugin-react.

Критерии были жёсткие:

Из 104 оригинальных правил осталось 11. Вот как я их отбирал:

  1. Дублирование с Biome — сразу в корзину (20 правил типа jsx-key)
  2. Стилевые предписания — не наш профиль (jsx-sort-props, function-component-definition)
  3. Сомнительные эвристики — если правило не может дать точный ответ без type-aware анализа (no-unused-prop-types)
  4. Устаревшие API — React 18 и классические конфиги не наш target

Остались только правила, которые:

Интеграция с Next.js: где собака порылась

Теоретически всё работало. Практически Next.js 16 упорно пытался загрузить старый eslint-plugin-react. Проблема в том, что eslint-config-next:

  1. Использует flat config
  2. Но лезет за правилами в react.configs.recommended.rules (legacy-формат)

Решение выглядело как костыль, но работало:

// В eslint.config.js
import react from '@ternaus/eslint-plugin-react';

export default [
  {
    files: ['**/*.{js,jsx,mjs,cjs,ts,tsx}'],
    ...react.configs.flat.recommended,
    // Дублируем для Next.js
    rules: react.configs.recommended.rules
  },
];

Плюс магия Yarn resolutions:

{
  "resolutions": {
    "eslint-plugin-react": "npm:@ternaus/eslint-plugin-react@8.0.0"
  }
}

Что делать, если ваш проект не вписывается в матрицу

Мой форк — это opinionated выбор. Если вам нужно:

Для остальных случаев вот рецепт:

  1. Ставим связку:
yarn add -D @biomejs/biome@latest eslint@10 @ternaus/eslint-plugin-react@8
  1. Настраиваем Biome на полный набор правил:
{
  "linter": {
    "rules": { "preset": "all" },
    "domains": { "react": "all" }
  }
}
  1. Добавляем ESLint только для React-специфики:
// eslint.config.js
import react from '@ternaus/eslint-plugin-react';

export default [{
  files: ['**/*.{js,jsx,ts,tsx}'],
  ...react.configs.flat.recommended
}];

Выводы для тех, кто хочет повторить

  1. Не бойтесь форков — особенно когда upstream застрял в переходном периоде
  2. Интегрируйте с Biome — он покрывает 80% случаев, ESLint оставьте для framework-specific правил
  3. Тестируйте на реальных проектах — мой первый RC упал на валидных HTML-атрибутах в <select>
  4. Yarn resolutions — ваш друг — когда Next.js упорно тянет старую версию

Сейчас пакет стабильно работает в продакшене на трёх проектах с разными стеками. Главное — чётко определить границы поддержки и не пытаться угодить всем. Иногда меньше — значит действительно лучше.


Источник: https://dev.to/viglovikov/i-needed-react-linting-on-eslint-10-so-i-built-a-react-19-continuation-39id