Введение: Почему TypeScript может быть медленным?
TypeScript — мощный инструмент, который добавляет статическую типизацию в JavaScript. Однако разработчики часто сталкиваются с тем, что "из коробки" TypeScript-приложения работают медленнее, чем чистый JS. Основные причины — это избыточная транспиляция, неправильная настройка компилятора и неоптимальные паттерны кода. В этой статье мы разберем практические методы оптимизации, которые помогут ускорить как процесс сборки, так и рантайм вашего приложения.
1. Профилирование и поиск узких мест
Прежде чем что-то оптимизировать, нужно понять, что именно тормозит. Используйте встроенные инструменты браузера и Node.js.
1.1. Chrome DevTools Performance
Запишите профиль выполнения приложения. Обратите внимание на длительные задачи (Long Tasks) и частые вызовы сборщика мусора (GC). TypeScript-код после компиляции — это обычный JavaScript, поэтому все стандартные методы профилирования работают.
1.2. Анализ времени сборки (tsc --generateTrace)
Если медленная сама компиляция, используйте флаг --generateTrace:
tsc --generateTrace trace_output
Затем откройте файл trace_output/trace.json в Chrome по адресу chrome://tracing. Вы увидите, какие файлы компилируются дольше всего. Часто это большие библиотеки или файлы с избыточными импортами.
2. Настройка tsconfig.json для максимальной скорости
Неправильная конфигурация — главный враг производительности. Рассмотрим ключевые опции.
2.1. strict: true — не спешите
Режим strict включает все строгие проверки типов. Это замедляет компиляцию. Для продакшн-сборки используйте:
{
"compilerOptions": {
"strict": false,
"noImplicitAny": true,
"strictNullChecks": true
}
}
Эти две опции покрывают 90% ошибок, но работают быстрее полного strict.
2.2. Опции для ускорения сборки
incremental: true— включает инкрементальную компиляцию. TypeScript сохраняет информацию о предыдущей сборке и перекомпилирует только измененные файлы.skipLibCheck: true— отключает проверку типов в файлах деклараций (.d.ts). Это дает значительный прирост скорости, особенно если используется много сторонних библиотек.isolatedModules: true— позволяет использовать более быстрые транспиляторы (например, esbuild) вместо tsc.
Пример оптимизированной конфигурации для разработки:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"incremental": true,
"skipLibCheck": true,
"strict": false,
"noImplicitAny": true,
"strictNullChecks": true,
"outDir": "./dist"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.test.ts"]
}
3. Оптимизация рантайма: типы и структуры данных
TypeScript-типы исчезают после компиляции, но их "отпечаток" остается в виде кода, который генерирует компилятор.
3.1. Избегайте сложных условных типов в рантайме
Сложные conditional types могут генерировать много кода. Вместо этого используйте простые union-типы:
// Плохо: сложный условный тип
type ResponseType<T> = T extends string ? { data: string } : { data: number };
function handleResponse<T>(value: T): ResponseType<T> { ... }
// Хорошо: простой union
type ResponseType = { data: string } | { data: number };
function handleResponse(value: string | number): ResponseType { ... }
3.2. Используйте as const для констант
Это помогает TypeScript выводить более точные типы, что уменьшает количество проверок:
const COLORS = ['red', 'green', 'blue'] as const;
// Тип: readonly ["red", "green", "blue"]
// Вместо:
const COLORS = ['red', 'green', 'blue']; // Тип: string[]
3.3. Мемоизация дорогих вычислений
Если функция вызывается часто и возвращает одинаковый результат для одинаковых аргументов, используйте мемоизацию. Простой пример на TypeScript:
function memoize<T extends (...args: any[]) => any>(fn: T): T {
const cache = new Map<string, ReturnType<T>>();
return ((...args: Parameters<T>) => {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key)!;
}
const result = fn(...args);
cache.set(key, result);
return result;
}) as T;
}
// Использование
const expensiveCalc = memoize((a: number, b: number) => {
// Дорогой расчет
return a + b;
});
4. Ленивая загрузка и динамические импорты
Разделяйте код на чанки (chunks) с помощью динамических импортов. Это уменьшает размер начального бандла.
4.1. Динамические импорты в TypeScript
TypeScript поддерживает динамические импорты "из коробки". Пример для React-приложения:
// Вместо статического импорта
// import HeavyComponent from './HeavyComponent';
// Используем динамический импорт
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Загрузка...</div>}>
<HeavyComponent />
</Suspense>
);
}
4.2. Типизация lazy-компонентов
Чтобы TypeScript не ругался, укажите тип для lazy-компонента:
import { ComponentType, LazyExoticComponent } from 'react';
const HeavyComponent: LazyExoticComponent<ComponentType<{}>> = React.lazy(
() => import('./HeavyComponent').then(module => ({ default: module.HeavyComponent }))
);