fork-ts-checker-webpack-plugin: параллельная проверка типов

При сборке проектов на TypeScript через Webpack возникает важная проблема производительности: полноценная проверка типов требует значительных вычислительных ресурсов. Если использовать только ts-loader в стандартном режиме, процесс компиляции и type checking выполняются последовательно внутри одного процесса Webpack. На крупных проектах это приводит к медленным rebuild-циклам, увеличению времени HMR и общей деградации DX.

Плагин fork-ts-checker-webpack-plugin решает эту проблему за счёт вынесения проверки типов и линтинга в отдельный процесс. Основная сборка Webpack продолжает выполняться независимо, а анализ TypeScript запускается параллельно.

Главная идея работы:

  • Webpack занимается бандлингом;
  • ts-loader или другой transpiler выполняет только трансформацию TS → JS;
  • fork-ts-checker-webpack-plugin отдельно запускает TypeScript compiler API для проверки типов.

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


Почему обычный ts-loader замедляет сборку

По умолчанию ts-loader выполняет сразу две задачи:

  1. Транспиляцию TypeScript.
  2. Полную проверку типов.

Это означает, что на каждом rebuild Webpack вынужден:

  • строить dependency graph;
  • компилировать модули;
  • запускать TypeScript semantic analysis.

Особенно тяжёлой является именно semantic phase:

  • анализ интерфейсов;
  • вывод generic-типов;
  • проверка деклараций;
  • проверка совместимости типов;
  • анализ импортов;
  • построение symbol table.

На больших монорепозиториях именно эта часть становится узким местом.


Архитектура ускорения сборки

Типичная схема работы выглядит так:

Webpack Process
 ├── transpilation (fast)
 ├── asset graph
 ├── chunk generation
 └── emit

Forked TypeScript Process
 ├── semantic diagnostics
 ├── syntactic diagnostics
 ├── declaration analysis
 └── eslint

Благодаря разделению задач:

  • UI dev-server остаётся отзывчивым;
  • HMR работает быстрее;
  • rebuild происходит почти мгновенно;
  • type checking не блокирует emit.

Установка

npm install -D fork-ts-checker-webpack-plugin typescript

Чаще всего плагин используется совместно с:

npm install -D ts-loader

или:

npm install -D babel-loader @babel/preset-typescript

Базовая конфигурация

Конфигурация с ts-loader

const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /\.ts$/,
        loader: 'ts-loader',
        options: {
          transpileOnly: true,
        },
      },
    ],
  },

  plugins: [
    new ForkTsCheckerWebpackPlugin(),
  ],
};

Ключевой параметр:

transpileOnly: true

Он отключает проверку типов внутри ts-loader.

После этого:

  • ts-loader выполняет только transpilation;
  • вся type checking логика переносится в плагин.

Что происходит без transpileOnly

Если забыть включить:

transpileOnly: true

получается двойная проверка типов:

  1. внутри ts-loader;
  2. внутри fork-ts-checker-webpack-plugin.

Это приводит к:

  • лишней нагрузке на CPU;
  • повышенному потреблению памяти;
  • замедлению rebuild;
  • дублирующимся ошибкам.

Использование с Babel

Одна из самых популярных современных схем:

TypeScript → Babel → Webpack

В таком режиме:

  • Babel удаляет типы;
  • TypeScript не участвует в transpilation;
  • fork-ts-checker-webpack-plugin выполняет только анализ типов.

Пример:

const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: [
              '@babel/preset-env',
              '@babel/preset-typescript',
            ],
          },
        },
      },
    ],
  },

  plugins: [
    new ForkTsCheckerWebpackPlugin(),
  ],
};

Преимущества такой схемы:

  • очень быстрый transpilation;
  • поддержка Babel ecosystem;
  • возможность использования Babel plugins;
  • минимизация времени rebuild.

Разница между transpilation и type checking

Transpilation

Удаление TypeScript-синтаксиса:

const user: string = 'Alex';

превращается в:

const user = 'Alex';

Type checking

Проверка корректности типов:

const age: number = '18';

TypeScript обнаруживает ошибку:

Type 'string' is not assignable to type 'number'

fork-ts-checker-webpack-plugin занимается именно второй частью.


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

const path = require('path');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

module.exports = {
  mode: 'development',

  entry: './src/index.ts',

  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
  },

  resolve: {
    extensions: ['.ts', '.js'],
  },

  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        use: {
          loader: 'ts-loader',
          options: {
            transpileOnly: true,
          },
        },
      },
    ],
  },

  plugins: [
    new ForkTsCheckerWebpackPlugin(),
  ],

  devtool: 'source-map',
};

Работа с tsconfig.json

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

{
  "compilerOptions": {
    "strict": true,
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Node",
    "skipLibCheck": true
  }
}

Все ошибки формируются на основе этих правил.


Проверка синтаксических ошибок

По умолчанию TypeScript разделяет:

  • syntactic diagnostics;
  • semantic diagnostics.

Плагин умеет обрабатывать оба типа.

Пример синтаксической ошибки:

const a = ;

Пример semantic ошибки:

const a: number = 'hello';

Настройка typescript секции

Плагин поддерживает глубокую конфигурацию.

Пример:

new ForkTsCheckerWebpackPlugin({
  typescript: {
    configFile: './tsconfig.json',
  },
});

Настройка memory limit

Крупные проекты могут упираться в ограничение памяти Node.js.

Для этого используется:

new ForkTsCheckerWebpackPlugin({
  typescript: {
    memoryLimit: 4096,
  },
});

Значение указывается в мегабайтах.

Типичные симптомы нехватки памяти:

JavaScript heap out of memory

или:

Allocation failed - JavaScript heap out of memory

Async режим

По умолчанию в development-режиме плагин работает асинхронно.

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

  • Webpack emit не блокируется;
  • bundle может быть сгенерирован до завершения type checking;
  • ошибки появляются позже в консоли.

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

new ForkTsCheckerWebpackPlugin({
  async: true,
});

Синхронный режим

Для CI или production build часто используют:

new ForkTsCheckerWebpackPlugin({
  async: false,
});

Теперь:

  • сборка остановится при ошибках типов;
  • emit не произойдёт до завершения проверки.

Это особенно важно для production deployment.


Интеграция с Webpack Dev Server

Плагин хорошо работает вместе с:

  • webpack-dev-server;
  • HMR;
  • watch mode.

Пример:

devServer: {
  hot: true,
}

Ошибки типов появляются:

  • в terminal;
  • в browser overlay.

Overlay ошибок

Современные dev-server конфигурации показывают TypeScript ошибки прямо в браузере.

Например:

ERROR in src/app.ts:15:7
TS2322: Type 'string' is not assignable to type 'number'

Это существенно ускоряет debugging.


Поддержка ESLint

Плагин умеет запускать ESLint параллельно с type checking.

Пример:

new ForkTsCheckerWebpackPlugin({
  eslint: {
    files: './src/**/*.{ts,tsx}',
  },
});

Такой режим позволяет:

  • не блокировать Webpack;
  • ускорять linting;
  • централизовать diagnostics.

Отличие от eslint-webpack-plugin

fork-ts-checker-webpack-plugin

Фокус:

  • TypeScript diagnostics;
  • semantic analysis;
  • type checking.

eslint-webpack-plugin

Фокус:

  • style rules;
  • code quality;
  • formatting.

Часто они используются одновременно.


Работа с React и TSX

Плагин полностью поддерживает:

const App: React.FC = () => {
  return <div>Hello</div>;
};

Конфигурация:

resolve: {
  extensions: ['.tsx', '.ts', '.js'],
}

Использование в монорепозиториях

На monorepo-проектах плагин особенно полезен.

Причины:

  • огромное количество типов;
  • project references;
  • shared packages;
  • медленный semantic analysis.

Пример структуры:

packages/
  core/
  ui/
  api/
  shared/

Project References

Плагин поддерживает TypeScript project references.

Пример:

{
  "references": [
    { "path": "./packages/core" },
    { "path": "./packages/ui" }
  ]
}

Это позволяет:

  • кэшировать compilation units;
  • уменьшать объём повторного анализа;
  • ускорять incremental build.

Incremental mode

TypeScript поддерживает incremental compilation:

{
  "compilerOptions": {
    "incremental": true
  }
}

Плагин способен использовать преимущества incremental cache.

Результат:

  • ускоренные rebuild;
  • уменьшенная нагрузка на CPU;
  • меньше повторного анализа AST.

Поведение в CI

Типичная production-конфигурация:

new ForkTsCheckerWebpackPlugin({
  async: false,
});

В CI ошибки типов должны приводить к падению сборки.

Пример:

npm run build

при ошибке:

Build failed with TypeScript errors

Производительность на больших проектах

На крупных codebase ускорение может быть весьма значительным.

Типичная динамика:

Конфигурация Rebuild
ts-loader full type check 15–40 сек
transpileOnly + fork plugin 2–8 сек

Особенно заметна разница:

  • при HMR;
  • при watch mode;
  • при React development;
  • в NX/Turborepo монорепозиториях.

Ограничения плагина

Несмотря на преимущества, существуют особенности.

Возможность emit при ошибках

В async режиме bundle может быть создан даже при наличии type errors.

Это иногда приводит к:

  • runtime ошибкам;
  • неконсистентному состоянию dev-сборки.

Повышенное потребление памяти

Поскольку запускается отдельный процесс:

  • увеличивается общий memory footprint;
  • возможна конкуренция за CPU.

На слабых машинах это может быть заметно.


Не заменяет полноценный tsc

Плагин интегрирован в Webpack pipeline.

Однако для:

  • генерации declaration files;
  • composite builds;
  • некоторых CI сценариев;

часто всё равно используется отдельный запуск:

tsc --noEmit

Частая ошибка: отсутствуют ошибки типов

Типичная причина:

transpileOnly: true

включён, но плагин не подключён.

В результате TypeScript:

  • удаляет типы;
  • не выполняет semantic analysis.

Ошибки перестают обнаруживаться.


Частая ошибка: дублирование diagnostics

Причина:

transpileOnly: false

при использовании плагина.

Результат:

  • ошибки выводятся дважды;
  • сборка замедляется.

Частая ошибка: высокая загрузка CPU

На больших проектах TypeScript analysis может потреблять 100% CPU.

Типичные причины:

  • огромные union types;
  • deeply nested generics;
  • excessive conditional types;
  • слишком большой monorepo graph.

Практическая схема современной сборки

Наиболее распространённая production-связка:

Webpack
 + babel-loader
 + @babel/preset-typescript
 + fork-ts-checker-webpack-plugin

или:

Webpack
 + ts-loader (transpileOnly)
 + fork-ts-checker-webpack-plugin

Первая схема обычно быстрее.

Вторая обеспечивает более тесную интеграцию с TypeScript compiler pipeline.


Когда плагин особенно необходим

Использование fork-ts-checker-webpack-plugin практически обязательно при:

  • больших React-приложениях;
  • enterprise TypeScript codebase;
  • монорепозиториях;
  • сложных generic-типах;
  • долгих rebuild;
  • медленном HMR;
  • большом количестве TSX-файлов.

На маленьких проектах разница может быть не столь заметной.


Взаимодействие с source maps

Плагин не влияет напрямую на генерацию source maps.

Но благодаря ускорению transpilation можно использовать:

devtool: 'eval-source-map'

без серьёзного ухудшения rebuild performance.


Диагностика производительности

Для анализа bottleneck-проектов часто проверяют:

  • время semantic analysis;
  • количество TS файлов;
  • размер dependency graph;
  • использование project references;
  • memory consumption.

Особенно важно контролировать:

Files: 5000+
Types: 100000+
Memory: 2GB+

При таких масштабах разделение transpilation и type checking становится критически важным.