Статьи с тегом "#eslint"

✅ Опубликовано: 1
📌 Показаны только опубликованные статьи.

Фильтры статей

Практическое руководство по настройке ESLint v9+ (Flat Config) для стека React + TypeScript, по методологии Feature-Sliced Design (FSD)

14 0 0.0 0
Категории: IT и программирование Языки программирования Веб-технологии
Вот готовое практическое руководство по настройке ESLint v9+ (Flat Config) для стека React + TypeScript и созданию структуры папок по методологии Feature-Sliced Design (FSD).

1. Настройка ESLint (для React + TS)

Современный ESLint (начиная с версии 9) использует новый формат конфигурации eslint.config.js (Flat Config).

Шаг 1: Установка зависимостей

Выполните команду в терминале проекта:
npm install -D eslint @eslint/js typescript-eslint eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-refresh

Шаг 2: Создание файла eslint.config.js

Создайте этот файл в корневом каталоге проекта:
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import reactPlugin from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';

export default tseslint.config(
  // Игнорируемые папки (замена старого .eslintignore)
  { ignores: ['dist', 'node_modules', 'build'] },
  
  // Базовые настройки для JavaScript и TypeScript
  js.configs.recommended,
  ...tseslint.configs.recommended,
  
  // Настройки для React-компонентов
  {
    files: ['**/*.{ts,tsx}'],
    plugins: {
      'react': reactPlugin,
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
    },
    languageOptions: {
      parserOptions: {
        ecmaFeatures: { jsx: true },
      },
    },
    settings: {
      react: { version: 'detect' }, // Автоопределение версии React
    },
    rules: {
      // Правила React Hooks
      ...reactHooks.configs.recommended.rules,
      
      // Специфичные правила для React
      'react/react-in-jsx-scope': 'off', // Отключено для React 17+
      'react/jsx-no-target-blank': 'warn',
      
      // Правила для React Fast Refresh (полезно для Vite)
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
      
      // Кастомные правила TypeScript
      '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
      '@typescript-eslint/no-explicit-any': 'warn',
    },
  }
);

2. Структура папок по стандарту Feature-Sliced Design (FSD)

Методология FSD делит проект на слои (Layers), внутри которых находятся слайсы (Slices), разбитые на сегменты (Segments). Главное правило: верхние слои могут импортировать код только из нижних, но не наоборот.
Вот как выглядит правильная структура каталога src для типичного React-приложения:
src/
├── 1_app/                  # Слои (Layers) пишутся строчными буквами, цифры — для визуального порядка
│   ├── providers/          # Провайдеры (Redux Store, RouterProvider, ThemeProvider)
│   ├── styles/             # Глобальные стили (index.css, variables.scss)
│   └── App.tsx             # Инициализация приложения
│
├── 2_pages/                # Страницы приложения
│   ├── home/               # Слайс страницы (Slice)
│   │   ├── ui/             # Сегменты (Segments): UI-компоненты страницы
│   │   └── index.ts        # Публичное API (только отсюда можно делать импорт наружу!)
│   └── profile/
│       ├── ui/
│       └── index.ts
│
├── 3_widgets/              # Крупные самостоятельные блоки (из фич и сущностей)
│   ├── header/
│   │   ├── ui/             # Header.tsx
│   │   └── index.ts
│   └── sidebar/
│
├── 4_features/             # Действия пользователя, несущие бизнес-ценность
│   ├── auth-by-username/   # Авторизация
│   │   ├── model/          # Стейт, экшены, селекторы (Redux/Zustand)
│   │   ├── ui/             # Форма логина, кнопка
│   │   └── index.ts
│   └── add-to-cart/        # Добавление в корзину
│
├── 5_entities/             # Бизнес-сущности (без привязки к конкретным действиям)
│   ├── user/
│   │   ├── model/          # Типы пользователя, стейт авторизации
│   │   └── index.ts
│   └── product/
│       ├── ui/             # Карточка продукта (ProductCard)
│       └── index.ts
│
└── 6_shared/               # Переиспользуемый код (инфраструктура, утилиты)
    ├── api/                # Базовые запросы (axios, fetch)
    ├── assets/             # Иконки, картинки, шрифты
    ├── lib/                # Хелперы, кастомные хуки (useDebounce и др.)
    └── ui/                 # Атомарные UI-компоненты (Button, Input, Modal)
        ├── button/
        │   ├── Button.tsx
        │   └── index.ts
        └── input/

Важные правила FSD для вашей команды:

 
  1. Индексные файлы (index.ts) — это «ворота» слайса. Извне разрешено импортировать код только через них (например: import { Button } from '@/shared/ui/button'). Импорты вида .../button/Button.tsx строго запрещены.
  2. Иерархия импортов — widgets может импортировать из features, entities и shared. Но shared не может импортировать ничего из слоев выше.


 
Настроить специальный плагин для ESLint, который запретит некорректные импорты между слоями. 
Для автоматического контроля архитектуры Feature-Sliced Design (FSD) в ESLint используется официальный плагин eslint-plugin-boundaries или специализированный eslint-plugin-fsd (также известный как eslint-plugin-feature-sliced).
Наиболее гибким, поддерживаемым и совместимым с ESLint v9+ (Flat Config) является eslint-plugin-boundaries. Он позволяет жестко заблокировать импорты снизу вверх (например, из shared в features).
Вот пошаговое руководство по его интеграции.

Шаг 1: Установка плагина

Установите плагин контроля границ в dev-зависимости:
npm install -D eslint-plugin-boundaries

Шаг 2: Обновление конфигурации eslint.config.js

Вам нужно импортировать плагин, разметить папки вашего проекта на «типы» (слои) и задать правила их взаимодействия.
Добавьте этот код в ваш существующий файл eslint.config.js:
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import reactPlugin from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
// 1. Импортируем плагин границ
import boundaries from 'eslint-plugin-boundaries';

export default tseslint.config(
  { ignores: ['dist', 'node_modules', 'build'] },
  js.configs.recommended,
  ...tseslint.configs.recommended,
  
  {
    files: ['**/*.{ts,tsx,js,jsx}'],
    plugins: {
      'react': reactPlugin,
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
      // 2. Регистрируем плагин
      'boundaries': boundaries,
    },
    languageOptions: {
      parserOptions: {
        ecmaFeatures: { jsx: true },
      },
    },
    settings: {
      react: { version: 'detect' },
      // 3. Настраиваем распознавание путей (поддерживает относительные импорты и алиасы вроде @/*)
      'boundaries/elements': [
        { type: 'app', pattern: 'src/1_app/**/*' },
        { type: 'pages', pattern: 'src/2_pages/**/*' },
        { type: 'widgets', pattern: 'src/3_widgets/**/*' },
        { type: 'features', pattern: 'src/4_features/**/*' },
        { type: 'entities', pattern: 'src/5_entities/**/*' },
        { type: 'shared', pattern: 'src/6_shared/**/*' },
      ],
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      'react/react-in-jsx-scope': 'off',
      
      // 4. Включаем правила FSD
      'boundaries/entry-point': 'error', // Запрещает глубокие импорты в обход index.ts
      'boundaries/element-types': [
        'error',
        {
          default: 'disallow', // По умолчанию всё запрещено, кроме явных разрешений ниже
          message: 'Архитектурная ошибка FSD: импорт из слоев выше или чужих слайсов запрещен (${file.type} <- ${dependency.type})',
          rules: [
            // Слою App доступно всё
            { from: 'app', allow: ['pages', 'widgets', 'features', 'entities', 'shared'] },
            // Слою Pages доступны все нижележащие слои
            { from: 'pages', allow: ['widgets', 'features', 'entities', 'shared'] },
            // Слою Widgets доступны фичи, сущности и shared
            { from: 'widgets', allow: ['features', 'entities', 'shared'] },
            // Слою Features доступны сущности и shared
            { from: 'features', allow: ['entities', 'shared'] },
            // Слою Entities доступен только shared
            { from: 'entities', allow: ['shared'] },
            // Слою Shared запрещено импортировать из любых других слоев FSD
            { from: 'shared', allow: [] },
          ],
        },
      ],
    },
  }
);

Как это работает на практике (Примеры)

Если линтер настроен правильно, вы сразу получите сообщения об ошибках в редакторе (VS Code / WebStorm) или при сборке проекта:
 
  • Ошибка ❌: Попытка импортировать хук авторизации из features внутрь кнопки в shared:
    // Внутри src/6_shared/ui/button/Button.tsx
    import { useAuth } from '@/features/auth-by-username'; 
    // ESLint выведет: "Архитектурная ошибка FSD: импорт из слоев выше или чужих слайсов запрещен (shared <- features)"
    
  • Ошибка ❌: Нарушение принципа Public API (глубокий импорт в обход index.ts слайса):
    // Внутри src/2_pages/home/ui/HomePage.tsx
    import { LoginForm } from '@/features/auth-by-username/ui/LoginForm'; 
    // ESLint выдаст ошибку правила boundaries/entry-point. Нужно импортировать строго из '@/features/auth-by-username'
    
  • **Корректно Запрос из виджета в фичу через Public API:
    // Внутри src/3_widgets/header/ui/Header.tsx
    import { LoginButton } from '@/features/auth-by-username'; 
    


 
Настройка алиасов путей (Path Aliases) в tsconfig.json и Vite/Webpack, чтобы вместо длинных относительных путей ../../ использовать красивый префикс @/
Настройка Path Aliases (алиасов путей) сделает ваши импорты чистыми (вместо ../../../../shared/ui вы будете писать @/shared/ui), а также поможет плагину eslint-plugin-boundaries работать без сбоев.
Поскольку вы используете React, проект, скорее всего, собран на Vite. Нам нужно настроить алиасы в трех местах: в TypeScript (для подсказок в IDE), в Vite (для сборщика) и в ESLint (для резолва путей плагином границ).

Шаг 1: Настройка TypeScript (tsconfig.json)

Откройте файл tsconfig.json (или tsconfig.app.json, если у вас проект на свежем Vite) и добавьте секцию paths внутрь compilerOptions:
{
  "compilerOptions": {
    // ... ваши текущие настройки
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}
Что это делает: Говорит TypeScript, что символ @/ указывает на папку src/.

Шаг 2: Настройка Vite (vite.config.ts)

Чтобы Vite понимал эти пути при сборке, установим плагин vite-tsconfig-paths. Он автоматически синхронизирует настройки из tsconfig.json в Vite, избавляя от ручного дублирования.
 
  1. Установите плагин:
    npm install -D vite-tsconfig-paths
    
  2. Обновите vite.config.ts:
    import { defineConfig } from 'vite';
    import react from '@vitejs/plugin-react';
    // 1. Импортируем плагин
    import tsconfigPaths from 'vite-tsconfig-paths';
    
    export default defineConfig({
      // 2. Добавляем в массив плагинов
      plugins: [react(), tsconfigPaths()],
    });
    

Шаг 3: Корректировка ESLint для плагина границ (eslint.config.js)

Чтобы плагин eslint-plugin-boundaries правильно читал пути с префиксом @/, нам нужно добавить специальный плагин-резолвер для ESLint.
 
  1. Установите парсер путей TypeScript для ESLint:
    npm install -D eslint-import-resolver-typescript
    
  2. Обновите секцию settings в вашем eslint.config.js:
// ... ваши импорты

export default tseslint.config(
  { ignores: ['dist', 'node_modules', 'build'] },
  js.configs.recommended,
  ...tseslint.configs.recommended,
  
  {
    files: ['**/*.{ts,tsx,js,jsx}'],
    plugins: {
      'react': reactPlugin,
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
      'boundaries': boundaries,
    },
    // ОБНОВЛЕННАЯ СЕКЦИЯ SETTINGS
    settings: {
      react: { version: 'detect' },
      
      // Настройка для распознавания алиаса @/* парсером ESLint
      'import/resolver': {
        typescript: {}, 
      },
      
      // Шаблоны теперь учитывают как относительные пути, так и алиас @/
      'boundaries/elements': [
        { type: 'app', pattern: ['src/1_app/**/*', '@/1_app/**/*'] },
        { type: 'pages', pattern: ['src/2_pages/**/*', '@/2_pages/**/*'] },
        { type: 'widgets', pattern: ['src/3_widgets/**/*', '@/3_widgets/**/*'] },
        { type: 'features', pattern: ['src/4_features/**/*', '@/4_features/**/*'] },
        { type: 'entities', pattern: ['src/5_entities/**/*', '@/5_entities/**/*'] },
        { type: 'shared', pattern: ['src/6_shared/**/*', '@/6_shared/**/*'] },
      ],
    },
    rules: {
      // ... правила, которые мы настроили на прошлом шаге
    }
  }
);

Проверка результата 🚀

После перезапуска IDE (иногда VS Code нужно перезапустить через Ctrl+Shift+P -> Developer: Reload Window), вы сможете писать импорты в стиле FSD:
// Внутри src/2_pages/home/ui/HomePage.tsx

// Отлично: импорт виджета через красивый алиас
import { Header } from '@/3_widgets/header'; 

// Отлично: импорт кнопки из shared
import { Button } from '@/6_shared/ui/button'; 

// ESLint выдаст ошибку ❌ (импорт сверху вниз):
// import { ProfilePage } from '@/2_pages/profile'; 
Если вы планируете использовать менеджер состояния (например, Zustand или Redux Toolkit), я могу показать, как правильно разложить стейт по слоям features и entities, чтобы не нарушить правила линтера. Рассказать об этом подробнее?

 
Вам понравилась статья?
Read more