ts-loader: базовая настройка

ts-loader — официальный загрузчик для интеграции TypeScript с Webpack. Его задача заключается в передаче .ts и .tsx файлов компилятору TypeScript внутри процесса сборки Webpack.

Связка Webpack + TypeScript решает сразу несколько задач:

  • компиляция TypeScript в JavaScript;
  • поддержка модульной архитектуры;
  • объединение файлов в бандлы;
  • работа с динамическими импортами;
  • генерация sourcemap;
  • поддержка JSX и React;
  • интеграция с Babel, PostCSS, ESLint и другими инструментами.

ts-loader использует реальный компилятор TypeScript (typescript package), поэтому поведение максимально близко к обычному tsc.


Установка зависимостей

Минимальный набор пакетов:

npm install --save-dev webpack webpack-cli typescript ts-loader

После установки появляются следующие ключевые зависимости:

Пакет Назначение
webpack Сборщик модулей
webpack-cli CLI-интерфейс Webpack
typescript Компилятор TypeScript
ts-loader Интеграция TypeScript с Webpack

Базовая структура проекта

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

project/
├─ src/
│  ├─ index.ts
│  └─ utils.ts
├─ dist/
├─ webpack.config.js
├─ tsconfig.json
├─ package.json

Создание tsconfig.json

TypeScript требует конфигурационный файл.

Минимальный вариант:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true,
    "sourceMap": true,
    "outDir": "./dist",
    "moduleResolution": "node",
    "esModuleInterop": true
  },
  "include": ["src"]
}

Основные параметры

target

Определяет версию JavaScript после компиляции.

{
  "target": "ES2020"
}

Популярные значения:

Значение Описание
ES5 Старые браузеры
ES2015 Современный базовый JS
ES2020 Современные возможности
ESNext Максимально новый JS

module

Для Webpack почти всегда используется:

{
  "module": "ESNext"
}

Webpack самостоятельно анализирует ES-модули.


strict

Включает строгую типизацию.

{
  "strict": true
}

Активирует:

  • noImplicitAny
  • strictNullChecks
  • strictFunctionTypes
  • другие проверки

sourceMap

Создание sourcemap для отладки.

{
  "sourceMap": true
}

Позволяет видеть TypeScript-код в DevTools браузера.


moduleResolution

Обычно:

{
  "moduleResolution": "node"
}

Используется алгоритм поиска модулей Node.js.


Настройка Webpack

Минимальный конфиг:

const path = require('path');

module.exports = {
  mode: 'development',

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

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

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

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

  devtool: 'source-map'
};

Разбор конфигурации

entry

Точка входа приложения:

entry: './src/index.ts'

Webpack начинает строить граф зависимостей именно отсюда.


resolve.extensions

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

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

Теперь можно писать:

import { sum } from './utils';

вместо:

import { sum } from './utils.ts';

module.rules

Правила обработки файлов.

{
  test: /\.ts$/,
  use: 'ts-loader'
}

Логика работы:

  1. Webpack находит .ts файл.
  2. Передаёт его в ts-loader.
  3. ts-loader вызывает TypeScript Compiler API.
  4. TypeScript преобразуется в JavaScript.
  5. Webpack продолжает обработку.

exclude

Исключение лишних директорий:

exclude: /node_modules/

Компиляция зависимостей обычно не требуется.


devtool

Sourcemap:

devtool: 'source-map'

Часто используемые режимы:

Значение Особенности
eval Очень быстро
eval-source-map Хорошо для dev
source-map Полные sourcemap
hidden-source-map Для production
inline-source-map Карты внутри файла

Создание TypeScript-файлов

src/utils.ts

export function sum(a: number, b: number): number {
  return a + b;
}

src/index.ts

import { sum } from './utils';

const result = sum(10, 20);

console.log(result);

Добавление scripts в package.json

{
  "scripts": {
    "build": "webpack",
    "dev": "webpack --watch"
  }
}

Режим watch

Автоматическая пересборка:

npm run dev

Webpack отслеживает изменения файлов и пересобирает проект.


Работа ts-loader внутри Webpack

ts-loader является bridge-слоем между Webpack и TypeScript Compiler API.

Схема обработки:

TypeScript File
       ↓
Webpack Rule
       ↓
ts-loader
       ↓
TypeScript Compiler
       ↓
JavaScript Output
       ↓
Webpack Bundle

Отличие ts-loader от babel-loader

ts-loader

Особенности:

  • использует настоящий TypeScript compiler;
  • выполняет type checking;
  • поддерживает project references;
  • обеспечивает полную совместимость TypeScript.

babel-loader

Особенности:

  • быстрее;
  • только transpilation;
  • не выполняет проверку типов;
  • требует отдельного type checking.

Проверка типов

Одно из главных преимуществ ts-loader — полноценная проверка типов.

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

const value: number = 'hello';

Во время сборки:

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

Webpack завершит сборку ошибкой.


Режим transpileOnly

Для ускорения сборки:

{
  test: /\.ts$/,
  use: {
    loader: 'ts-loader',
    options: {
      transpileOnly: true
    }
  }
}

В этом режиме:

  • TypeScript только компилируется;
  • type checking отключается;
  • сборка становится значительно быстрее.

Проверка типов через ForkTsCheckerWebpackPlugin

Популярная схема оптимизации:

npm install --save-dev fork-ts-checker-webpack-plugin

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

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

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

  plugins: [
    new ForkTsCheckerWebpackPlugin()
  ]
};

Преимущества схемы

Подход Скорость
Обычный ts-loader Медленнее
transpileOnly + ForkTsChecker Быстрее

Type checking выносится в отдельный процесс.


Поддержка TSX и React

Для React-проектов:

npm install react react-dom
npm install --save-dev @types/react @types/react-dom

Настройка tsconfig.json

{
  "compilerOptions": {
    "jsx": "react-jsx"
  }
}

Webpack-конфиг

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

Правило загрузчика

{
  test: /\.tsx?$/,
  use: 'ts-loader',
  exclude: /node_modules/
}

Регулярное выражение:

Выражение Файлы
/.ts$/ | Только `.ts` | | /.tsx?$/ .ts и .tsx

Пример React-компонента

type Props = {
  title: string;
};

export function Header({ title }: Props) {
  return <h1>{title}</h1>;
}

Использование нескольких tsconfig

Иногда проект имеет разные конфигурации:

  • development;
  • production;
  • tests;
  • server;
  • client.

Пример:

{
  loader: 'ts-loader',
  options: {
    configFile: 'tsconfig.build.json'
  }
}

Настройка alias

Webpack:

resolve: {
  alias: {
    '@': path.resolve(__dirname, 'src')
  }
}

TypeScript:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

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

import { api } from '@/services/api';

Inline source map

Иногда используется:

devtool: 'inline-source-map'

Особенности:

  • sourcemap внедряется в bundle;
  • удобно для dev;
  • увеличивает размер файлов.

Production-конфигурация

Пример production-сборки:

const path = require('path');

module.exports = {
  mode: 'production',

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

  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
    clean: true
  },

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

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

Частые ошибки

Cannot find module

Ошибка:

Cannot find module './app'

Причины:

  • отсутствует файл;
  • ошибка регистра;
  • неверный alias;
  • отсутствует расширение в resolve.extensions.

TypeScript emitted no output

Причины:

  • noEmit: true;
  • ошибка в tsconfig;
  • неправильный include/exclude.

Проблемный вариант:

{
  "noEmit": true
}

Unexpected token

Ошибка:

Unexpected token <

Обычно означает:

  • .tsx не обрабатывается;
  • отсутствует JSX-конфигурация;
  • loader не применился.

Совместное использование с Babel

Популярная архитектура:

TypeScript
    ↓
ts-loader
    ↓
babel-loader
    ↓
Webpack

Пример:

{
  test: /\.ts$/,
  use: [
    'babel-loader',
    'ts-loader'
  ]
}

Порядок выполнения loaders идёт справа налево.


Кэширование

Webpack 5 поддерживает filesystem cache.

module.exports = {
  cache: {
    type: 'filesystem'
  }
};

Это заметно ускоряет повторные сборки.


Incremental compilation

TypeScript поддерживает incremental build.

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

Создаётся файл:

tsconfig.tsbuildinfo

Он содержит информацию о предыдущей компиляции.


HappyPackMode

Опция:

options: {
  happyPackMode: true
}

Используется вместе с:

  • thread-loader;
  • HappyPack.

Уменьшает часть внутренних проверок ради производительности.


Thread-loader

Параллельная обработка:

npm install --save-dev thread-loader

Пример:

{
  test: /\.ts$/,
  use: [
    'thread-loader',
    {
      loader: 'ts-loader',
      options: {
        happyPackMode: true
      }
    }
  ]
}

Разделение конфигураций

Частая архитектура:

webpack.common.js
webpack.dev.js
webpack.prod.js

Общая часть:

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

  module: {
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader'
      }
    ]
  }
};

Использование ESM-конфигурации Webpack

При "type": "module":

import path from 'path';

export default {
  entry: './src/index.ts'
};

Node.js типы

Для Node.js-проектов:

npm install --save-dev @types/node

И настройка:

{
  "compilerOptions": {
    "types": ["node"]
  }
}

Работа с declaration files

Генерация .d.ts:

{
  "declaration": true
}

Результат:

dist/
├─ index.js
├─ index.d.ts

Особенно важно для библиотек.


EmitDecoratorMetadata и decorators

Для NestJS и других framework:

{
  "experimentalDecorators": true,
  "emitDecoratorMetadata": true
}

Оптимальная базовая конфигурация

webpack.config.js

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'),
    clean: true
  },

  cache: {
    type: 'filesystem'
  },

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

  devtool: 'eval-source-map',

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

  plugins: [
    new ForkTsCheckerWebpackPlugin()
  ]
};

Оптимальный tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "sourceMap": true,
    "incremental": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}

Когда использовать ts-loader

ts-loader особенно полезен в случаях:

  • требуется полноценный TypeScript compiler;
  • нужен строгий type checking;
  • используется сложная TS-конфигурация;
  • применяются project references;
  • необходима максимальная совместимость с TypeScript ecosystem;
  • проект ориентирован на надёжность типизации.

Для больших production-проектов наиболее распространённой считается схема:

ts-loader (transpileOnly)
            +
ForkTsCheckerWebpackPlugin

Она обеспечивает баланс между скоростью сборки и полноценной проверкой типов.