Можно разделить конфигурацию на базовую и специализированные:
// tsconfig.base.json — общие настройки
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
// tsconfig.json — для разработки
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"sourceMap": true
}
}
// tsconfig.build.json — для сборки
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"noUnusedLocals": true,
"noUnusedParameters": true,
"declaration": true,
"outDir": "./dist"
},
"exclude": ["**/*.test.ts", "**/*.spec.ts"]
}Эти два параметра независимы и часто путаются:
{
"compilerOptions": {
// target: какой JS выдаёт компилятор
"target": "ES2020", // стрелки, async/await, опциональная цепочка
// module: формат модулей в скомпилированном коде
"module": "CommonJS", // require() — для Node.js
"module": "ESNext", // import/export — для браузеров/бандлеров
"module": "Node16", // для Node.js 16+
}
}Типичные комбинации:
target: ES2022, module: CommonJS или module: Node16target: ES2020, module: ESNext{
"compilerOptions": {
// Без lib TypeScript использует дефолтные типы по target
"lib": ["ES2022", "DOM", "DOM.Iterable"]
// ES2022 — Promise.allSettled, Array.at(), Object.hasOwn()
// DOM — window, document, HTMLElement
// DOM.Iterable — NodeList.forEach(), итерация по коллекциям
}
}Для Node.js без DOM:
{ "lib": ["ES2022"] }composite: true включает инкрементальную сборку для монорепо:
// packages/utils/tsconfig.json
{
"compilerOptions": {
"composite": true, // обязательно для project references
"declaration": true, // генерирует .d.ts
"declarationMap": true // source maps для .d.ts
}
}// packages/app/tsconfig.json
{
"compilerOptions": { "composite": true },
"references": [
{ "path": "../utils" }, // зависит от utils
{ "path": "../shared" }
]
}# Сборка всех проектов в правильном порядке:
tsc --build
tsc -b # короткая форма
# Только один проект:
tsc -b packages/app{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": ".tsbuildinfo" // кэш-файл
}
}При повторной компиляции TypeScript перекомпилирует только изменённые файлы.
{
"compilerOptions": {
"skipLibCheck": true, // не проверять .d.ts файлы зависимостей — ускоряет
"isolatedModules": true, // каждый файл независим — требует babel/esbuild транспиляции
"noEmit": true, // только проверка типов, без генерации файлов
"resolveJsonModule": true, // import data from './data.json'
"allowSyntheticDefaultImports": true, // import React from 'react'
"esModuleInterop": true, // совместимость CommonJS и ESM импортов
}
}{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"exactOptionalPropertyTypes": true,
"skipLibCheck": true,
"incremental": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
}
}Симуляция системы tsconfig: наследование конфигов, слияние опций, валидация target/module совместимости
// Симулируем систему конфигурации TypeScript компилятора.
// Демонстрируем extends, target vs module, lib, composite.
// --- Встроенные конфиги (как @tsconfig/recommended) ---
const PRESET_CONFIGS = {
base: {
strict: true,
esModuleInterop: true,
skipLibCheck: true,
forceConsistentCasingInFileNames: true,
declaration: false,
},
'node-lts': {
extends: 'base',
target: 'ES2022',
lib: ['ES2022'],
module: 'CommonJS',
moduleResolution: 'node',
outDir: './dist',
},
'node-esm': {
extends: 'base',
target: 'ES2022',
lib: ['ES2022'],
module: 'Node16',
moduleResolution: 'Node16',
},
'react-app': {
extends: 'base',
target: 'ES2020',
lib: ['ES2020', 'DOM', 'DOM.Iterable'],
module: 'ESNext',
moduleResolution: 'bundler',
jsx: 'react-jsx',
sourceMap: true,
},
library: {
extends: 'base',
target: 'ES2020',
module: 'ESNext',
declaration: true,
declarationMap: true,
composite: true,
incremental: true,
}
}
// --- Резолвер extends ---
function resolveConfig(configName, visited = new Set()) {
if (visited.has(configName)) {
throw new Error(`Circular extends: ${configName}`)
}
visited.add(configName)
const config = PRESET_CONFIGS[configName]
if (!config) return {}
if (config.extends) {
const base = resolveConfig(config.extends, visited)
const { extends: _, ...rest } = config
// Merge: специфичный конфиг перекрывает базовый
return mergeDeep(base, rest)
}
return { ...config }
}
function mergeDeep(base, override) {
const result = { ...base }
for (const [key, val] of Object.entries(override)) {
if (Array.isArray(val) && Array.isArray(result[key])) {
result[key] = [...new Set([...result[key], ...val])]
} else {
result[key] = val
}
}
return result
}
// --- Валидация совместимости target/module ---
const TARGET_FEATURES = {
'ES5': ['var', 'function', 'prototype'],
'ES2015': ['arrow', 'class', 'let', 'const', 'Promise', 'Symbol'],
'ES2017': ['async/await', 'Object.entries', 'Object.values'],
'ES2020': ['optional-chaining', 'nullish-coalescing', 'BigInt'],
'ES2022': ['top-level-await', 'class-fields', 'Array.at', 'Object.hasOwn'],
'ESNext': ['latest-features'],
}
function getCompatibilityWarnings(config) {
const warnings = []
// isolatedModules несовместим с const enum
if (config.isolatedModules && config.module !== 'CommonJS') {
// OK
}
// composite требует declaration
if (config.composite && !config.declaration) {
warnings.push('composite: true требует declaration: true')
}
// DOM lib без браузерного target — необычно
if (config.lib?.includes('DOM') && config.module === 'CommonJS') {
warnings.push('lib: DOM с module: CommonJS — обычно это Node.js без DOM')
}
// incremental лучше с tsBuildInfoFile
if (config.incremental && !config.tsBuildInfoFile) {
warnings.push('Рекомендуется указать tsBuildInfoFile для incremental')
}
return warnings
}
// --- Демонстрация ---
console.log('=== Резолвинг пресетов ===')
;['node-lts', 'react-app', 'library'].forEach(preset => {
const resolved = resolveConfig(preset)
console.log(`\n[${preset}]`)
console.log(' target:', resolved.target)
console.log(' module:', resolved.module)
console.log(' lib:', resolved.lib?.join(', ') || 'default')
console.log(' strict:', resolved.strict)
if (resolved.composite) console.log(' composite:', resolved.composite)
if (resolved.jsx) console.log(' jsx:', resolved.jsx)
})
console.log('\n=== Кастомный конфиг с extends ===')
const customConfig = mergeDeep(
resolveConfig('react-app'),
{
noUnusedLocals: true,
noUnusedParameters: true,
noImplicitReturns: true,
baseUrl: '.',
paths: { '@/*': ['src/*'] }
}
)
console.log('Resolved custom config:')
console.log(' strict:', customConfig.strict)
console.log(' paths:', customConfig.paths)
console.log(' noUnusedLocals:', customConfig.noUnusedLocals)
console.log('\n=== Проверка совместимости ===')
const compositeWithoutDeclaration = { composite: true, declaration: false }
console.log('composite без declaration:', getCompatibilityWarnings(compositeWithoutDeclaration))
const nodeWithDom = { lib: ['ES2022', 'DOM'], module: 'CommonJS', composite: true, declaration: true }
console.log('Node + DOM:', getCompatibilityWarnings(nodeWithDom))
const incrementalNoFile = { incremental: true }
console.log('incremental без tsBuildInfoFile:', getCompatibilityWarnings(incrementalNoFile))Можно разделить конфигурацию на базовую и специализированные:
// tsconfig.base.json — общие настройки
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
// tsconfig.json — для разработки
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"sourceMap": true
}
}
// tsconfig.build.json — для сборки
{
"extends": "./tsconfig.base.json",
"compilerOptions": {
"noUnusedLocals": true,
"noUnusedParameters": true,
"declaration": true,
"outDir": "./dist"
},
"exclude": ["**/*.test.ts", "**/*.spec.ts"]
}Эти два параметра независимы и часто путаются:
{
"compilerOptions": {
// target: какой JS выдаёт компилятор
"target": "ES2020", // стрелки, async/await, опциональная цепочка
// module: формат модулей в скомпилированном коде
"module": "CommonJS", // require() — для Node.js
"module": "ESNext", // import/export — для браузеров/бандлеров
"module": "Node16", // для Node.js 16+
}
}Типичные комбинации:
target: ES2022, module: CommonJS или module: Node16target: ES2020, module: ESNext{
"compilerOptions": {
// Без lib TypeScript использует дефолтные типы по target
"lib": ["ES2022", "DOM", "DOM.Iterable"]
// ES2022 — Promise.allSettled, Array.at(), Object.hasOwn()
// DOM — window, document, HTMLElement
// DOM.Iterable — NodeList.forEach(), итерация по коллекциям
}
}Для Node.js без DOM:
{ "lib": ["ES2022"] }composite: true включает инкрементальную сборку для монорепо:
// packages/utils/tsconfig.json
{
"compilerOptions": {
"composite": true, // обязательно для project references
"declaration": true, // генерирует .d.ts
"declarationMap": true // source maps для .d.ts
}
}// packages/app/tsconfig.json
{
"compilerOptions": { "composite": true },
"references": [
{ "path": "../utils" }, // зависит от utils
{ "path": "../shared" }
]
}# Сборка всех проектов в правильном порядке:
tsc --build
tsc -b # короткая форма
# Только один проект:
tsc -b packages/app{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": ".tsbuildinfo" // кэш-файл
}
}При повторной компиляции TypeScript перекомпилирует только изменённые файлы.
{
"compilerOptions": {
"skipLibCheck": true, // не проверять .d.ts файлы зависимостей — ускоряет
"isolatedModules": true, // каждый файл независим — требует babel/esbuild транспиляции
"noEmit": true, // только проверка типов, без генерации файлов
"resolveJsonModule": true, // import data from './data.json'
"allowSyntheticDefaultImports": true, // import React from 'react'
"esModuleInterop": true, // совместимость CommonJS и ESM импортов
}
}{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022", "DOM"],
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noImplicitReturns": true,
"exactOptionalPropertyTypes": true,
"skipLibCheck": true,
"incremental": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
}
}Симуляция системы tsconfig: наследование конфигов, слияние опций, валидация target/module совместимости
// Симулируем систему конфигурации TypeScript компилятора.
// Демонстрируем extends, target vs module, lib, composite.
// --- Встроенные конфиги (как @tsconfig/recommended) ---
const PRESET_CONFIGS = {
base: {
strict: true,
esModuleInterop: true,
skipLibCheck: true,
forceConsistentCasingInFileNames: true,
declaration: false,
},
'node-lts': {
extends: 'base',
target: 'ES2022',
lib: ['ES2022'],
module: 'CommonJS',
moduleResolution: 'node',
outDir: './dist',
},
'node-esm': {
extends: 'base',
target: 'ES2022',
lib: ['ES2022'],
module: 'Node16',
moduleResolution: 'Node16',
},
'react-app': {
extends: 'base',
target: 'ES2020',
lib: ['ES2020', 'DOM', 'DOM.Iterable'],
module: 'ESNext',
moduleResolution: 'bundler',
jsx: 'react-jsx',
sourceMap: true,
},
library: {
extends: 'base',
target: 'ES2020',
module: 'ESNext',
declaration: true,
declarationMap: true,
composite: true,
incremental: true,
}
}
// --- Резолвер extends ---
function resolveConfig(configName, visited = new Set()) {
if (visited.has(configName)) {
throw new Error(`Circular extends: ${configName}`)
}
visited.add(configName)
const config = PRESET_CONFIGS[configName]
if (!config) return {}
if (config.extends) {
const base = resolveConfig(config.extends, visited)
const { extends: _, ...rest } = config
// Merge: специфичный конфиг перекрывает базовый
return mergeDeep(base, rest)
}
return { ...config }
}
function mergeDeep(base, override) {
const result = { ...base }
for (const [key, val] of Object.entries(override)) {
if (Array.isArray(val) && Array.isArray(result[key])) {
result[key] = [...new Set([...result[key], ...val])]
} else {
result[key] = val
}
}
return result
}
// --- Валидация совместимости target/module ---
const TARGET_FEATURES = {
'ES5': ['var', 'function', 'prototype'],
'ES2015': ['arrow', 'class', 'let', 'const', 'Promise', 'Symbol'],
'ES2017': ['async/await', 'Object.entries', 'Object.values'],
'ES2020': ['optional-chaining', 'nullish-coalescing', 'BigInt'],
'ES2022': ['top-level-await', 'class-fields', 'Array.at', 'Object.hasOwn'],
'ESNext': ['latest-features'],
}
function getCompatibilityWarnings(config) {
const warnings = []
// isolatedModules несовместим с const enum
if (config.isolatedModules && config.module !== 'CommonJS') {
// OK
}
// composite требует declaration
if (config.composite && !config.declaration) {
warnings.push('composite: true требует declaration: true')
}
// DOM lib без браузерного target — необычно
if (config.lib?.includes('DOM') && config.module === 'CommonJS') {
warnings.push('lib: DOM с module: CommonJS — обычно это Node.js без DOM')
}
// incremental лучше с tsBuildInfoFile
if (config.incremental && !config.tsBuildInfoFile) {
warnings.push('Рекомендуется указать tsBuildInfoFile для incremental')
}
return warnings
}
// --- Демонстрация ---
console.log('=== Резолвинг пресетов ===')
;['node-lts', 'react-app', 'library'].forEach(preset => {
const resolved = resolveConfig(preset)
console.log(`\n[${preset}]`)
console.log(' target:', resolved.target)
console.log(' module:', resolved.module)
console.log(' lib:', resolved.lib?.join(', ') || 'default')
console.log(' strict:', resolved.strict)
if (resolved.composite) console.log(' composite:', resolved.composite)
if (resolved.jsx) console.log(' jsx:', resolved.jsx)
})
console.log('\n=== Кастомный конфиг с extends ===')
const customConfig = mergeDeep(
resolveConfig('react-app'),
{
noUnusedLocals: true,
noUnusedParameters: true,
noImplicitReturns: true,
baseUrl: '.',
paths: { '@/*': ['src/*'] }
}
)
console.log('Resolved custom config:')
console.log(' strict:', customConfig.strict)
console.log(' paths:', customConfig.paths)
console.log(' noUnusedLocals:', customConfig.noUnusedLocals)
console.log('\n=== Проверка совместимости ===')
const compositeWithoutDeclaration = { composite: true, declaration: false }
console.log('composite без declaration:', getCompatibilityWarnings(compositeWithoutDeclaration))
const nodeWithDom = { lib: ['ES2022', 'DOM'], module: 'CommonJS', composite: true, declaration: true }
console.log('Node + DOM:', getCompatibilityWarnings(nodeWithDom))
const incrementalNoFile = { incremental: true }
console.log('incremental без tsBuildInfoFile:', getCompatibilityWarnings(incrementalNoFile))Реализуй `mergeConfigs(...configs)` — функцию слияния tsconfig объектов. Правила: позднейший конфиг перекрывает предыдущий, массивы объединяются (без дублей через Set), вложенный объект `paths` мержится (не перезаписывается целиком). Также реализуй `resolveExtends(config, registry)` — если у конфига есть поле `extends` (строка-ключ в registry), рекурсивно мерджит базовый конфиг с текущим.
Для массивов: [...new Set([...result[key], ...value])]. Для paths: { ...result.paths, ...value }. В resolveExtends: const resolvedBase = resolveExtends(base, registry) — рекурсия, потом mergeConfigs(resolvedBase, rest).
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке