Не нужно переписывать всё сразу. TypeScript поддерживает пошаговую миграцию: вы можете добавлять типы файл за файлом.
Начните с минимальных изменений в tsconfig.json:
{
"compilerOptions": {
"allowJs": true,
"checkJs": false,
"strict": false,
"noEmit": true
},
"include": ["src/**/*"]
}allowJs — TypeScript видит и обрабатывает .js файлыcheckJs: false — пока не проверяем JS файлы на ошибки{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"strict": false,
"noImplicitAny": false
}
}JSDoc-аннотации начинают работать:
// @ts-check (или checkJs: true в tsconfig)
/**
* @param {string} name
* @param {number} age
* @returns {{ name: string, age: number }}
*/
function createUser(name, age) {
return { name, age }
}Переименовывайте по одному: .js → .ts. Начните с утилит (листья дерева зависимостей):
utils/date.js → utils/date.ts (нет зависимостей)
utils/format.js → utils/format.ts (нет зависимостей)
services/api.js → services/api.ts (зависит от utils)
components/App.js → components/App.tsx (зависит от всего)При переименовании в .ts TypeScript начнёт жаловаться. Используйте any как временное решение:
// Временно допустимо — исправим позже
function processData(data: any) {
return data.map((item: any) => item.value)
}Включайте строгие флаги по одному:
// Этап 1:
{ "noImplicitAny": true }
// Этап 2:
{ "noImplicitAny": true, "strictNullChecks": true }
// Этап 3:
{ "strict": true }Если в проекте есть jsconfig.json — это почти готовый tsconfig.json:
// jsconfig.json (было)
{
"compilerOptions": {
"baseUrl": ".",
"paths": { "@/*": ["src/*"] }
}
}
// tsconfig.json (стало) — добавляем TypeScript-специфичные опции
{
"compilerOptions": {
"baseUrl": ".",
"paths": { "@/*": ["src/*"] },
"allowJs": true,
"target": "ES2020",
"module": "ESNext",
"strict": true
}
}**ts-migrate** — автоматическая конвертация JS → TS от Airbnb:
npx @ts-morph/ts-morph
npx ts-migrate migrate src/Добавляет any там где нет типов — потом исправляете постепенно.
// 1. Динамические ключи:
const key = 'name'
obj[key] // ошибка — используй Record<string, unknown>
// 2. Функции с неизвестным количеством аргументов:
function fn(...args: any[]) { } // временное решение
// 3. Сторонние библиотеки без типов:
// @ts-ignore или установить @types/library-name
// 4. JSON import:
import data from './data.json'
// tsconfig: "resolveJsonModule": trueСимуляция процесса миграции: анализ JS-кода, добавление JSDoc, отслеживание прогресса миграции по файлам
// Симулируем процесс миграции JS проекта на TypeScript.
// Анализируем "файлы", определяем порядок миграции, отслеживаем прогресс.
// --- Структура проекта (симуляция) ---
const projectFiles = [
{
path: 'src/utils/date.js',
deps: [],
hasJsDoc: true,
complexity: 'low',
linesOfCode: 45
},
{
path: 'src/utils/format.js',
deps: [],
hasJsDoc: false,
complexity: 'low',
linesOfCode: 30
},
{
path: 'src/utils/validation.js',
deps: ['src/utils/format.js'],
hasJsDoc: true,
complexity: 'medium',
linesOfCode: 80
},
{
path: 'src/api/client.js',
deps: ['src/utils/format.js'],
hasJsDoc: false,
complexity: 'medium',
linesOfCode: 120
},
{
path: 'src/services/UserService.js',
deps: ['src/api/client.js', 'src/utils/validation.js'],
hasJsDoc: true,
complexity: 'high',
linesOfCode: 200
},
{
path: 'src/components/UserForm.jsx',
deps: ['src/services/UserService.js', 'src/utils/validation.js'],
hasJsDoc: false,
complexity: 'high',
linesOfCode: 350
},
{
path: 'src/App.jsx',
deps: ['src/components/UserForm.jsx', 'src/services/UserService.js'],
hasJsDoc: false,
complexity: 'high',
linesOfCode: 180
}
]
// --- Алгоритм топологической сортировки (порядок миграции) ---
function topologicalSort(files) {
const sorted = []
const visited = new Set()
const fileMap = new Map(files.map(f => [f.path, f]))
function visit(file) {
if (visited.has(file.path)) return
// Сначала обрабатываем зависимости
file.deps.forEach(dep => {
if (fileMap.has(dep)) visit(fileMap.get(dep))
})
visited.add(file.path)
sorted.push(file)
}
files.forEach(f => visit(f))
return sorted
}
// --- Оценка сложности миграции ---
function estimateMigrationEffort(file) {
const complexityScore = { low: 1, medium: 2, high: 3 }[file.complexity]
const jsDocBonus = file.hasJsDoc ? -0.5 : 0 // JSDoc упрощает миграцию
const locScore = file.linesOfCode / 100
const score = complexityScore + jsDocBonus + locScore
const hours = Math.round(score * 2)
return {
score: Math.round(score * 10) / 10,
estimatedHours: hours,
priority: score < 2 ? 'start here' : score < 4 ? 'medium' : 'last'
}
}
// --- Симуляция поэтапной миграции ---
class MigrationTracker {
constructor(files) {
this.files = files
this.migrated = new Set()
this.phase = 1
}
getStatus(filePath) {
return this.migrated.has(filePath) ? '✓ migrated' : '○ pending'
}
canMigrate(file) {
// Можно мигрировать если все зависимости уже мигрированы
return file.deps.every(dep => this.migrated.has(dep))
}
migrateFile(filePath) {
const file = this.files.find(f => f.path === filePath)
if (!file) return false
if (!this.canMigrate(file)) {
console.log(` ✗ Cannot migrate ${filePath}: dependencies not migrated`)
return false
}
this.migrated.add(filePath)
const newPath = filePath.replace(/\.jsx?$/, m => m === '.jsx' ? '.tsx' : '.ts')
console.log(` ✓ Migrated: ${filePath} -> ${newPath}`)
return true
}
getProgress() {
return {
total: this.files.length,
done: this.migrated.size,
percent: Math.round((this.migrated.size / this.files.length) * 100)
}
}
}
// --- Демонстрация ---
console.log('=== Топологический порядок миграции ===')
const sorted = topologicalSort(projectFiles)
sorted.forEach((file, i) => {
const effort = estimateMigrationEffort(file)
console.log(`${i + 1}. ${file.path}`)
console.log(` deps: ${file.deps.length}, effort: ${effort.estimatedHours}h, priority: ${effort.priority}`)
})
console.log('\n=== Оценка трудозатрат ===')
const totalHours = sorted.reduce((sum, f) => sum + estimateMigrationEffort(f).estimatedHours, 0)
console.log('Общая оценка:', totalHours, 'часов')
console.log('\n=== Процесс миграции ===')
const tracker = new MigrationTracker(sorted)
// Попытка мигрировать в неправильном порядке:
console.log('Попытка мигрировать App.jsx первым:')
tracker.migrateFile('src/App.jsx') // не получится
// Правильный порядок (начинаем с листьев):
console.log('\nПравильный порядок:')
sorted.forEach(file => tracker.migrateFile(file.path))
const progress = tracker.getProgress()
console.log(`\nПрогресс: ${progress.done}/${progress.total} файлов (${progress.percent}%)`)Не нужно переписывать всё сразу. TypeScript поддерживает пошаговую миграцию: вы можете добавлять типы файл за файлом.
Начните с минимальных изменений в tsconfig.json:
{
"compilerOptions": {
"allowJs": true,
"checkJs": false,
"strict": false,
"noEmit": true
},
"include": ["src/**/*"]
}allowJs — TypeScript видит и обрабатывает .js файлыcheckJs: false — пока не проверяем JS файлы на ошибки{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"strict": false,
"noImplicitAny": false
}
}JSDoc-аннотации начинают работать:
// @ts-check (или checkJs: true в tsconfig)
/**
* @param {string} name
* @param {number} age
* @returns {{ name: string, age: number }}
*/
function createUser(name, age) {
return { name, age }
}Переименовывайте по одному: .js → .ts. Начните с утилит (листья дерева зависимостей):
utils/date.js → utils/date.ts (нет зависимостей)
utils/format.js → utils/format.ts (нет зависимостей)
services/api.js → services/api.ts (зависит от utils)
components/App.js → components/App.tsx (зависит от всего)При переименовании в .ts TypeScript начнёт жаловаться. Используйте any как временное решение:
// Временно допустимо — исправим позже
function processData(data: any) {
return data.map((item: any) => item.value)
}Включайте строгие флаги по одному:
// Этап 1:
{ "noImplicitAny": true }
// Этап 2:
{ "noImplicitAny": true, "strictNullChecks": true }
// Этап 3:
{ "strict": true }Если в проекте есть jsconfig.json — это почти готовый tsconfig.json:
// jsconfig.json (было)
{
"compilerOptions": {
"baseUrl": ".",
"paths": { "@/*": ["src/*"] }
}
}
// tsconfig.json (стало) — добавляем TypeScript-специфичные опции
{
"compilerOptions": {
"baseUrl": ".",
"paths": { "@/*": ["src/*"] },
"allowJs": true,
"target": "ES2020",
"module": "ESNext",
"strict": true
}
}**ts-migrate** — автоматическая конвертация JS → TS от Airbnb:
npx @ts-morph/ts-morph
npx ts-migrate migrate src/Добавляет any там где нет типов — потом исправляете постепенно.
// 1. Динамические ключи:
const key = 'name'
obj[key] // ошибка — используй Record<string, unknown>
// 2. Функции с неизвестным количеством аргументов:
function fn(...args: any[]) { } // временное решение
// 3. Сторонние библиотеки без типов:
// @ts-ignore или установить @types/library-name
// 4. JSON import:
import data from './data.json'
// tsconfig: "resolveJsonModule": trueСимуляция процесса миграции: анализ JS-кода, добавление JSDoc, отслеживание прогресса миграции по файлам
// Симулируем процесс миграции JS проекта на TypeScript.
// Анализируем "файлы", определяем порядок миграции, отслеживаем прогресс.
// --- Структура проекта (симуляция) ---
const projectFiles = [
{
path: 'src/utils/date.js',
deps: [],
hasJsDoc: true,
complexity: 'low',
linesOfCode: 45
},
{
path: 'src/utils/format.js',
deps: [],
hasJsDoc: false,
complexity: 'low',
linesOfCode: 30
},
{
path: 'src/utils/validation.js',
deps: ['src/utils/format.js'],
hasJsDoc: true,
complexity: 'medium',
linesOfCode: 80
},
{
path: 'src/api/client.js',
deps: ['src/utils/format.js'],
hasJsDoc: false,
complexity: 'medium',
linesOfCode: 120
},
{
path: 'src/services/UserService.js',
deps: ['src/api/client.js', 'src/utils/validation.js'],
hasJsDoc: true,
complexity: 'high',
linesOfCode: 200
},
{
path: 'src/components/UserForm.jsx',
deps: ['src/services/UserService.js', 'src/utils/validation.js'],
hasJsDoc: false,
complexity: 'high',
linesOfCode: 350
},
{
path: 'src/App.jsx',
deps: ['src/components/UserForm.jsx', 'src/services/UserService.js'],
hasJsDoc: false,
complexity: 'high',
linesOfCode: 180
}
]
// --- Алгоритм топологической сортировки (порядок миграции) ---
function topologicalSort(files) {
const sorted = []
const visited = new Set()
const fileMap = new Map(files.map(f => [f.path, f]))
function visit(file) {
if (visited.has(file.path)) return
// Сначала обрабатываем зависимости
file.deps.forEach(dep => {
if (fileMap.has(dep)) visit(fileMap.get(dep))
})
visited.add(file.path)
sorted.push(file)
}
files.forEach(f => visit(f))
return sorted
}
// --- Оценка сложности миграции ---
function estimateMigrationEffort(file) {
const complexityScore = { low: 1, medium: 2, high: 3 }[file.complexity]
const jsDocBonus = file.hasJsDoc ? -0.5 : 0 // JSDoc упрощает миграцию
const locScore = file.linesOfCode / 100
const score = complexityScore + jsDocBonus + locScore
const hours = Math.round(score * 2)
return {
score: Math.round(score * 10) / 10,
estimatedHours: hours,
priority: score < 2 ? 'start here' : score < 4 ? 'medium' : 'last'
}
}
// --- Симуляция поэтапной миграции ---
class MigrationTracker {
constructor(files) {
this.files = files
this.migrated = new Set()
this.phase = 1
}
getStatus(filePath) {
return this.migrated.has(filePath) ? '✓ migrated' : '○ pending'
}
canMigrate(file) {
// Можно мигрировать если все зависимости уже мигрированы
return file.deps.every(dep => this.migrated.has(dep))
}
migrateFile(filePath) {
const file = this.files.find(f => f.path === filePath)
if (!file) return false
if (!this.canMigrate(file)) {
console.log(` ✗ Cannot migrate ${filePath}: dependencies not migrated`)
return false
}
this.migrated.add(filePath)
const newPath = filePath.replace(/\.jsx?$/, m => m === '.jsx' ? '.tsx' : '.ts')
console.log(` ✓ Migrated: ${filePath} -> ${newPath}`)
return true
}
getProgress() {
return {
total: this.files.length,
done: this.migrated.size,
percent: Math.round((this.migrated.size / this.files.length) * 100)
}
}
}
// --- Демонстрация ---
console.log('=== Топологический порядок миграции ===')
const sorted = topologicalSort(projectFiles)
sorted.forEach((file, i) => {
const effort = estimateMigrationEffort(file)
console.log(`${i + 1}. ${file.path}`)
console.log(` deps: ${file.deps.length}, effort: ${effort.estimatedHours}h, priority: ${effort.priority}`)
})
console.log('\n=== Оценка трудозатрат ===')
const totalHours = sorted.reduce((sum, f) => sum + estimateMigrationEffort(f).estimatedHours, 0)
console.log('Общая оценка:', totalHours, 'часов')
console.log('\n=== Процесс миграции ===')
const tracker = new MigrationTracker(sorted)
// Попытка мигрировать в неправильном порядке:
console.log('Попытка мигрировать App.jsx первым:')
tracker.migrateFile('src/App.jsx') // не получится
// Правильный порядок (начинаем с листьев):
console.log('\nПравильный порядок:')
sorted.forEach(file => tracker.migrateFile(file.path))
const progress = tracker.getProgress()
console.log(`\nПрогресс: ${progress.done}/${progress.total} файлов (${progress.percent}%)`)Реализуй `createMigrationPlan(files)` — функцию планирования миграции. Каждый файл: `{ path, deps: string[], hasTypes: boolean }`. Функция возвращает массив "этапов" (waves): каждый этап содержит файлы, все зависимости которых уже включены в предыдущие этапы. Файлы без зависимостей — первый этап, их зависимые — второй, и т.д.
После обработки файла в волне, уменьши inDegree для всех файлов которые зависят от него: inDegree.set(other.path, inDegree.get(other.path) - 1). Это классический алгоритм Кана для топологической сортировки.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке