При работе с TypeScript в связке с Webpack часто возникает проблема:
полноценная проверка типов существенно замедляет сборку. Особенно это
заметно в крупных проектах, где тысячи .ts и
.tsx файлов проходят через ts-loader.
Для ускорения сборки используется режим transpileOnly. В
этом режиме TypeScript-компилятор выполняет только транспиляцию
кода:
При этом полностью отключается:
Главная идея заключается в разделении задач:
| Процесс | Ответственность |
|---|---|
| Webpack + ts-loader | Быстрая сборка |
tsc --noEmit |
Полная проверка типов |
Такой подход стал стандартом для крупных frontend-проектов.
ts-loaderБез transpileOnly loader вызывает полноценный TypeScript
Compiler API:
{
test: /\.ts$/,
use: 'ts-loader'
}
Внутри происходит:
Фактически TypeScript выполняет почти ту же работу, что и команда:
tsc
В результате:
transpileOnlyОпция включает облегчённый режим работы loader-а:
{
loader: 'ts-loader',
options: {
transpileOnly: true
}
}
Теперь TypeScript выполняет только:
TypeScript -> JavaScript
Без анализа типов.
Это означает:
В реальных проектах ускорение обычно выглядит так:
| Размер проекта | Обычная сборка | transpileOnly |
|---|---|---|
| Малый | 3 сек | 2 сек |
| Средний | 15 сек | 6 сек |
| Крупный | 60+ сек | 15–20 сек |
Наибольший эффект наблюдается:
npm install webpack webpack-cli typescript ts-loader --save-dev
const path = require('path');
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
}
}
}
]
}
};
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"sourceMap": true
}
}
transpileOnlyПосле включения режима TypeScript перестаёт сообщать об ошибках типов.
Пример:
const age: number = '25';
При обычной сборке:
Type 'string' is not assignable to type 'number'
При transpileOnly:
Это ключевая особенность режима.
tsc обязателенБез проверки типов проект становится потенциально нестабильным.
Поэтому применяется отдельная команда:
tsc --noEmit
Она выполняет:
Но при этом не создаёт JS-файлы.
Опция:
--noEmit
отключает генерацию выходных файлов.
Современная архитектура TypeScript-проекта обычно выглядит так:
| Инструмент | Задача |
|---|---|
| Webpack | Сборка |
| ts-loader transpileOnly | Трансформация TS → JS |
| tsc –noEmit | Проверка типов |
| ESLint | Линтинг |
| Babel/SWC/esbuild | Дополнительная трансформация |
Такой подход обеспечивает:
{
"scripts": {
"build": "webpack",
"typecheck": "tsc --noEmit",
"dev": "webpack serve"
}
}
Теперь:
npm run dev
используется для быстрой разработки.
А:
npm run typecheck
для полной валидации типов.
Обычно pipeline строится так:
npm run typecheck
npm run build
Либо:
tsc --noEmit && webpack
Это гарантирует:
fork-ts-checker-webpack-pluginОтдельный запуск tsc — не единственный вариант.
Существует плагин:
npm install fork-ts-checker-webpack-plugin --save-dev
Он переносит проверку типов в отдельный процесс.
const ForkTsCheckerWebpackPlugin = require(
'fork-ts-checker-webpack-plugin'
);
module.exports = {
module: {
rules: [
{
test: /\.ts$/,
loader: 'ts-loader',
options: {
transpileOnly: true
}
}
]
},
plugins: [
new ForkTsCheckerWebpackPlugin()
]
};
Архитектура становится следующей:
Webpack Thread
└─ transpilation only
Separate Worker Process
└─ type checking
Преимущества:
| Подход | Скорость | Проверка типов | Сложность |
|---|---|---|---|
| ts-loader обычный | Низкая | Да | Низкая |
| transpileOnly + tsc | Высокая | Да | Средняя |
| transpileOnly + ForkTsChecker | Очень высокая | Да | Средняя |
transpileOnly особенно важен в больших проектахTypeScript type checker плохо масштабируется при:
В некоторых enterprise-проектах именно type checker становится главным bottleneck сборки.
Трансформация кода при этом занимает значительно меньше времени.
type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>;
};
При огромных объектах TypeScript может тратить значительное время на recursive type analysis.
transpileOnly полностью исключает эти вычисления из
Webpack pipeline.
Даже при отключённой проверке типов source maps продолжают работать:
{
"compilerOptions": {
"sourceMap": true
}
}
Debugging остаётся полноценным:
Очень распространённая архитектура:
ts-loader (transpileOnly)
↓
babel-loader
↓
webpack
Либо:
Babel TypeScript preset
↓
Webpack
Причина в том, что Babel вообще не умеет проверять типы.
Поэтому отдельный запуск:
tsc --noEmit
становится обязательным.
module.exports = {
module: {
rules: [
{
test: /\.ts$/,
use: [
{
loader: 'babel-loader'
},
{
loader: 'ts-loader',
options: {
transpileOnly: true
}
}
]
}
]
}
};
Иногда TypeScript используется исключительно как syntax layer:
{
test: /\.ts$/,
loader: 'babel-loader'
}
С preset:
@babel/preset-typescript
Но тогда:
tsc.Для ускорения tsc можно включить:
{
"compilerOptions": {
"incremental": true
}
}
TypeScript создаст:
.tsbuildinfo
Повторные проверки станут значительно быстрее.
В monorepo часто применяется:
{
"compilerOptions": {
"composite": true
}
}
И references:
{
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/ui" }
]
}
Это позволяет:
transpileOnlyimport { test } from './missing';
Webpack может не обнаружить часть ошибок типов до runtime.
function log<T extends number>(value: T) {}
log('hello');
Ошибка пропадёт во время сборки.
<Button size={123} />
Bundle будет создан даже при несовместимых типах.
Даже если Webpack работает без проверки типов:
продолжают использовать TypeScript Language Server.
Поэтому:
Это одна из причин популярности transpileOnly.
thread-loaderДополнительное ускорение:
npm install thread-loader --save-dev
{
test: /\.ts$/,
use: [
'thread-loader',
{
loader: 'ts-loader',
options: {
transpileOnly: true,
happyPackMode: true
}
}
]
}
happyPackModeРежим:
happyPackMode: true
отключает некоторые внутренние механизмы синхронизации TypeScript-loader-а.
Используется:
happyPackModeВозможны проблемы:
В обычных проектах режим работает стабильно.
transpileOnly использовать не стоитРежим может быть нежелателен:
В маленьких проектах выигрыш в скорости может быть минимальным.
webpack-dev-server
+ transpileOnly
+ ForkTsChecker
tsc --noEmit
webpack --mode production
Причины популярности:
В результате почти все крупные frontend-проекты используют одну из схем:
transpileOnly + tsc;transpileOnly + ForkTsChecker;Современные транспайлеры:
работают по тому же принципу:
Types are erased
without semantic checking
То есть:
Поэтому отдельный:
tsc --noEmit
остаётся обязательной частью production pipeline даже при отказе от
ts-loader.