Сужение области обработки: include/exclude в загрузчиках

Механизм загрузчиков в Webpack предполагает обработку каждого модуля через цепочку трансформаций. При росте проекта эта обработка может становиться значительной по времени, особенно если одни и те же правила применяются ко всем файлам без ограничений. Для управления производительностью и предсказуемостью сборки используется сужение области применения загрузчиков через параметры include и exclude.

Базовая модель работы include и exclude

Каждый загрузчик в Webpack может быть ограничен набором файлов, к которым он применяется. Это реализуется через проверку пути к модулю:

  • include задаёт набор директорий или файлов, которые разрешены для обработки
  • exclude задаёт набор директорий или файлов, которые исключаются из обработки

Оба параметра могут использоваться как отдельно, так и совместно, но их комбинация требует понимания приоритета и логики фильтрации.

Внутренняя логика проверки упрощённо выглядит следующим образом:

  1. Проверяется exclude
  2. Затем проверяется include
  3. Если файл не исключён и попадает в include, загрузчик применяется

Фактически Webpack использует функции-предикаты для определения попадания модуля в область обработки.

Форматы задания include и exclude

Параметры include и exclude принимают несколько типов значений:

  • строка пути
  • массив строк путей
  • регулярное выражение
  • функция, возвращающая true/false
  • объект с методом test (например, RegExp)

Пример использования строкового пути:

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        include: '/src',
        loader: 'babel-loader'
      }
    ]
  }
};

В этом случае загрузчик применяется только к файлам внутри директории src.

Использование массива путей:

include: [
  path.resolve(__dirname, 'src'),
  path.resolve(__dirname, 'shared')
]

Такой подход позволяет расширять зону обработки без дублирования правил.

Регулярные выражения дают более гибкий контроль:

include: /src\/(components|utils)/

Это позволяет ограничить обработку только определёнными подпапками.

Функциональный вариант:

include: (filepath) => filepath.includes('src')

Такой способ используется реже, но даёт максимальную гибкость.

Приоритет include и exclude

На практике важным аспектом является взаимодействие include и exclude. Если оба параметра заданы одновременно, исключения обычно имеют более высокий приоритет в типовых конфигурациях сборок.

Пример:

{
  test: /\.js$/,
  include: path.resolve(__dirname, 'src'),
  exclude: /node_modules/,
  loader: 'babel-loader'
}

Здесь:

  • все файлы из node_modules исключаются независимо от include
  • из src обрабатываются только JS-файлы, не попадающие под exclude

Такая конфигурация является стандартной, так как node_modules почти всегда исключается из транспиляции.

Производительность и влияние на сборку

Основная причина использования include и exclude — оптимизация времени сборки. Без ограничения области обработки загрузчик вынужден анализировать каждый модуль проекта, включая сторонние зависимости.

Наиболее критичные случаи:

  • Babel-транспиляция больших кодовых баз
  • обработка TypeScript
  • Sass/Less компиляция в больших монорепозиториях
  • обработка изображений и ресурсов через file-loader или asset modules

Каждый лишний файл увеличивает время работы пайплайна.

Ограничение области обработки позволяет:

  • уменьшить количество операций трансформации
  • снизить нагрузку на CPU
  • уменьшить время пересборки в режиме watch
  • ускорить hot module replacement

Типичные паттерны конфигурации

Исключение node_modules

Наиболее распространённый шаблон:

{
  test: /\.js$/,
  exclude: /node_modules/,
  use: 'babel-loader'
}

Идея заключается в том, что зависимости уже поставляются в транспилированном виде, либо совместимы с целевыми окружениями.

Явное включение src

Более строгий и контролируемый вариант:

{
  test: /\.ts$/,
  include: path.resolve(__dirname, 'src'),
  use: 'ts-loader'
}

Такой подход исключает случайную обработку файлов вне исходного кода приложения.

Комбинированный вариант

{
  test: /\.js$/,
  include: path.resolve(__dirname, 'src'),
  exclude: /legacy/,
  use: 'babel-loader'
}

Здесь создаётся дополнительный слой фильтрации внутри проекта.

Сложные сценарии фильтрации

В реальных проектах часто возникает необходимость более точной настройки, чем просто include/exclude по папкам.

Множественные зоны обработки

{
  test: /\.js$/,
  include: [
    path.resolve(__dirname, 'src'),
    path.resolve(__dirname, 'packages/ui')
  ],
  exclude: /node_modules/,
  use: 'babel-loader'
}

Это характерно для монорепозиториев, где код разделён на пакеты.

Исключение конкретных поддиректорий

exclude: [
  path.resolve(__dirname, 'src/__tests__'),
  path.resolve(__dirname, 'src/mock')
]

Такой подход позволяет исключить тестовые и вспомогательные файлы из производственной сборки.

Условная фильтрация через функцию

exclude: (filepath) => {
  return filepath.includes('__tests__') || filepath.endsWith('.spec.js');
}

Функции применяются, когда структура проекта динамическая или сложная для выражения через RegExp.

Ошибки при использовании include и exclude

Одной из распространённых проблем является чрезмерное ограничение области обработки.

Типичный случай:

include: path.resolve(__dirname, 'src/components')

При таком ограничении код вне components не обрабатывается, что может привести к:

  • ошибкам импорта
  • несовместимому синтаксису в других частях проекта
  • неожиданному поведению зависимостей

Другой частый случай — дублирование логики:

include: /src/,
exclude: /src/

Такая конфигурация фактически делает правило неработоспособным.

Также ошибкой является отсутствие исключения node_modules при использовании Babel, что приводит к значительному замедлению сборки.

Влияние порядка правил

Хотя include и exclude задаются внутри конкретного rule, общий порядок правил в конфигурации Webpack также влияет на результат.

Если один и тот же файл попадает под несколько правил, каждое из них может применяться последовательно, если не ограничено через oneOf или enforce.

Пример конфликтной конфигурации:

rules: [
  {
    test: /\.js$/,
    include: /src/,
    loader: 'babel-loader'
  },
  {
    test: /\.js$/,
    include: /src/,
    loader: 'eslint-loader'
  }
]

Оба загрузчика будут применены к одним и тем же файлам, что не всегда является желаемым поведением.

Оптимизация через точечное ограничение

В больших проектах применение include/exclude становится частью общей стратегии оптимизации сборки. Вместо глобальных правил используется точечное распределение зон ответственности:

  • транспиляция только исходного кода
  • отдельная обработка библиотек
  • изоляция тестов
  • исключение генерируемых файлов

Такой подход снижает стоимость пересборки и делает поведение конфигурации предсказуемым.

Особенно эффективно это проявляется при использовании кеширования загрузчиков, где объём входных файлов напрямую влияет на эффективность кеша.

Связь с резолвингом модулей

Сужение области обработки тесно связано с механизмом разрешения модулей. Webpack сначала определяет модуль, затем применяет к нему правила.

Это означает, что:

  • include/exclude не влияют на импорт
  • они влияют только на последующую трансформацию

Поэтому файл может быть доступен в проекте, но не проходить через определённый загрузчик, что иногда используется намеренно для оптимизации.

Роль в архитектуре сборки

Правильное использование include/exclude фактически формирует границы архитектуры сборки. Через них определяется:

  • где находится исходный код
  • какие части проекта считаются внешними зависимостями
  • какие области требуют трансформации
  • какие файлы считаются вспомогательными

В крупных приложениях эти параметры становятся частью стандарта кодовой базы и фиксируются в базовых конфигурациях сборщика.