TypeScript компилятор может замедляться при:
node_modules{
"compilerOptions": {
"skipLibCheck": true
}
}Пропускает проверку типов в .d.ts файлах зависимостей. Безопасно в большинстве случаев — при конфликтах версий типов экономит минуты.
{
"compilerOptions": {
"isolatedModules": true
}
}Каждый файл компилируется независимо — это позволяет использовать быстрые транспайлеры (esbuild, swc, Babel):
// Запрещено с isolatedModules:
const enum Status { Active, Inactive } // const enum требует межфайловый анализ
export { type User } // type-only re-export в некоторых случаях
// Разрешено:
enum Status { Active, Inactive } // обычный enum OK
export type { User } // явный type export OK{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": ".tsbuildinfo"
}
}TypeScript сохраняет информацию о предыдущей сборке и перекомпилирует только изменённые файлы. На больших проектах ускорение в 3-10 раз.
// Медленно — глубокая рекурсия типов:
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]
}
// Лучше — ограничить глубину или использовать intersections:
type Readonly1<T> = { readonly [K in keyof T]: T[K] }
// Проблема: Type instantiation is excessively deep (ошибка TS2589)
// Решение: добавить extends any[] или базовый случай рекурсии
type Flatten<T> = T extends Array<infer Item> ? Item : T# Профилировщик TypeScript:
tsc --extendedDiagnostics
# Вывод покажет:
# Files: 150
# Lines: 25000
# Identifiers: 18000
# Check time: 2.5s <- основное время
# Output generation: 0.3s
# Ещё детальнее:
tsc --generateTrace /tmp/ts-trace
# Открыть в chrome://tracing# Вместо компиляции всего:
tsc --build # перекомпилирует только изменённые пакеты1. Используйте type-only imports:
import type { User } from './types' // не попадёт в runtime bundle2. Избегайте re-export всего:
// Медленно (TypeScript должен загрузить весь модуль):
export * from './bigModule'
// Быстрее — явный re-export нужного:
export { SpecificThing } from './bigModule'3. paths и baseUrl — не злоупотребляйте:
Большое количество path aliases замедляет разрешение модулей.
4. Разбейте большие union типы:
// Медленно: 100+ вариантов в union
type BigUnion = 'option1' | 'option2' | ... | 'option100'
// Быстрее: string с enum/const object для валидации
const OPTIONS = { OPTION1: 'option1', OPTION2: 'option2' } as const
type Option = typeof OPTIONS[keyof typeof OPTIONS]Измерение производительности: бенчмарк компиляции, кэширование результатов (как incremental), симуляция isolatedModules
// Симулируем концепции производительности TypeScript компилятора.
// Демонстрируем incremental builds, кэширование и замеры времени.
// --- Симуляция incremental компиляции ---
class IncrementalCompiler {
constructor() {
this.cache = new Map() // path -> { hash, result, time }
this.totalFiles = 0
this.cachedFiles = 0
this.compiledFiles = 0
}
// Симуляция хэша файла (в реальности — хэш содержимого)
_hashFile(path, content) {
let hash = 0
for (let i = 0; i < content.length; i++) {
hash = ((hash << 5) - hash) + content.charCodeAt(i)
hash |= 0
}
return hash.toString(36)
}
// Симуляция компиляции файла (как tsc с типами)
_compile(path, content) {
// Имитируем время компиляции пропорционально размеру
const startTime = performance ? performance.now() : Date.now()
// Дорогая операция: traverse AST, type-check, emit
let result = ''
for (let i = 0; i < content.length; i++) {
result += content[i]
}
const elapsed = (performance ? performance.now() : Date.now()) - startTime
return { js: result, declarations: result + '.d.ts', elapsed }
}
compile(files) {
const results = {}
this.totalFiles = files.length
for (const { path, content } of files) {
const hash = this._hashFile(path, content)
const cached = this.cache.get(path)
if (cached && cached.hash === hash) {
// Cache hit — используем кэш (как incremental build)
results[path] = { ...cached.result, fromCache: true }
this.cachedFiles++
} else {
// Cache miss — компилируем
const result = this._compile(path, content)
this.cache.set(path, { hash, result })
results[path] = { ...result, fromCache: false }
this.compiledFiles++
}
}
return results
}
getStats() {
return {
total: this.totalFiles,
compiled: this.compiledFiles,
fromCache: this.cachedFiles,
cacheHitRate: this.totalFiles > 0
? Math.round((this.cachedFiles / this.totalFiles) * 100) + '%'
: '0%'
}
}
reset() {
this.totalFiles = 0
this.cachedFiles = 0
this.compiledFiles = 0
}
}
// --- Симуляция глубоких дженериков vs простых ---
function measureTypeComplexity(typeName, recursionDepth) {
// Симулируем стоимость вычисления типа
// Глубокая рекурсия = экспоненциальный рост
const cost = Math.pow(2, recursionDepth) * 0.1 // мс
return { typeName, recursionDepth, estimatedMs: cost.toFixed(2) }
}
// --- Симуляция isolatedModules проверки ---
function checkIsolatedModules(fileContent) {
const violations = []
// const enum не поддерживается в isolatedModules
if (/consts+enums+/.test(fileContent)) {
violations.push('const enum нельзя использовать с isolatedModules')
}
// namespace с implementation
if (/namespaces+w+s*{[sS]*?(?:class|function|let|const|var)/.test(fileContent)) {
violations.push('namespace с implementation несовместим с isolatedModules')
}
return {
valid: violations.length === 0,
violations
}
}
// --- Демонстрация ---
const compiler = new IncrementalCompiler()
const projectFiles = [
{ path: 'utils/date.ts', content: 'export function formatDate(d) { return d.toISOString() }' },
{ path: 'utils/math.ts', content: 'export function add(a, b) { return a + b }' },
{ path: 'services/api.ts', content: 'import { formatDate } from "../utils/date"; export const api = {}' },
{ path: 'App.ts', content: 'import { api } from "./services/api"; export default function App() {}' },
]
console.log('=== Первая сборка (без кэша) ===')
compiler.compile(projectFiles)
let stats = compiler.getStats()
console.log('Скомпилировано:', stats.compiled, 'файлов')
console.log('Из кэша:', stats.fromCache, 'файлов')
console.log('Cache hit rate:', stats.cacheHitRate)
// Вторая сборка — только один файл изменился
compiler.reset()
const updatedFiles = [
...projectFiles.slice(0, 3), // первые 3 не изменились
{ path: 'App.ts', content: 'import { api } from "./services/api"; export default function App() { return "updated" }' },
]
console.log('\n=== Вторая сборка (один файл изменился) ===')
compiler.compile(updatedFiles)
stats = compiler.getStats()
console.log('Скомпилировано:', stats.compiled, 'файлов (только изменённые)')
console.log('Из кэша:', stats.fromCache, 'файлов')
console.log('Cache hit rate:', stats.cacheHitRate)
console.log('\n=== Третья сборка (ничего не изменилось) ===')
compiler.reset()
compiler.compile(updatedFiles)
stats = compiler.getStats()
console.log('Скомпилировано:', stats.compiled, 'файлов')
console.log('Из кэша:', stats.fromCache, 'файлов')
console.log('Cache hit rate:', stats.cacheHitRate)
console.log('\n=== Стоимость дженерик-типов ===')
const types = [
{ name: 'Readonly<T>', depth: 1 },
{ name: 'DeepReadonly<T>', depth: 5 },
{ name: 'DeepReadonly<T> (глубокий)', depth: 10 },
{ name: 'Рекурсивный тип', depth: 15 },
]
types.forEach(({ name, depth }) => {
const result = measureTypeComplexity(name, depth)
console.log(` ${name}: ~${result.estimatedMs}ms (глубина ${depth})`)
})
console.log('\n=== Проверка isolatedModules ===')
const files_to_check = [
{ name: 'valid.ts', content: 'export type { User }; enum Status { Active }' },
{ name: 'invalid.ts', content: 'export const enum Status { Active, Inactive }' },
]
files_to_check.forEach(({ name, content }) => {
const { valid, violations } = checkIsolatedModules(content)
console.log(` ${name}: ${valid ? 'OK' : 'VIOLATION: ' + violations[0]}`)
})TypeScript компилятор может замедляться при:
node_modules{
"compilerOptions": {
"skipLibCheck": true
}
}Пропускает проверку типов в .d.ts файлах зависимостей. Безопасно в большинстве случаев — при конфликтах версий типов экономит минуты.
{
"compilerOptions": {
"isolatedModules": true
}
}Каждый файл компилируется независимо — это позволяет использовать быстрые транспайлеры (esbuild, swc, Babel):
// Запрещено с isolatedModules:
const enum Status { Active, Inactive } // const enum требует межфайловый анализ
export { type User } // type-only re-export в некоторых случаях
// Разрешено:
enum Status { Active, Inactive } // обычный enum OK
export type { User } // явный type export OK{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": ".tsbuildinfo"
}
}TypeScript сохраняет информацию о предыдущей сборке и перекомпилирует только изменённые файлы. На больших проектах ускорение в 3-10 раз.
// Медленно — глубокая рекурсия типов:
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]
}
// Лучше — ограничить глубину или использовать intersections:
type Readonly1<T> = { readonly [K in keyof T]: T[K] }
// Проблема: Type instantiation is excessively deep (ошибка TS2589)
// Решение: добавить extends any[] или базовый случай рекурсии
type Flatten<T> = T extends Array<infer Item> ? Item : T# Профилировщик TypeScript:
tsc --extendedDiagnostics
# Вывод покажет:
# Files: 150
# Lines: 25000
# Identifiers: 18000
# Check time: 2.5s <- основное время
# Output generation: 0.3s
# Ещё детальнее:
tsc --generateTrace /tmp/ts-trace
# Открыть в chrome://tracing# Вместо компиляции всего:
tsc --build # перекомпилирует только изменённые пакеты1. Используйте type-only imports:
import type { User } from './types' // не попадёт в runtime bundle2. Избегайте re-export всего:
// Медленно (TypeScript должен загрузить весь модуль):
export * from './bigModule'
// Быстрее — явный re-export нужного:
export { SpecificThing } from './bigModule'3. paths и baseUrl — не злоупотребляйте:
Большое количество path aliases замедляет разрешение модулей.
4. Разбейте большие union типы:
// Медленно: 100+ вариантов в union
type BigUnion = 'option1' | 'option2' | ... | 'option100'
// Быстрее: string с enum/const object для валидации
const OPTIONS = { OPTION1: 'option1', OPTION2: 'option2' } as const
type Option = typeof OPTIONS[keyof typeof OPTIONS]Измерение производительности: бенчмарк компиляции, кэширование результатов (как incremental), симуляция isolatedModules
// Симулируем концепции производительности TypeScript компилятора.
// Демонстрируем incremental builds, кэширование и замеры времени.
// --- Симуляция incremental компиляции ---
class IncrementalCompiler {
constructor() {
this.cache = new Map() // path -> { hash, result, time }
this.totalFiles = 0
this.cachedFiles = 0
this.compiledFiles = 0
}
// Симуляция хэша файла (в реальности — хэш содержимого)
_hashFile(path, content) {
let hash = 0
for (let i = 0; i < content.length; i++) {
hash = ((hash << 5) - hash) + content.charCodeAt(i)
hash |= 0
}
return hash.toString(36)
}
// Симуляция компиляции файла (как tsc с типами)
_compile(path, content) {
// Имитируем время компиляции пропорционально размеру
const startTime = performance ? performance.now() : Date.now()
// Дорогая операция: traverse AST, type-check, emit
let result = ''
for (let i = 0; i < content.length; i++) {
result += content[i]
}
const elapsed = (performance ? performance.now() : Date.now()) - startTime
return { js: result, declarations: result + '.d.ts', elapsed }
}
compile(files) {
const results = {}
this.totalFiles = files.length
for (const { path, content } of files) {
const hash = this._hashFile(path, content)
const cached = this.cache.get(path)
if (cached && cached.hash === hash) {
// Cache hit — используем кэш (как incremental build)
results[path] = { ...cached.result, fromCache: true }
this.cachedFiles++
} else {
// Cache miss — компилируем
const result = this._compile(path, content)
this.cache.set(path, { hash, result })
results[path] = { ...result, fromCache: false }
this.compiledFiles++
}
}
return results
}
getStats() {
return {
total: this.totalFiles,
compiled: this.compiledFiles,
fromCache: this.cachedFiles,
cacheHitRate: this.totalFiles > 0
? Math.round((this.cachedFiles / this.totalFiles) * 100) + '%'
: '0%'
}
}
reset() {
this.totalFiles = 0
this.cachedFiles = 0
this.compiledFiles = 0
}
}
// --- Симуляция глубоких дженериков vs простых ---
function measureTypeComplexity(typeName, recursionDepth) {
// Симулируем стоимость вычисления типа
// Глубокая рекурсия = экспоненциальный рост
const cost = Math.pow(2, recursionDepth) * 0.1 // мс
return { typeName, recursionDepth, estimatedMs: cost.toFixed(2) }
}
// --- Симуляция isolatedModules проверки ---
function checkIsolatedModules(fileContent) {
const violations = []
// const enum не поддерживается в isolatedModules
if (/consts+enums+/.test(fileContent)) {
violations.push('const enum нельзя использовать с isolatedModules')
}
// namespace с implementation
if (/namespaces+w+s*{[sS]*?(?:class|function|let|const|var)/.test(fileContent)) {
violations.push('namespace с implementation несовместим с isolatedModules')
}
return {
valid: violations.length === 0,
violations
}
}
// --- Демонстрация ---
const compiler = new IncrementalCompiler()
const projectFiles = [
{ path: 'utils/date.ts', content: 'export function formatDate(d) { return d.toISOString() }' },
{ path: 'utils/math.ts', content: 'export function add(a, b) { return a + b }' },
{ path: 'services/api.ts', content: 'import { formatDate } from "../utils/date"; export const api = {}' },
{ path: 'App.ts', content: 'import { api } from "./services/api"; export default function App() {}' },
]
console.log('=== Первая сборка (без кэша) ===')
compiler.compile(projectFiles)
let stats = compiler.getStats()
console.log('Скомпилировано:', stats.compiled, 'файлов')
console.log('Из кэша:', stats.fromCache, 'файлов')
console.log('Cache hit rate:', stats.cacheHitRate)
// Вторая сборка — только один файл изменился
compiler.reset()
const updatedFiles = [
...projectFiles.slice(0, 3), // первые 3 не изменились
{ path: 'App.ts', content: 'import { api } from "./services/api"; export default function App() { return "updated" }' },
]
console.log('\n=== Вторая сборка (один файл изменился) ===')
compiler.compile(updatedFiles)
stats = compiler.getStats()
console.log('Скомпилировано:', stats.compiled, 'файлов (только изменённые)')
console.log('Из кэша:', stats.fromCache, 'файлов')
console.log('Cache hit rate:', stats.cacheHitRate)
console.log('\n=== Третья сборка (ничего не изменилось) ===')
compiler.reset()
compiler.compile(updatedFiles)
stats = compiler.getStats()
console.log('Скомпилировано:', stats.compiled, 'файлов')
console.log('Из кэша:', stats.fromCache, 'файлов')
console.log('Cache hit rate:', stats.cacheHitRate)
console.log('\n=== Стоимость дженерик-типов ===')
const types = [
{ name: 'Readonly<T>', depth: 1 },
{ name: 'DeepReadonly<T>', depth: 5 },
{ name: 'DeepReadonly<T> (глубокий)', depth: 10 },
{ name: 'Рекурсивный тип', depth: 15 },
]
types.forEach(({ name, depth }) => {
const result = measureTypeComplexity(name, depth)
console.log(` ${name}: ~${result.estimatedMs}ms (глубина ${depth})`)
})
console.log('\n=== Проверка isolatedModules ===')
const files_to_check = [
{ name: 'valid.ts', content: 'export type { User }; enum Status { Active }' },
{ name: 'invalid.ts', content: 'export const enum Status { Active, Inactive }' },
]
files_to_check.forEach(({ name, content }) => {
const { valid, violations } = checkIsolatedModules(content)
console.log(` ${name}: ${valid ? 'OK' : 'VIOLATION: ' + violations[0]}`)
})Реализуй `memoizeType(fn)` — функцию кэширования результатов по аргументам (как TypeScript кэширует вычисленные типы). Кэш ключ — JSON.stringify аргументов. Также добавь `stats()` метод возвращающий `{ hits, misses, ratio }`. Затем реализуй `createLRUCache(maxSize)` — кэш с вытеснением наименее недавно использованных (Least Recently Used).
В memoizeType: key = JSON.stringify(args), если cache.has(key) — hits++, return cache.get(key), иначе misses++, result = fn(...args), cache.set(key, result), return result. В LRU.get: переставить в конец через delete+set. В LRU.set: после вставки если size > maxSize — удалить cache.keys().next().value.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке