monorepo/
├── packages/
│ ├── shared/ # Общие типы и утилиты
│ │ ├── src/
│ │ ├── tsconfig.json
│ │ └── package.json
│ ├── api/ # Backend
│ │ ├── src/
│ │ ├── tsconfig.json
│ │ └── package.json
│ └── web/ # Frontend
│ ├── src/
│ ├── tsconfig.json
│ └── package.json
├── tsconfig.base.json # Общая конфигурация
└── tsconfig.json # Корневой (project references)Каждый пакет должен иметь composite: true:
// packages/shared/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true,
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src/**/*"]
}// packages/api/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true
},
"references": [
{ "path": "../shared" }
]
}
// packages/web/tsconfig.json
{
"references": [
{ "path": "../shared" }
]
}
// Корневой tsconfig.json
{
"references": [
{ "path": "packages/shared" },
{ "path": "packages/api" },
{ "path": "packages/web" }
],
"files": [] // пустой — все файлы в пакетах
}// tsconfig.base.json
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "bundler",
"resolveJsonModule": true
}
}Каждый пакет наследует:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"target": "ES2022"
}
}// packages/api/tsconfig.json
{
"compilerOptions": {
"paths": {
"@monorepo/shared": ["../shared/src/index.ts"],
"@monorepo/shared/*": ["../shared/src/*"]
}
}
}// packages/api/src/routes/users.ts
import type { User, ApiResponse } from '@monorepo/shared'// packages/shared/package.json
{
"name": "@monorepo/shared",
"version": "1.0.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
}
}// packages/api/package.json
{
"dependencies": {
"@monorepo/shared": "workspace:*" // pnpm
"@monorepo/shared": "*" // npm/yarn
}
}// turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"], // сначала собрать зависимости
"outputs": ["dist/**"]
},
"typecheck": {
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["build"]
}
}
}# Собрать всё (в правильном порядке):
turbo build
# Только изменённые пакеты:
turbo build --filter=...web # web и его зависимости# Собрать всё в правильном порядке:
tsc --build
# С watch:
tsc --build --watch
# Принудительная пересборка:
tsc --build --force
# Очистить выходные файлы:
tsc --build --cleanСимуляция монорепо: граф зависимостей пакетов, топологическая сборка, отслеживание изменений как в Turborepo
// Симулируем систему сборки монорепо.
// Демонстрируем: project references, порядок сборки, кэширование как в Turborepo.
// --- Структура монорепо ---
const packages = {
'@mono/shared': {
path: 'packages/shared',
deps: [], // нет зависимостей
files: ['types.ts', 'utils.ts', 'constants.ts'],
built: false,
hash: null
},
'@mono/config': {
path: 'packages/config',
deps: [],
files: ['index.ts'],
built: false,
hash: null
},
'@mono/api-client': {
path: 'packages/api-client',
deps: ['@mono/shared'],
files: ['client.ts', 'endpoints.ts'],
built: false,
hash: null
},
'@mono/ui': {
path: 'packages/ui',
deps: ['@mono/shared', '@mono/config'],
files: ['Button.tsx', 'Input.tsx', 'Modal.tsx'],
built: false,
hash: null
},
'@mono/web': {
path: 'apps/web',
deps: ['@mono/ui', '@mono/api-client', '@mono/shared'],
files: ['App.tsx', 'pages/Home.tsx'],
built: false,
hash: null
},
'@mono/mobile': {
path: 'apps/mobile',
deps: ['@mono/ui', '@mono/api-client'],
files: ['App.tsx'],
built: false,
hash: null
}
}
// --- Топологическая сортировка ---
function buildOrder(packages) {
const sorted = []
const visited = new Set()
const inProgress = new Set()
function visit(name) {
if (visited.has(name)) return
if (inProgress.has(name)) throw new Error('Circular dependency: ' + name)
inProgress.add(name)
const pkg = packages[name]
for (const dep of pkg.deps) {
if (!packages[dep]) throw new Error('Unknown dependency: ' + dep)
visit(dep)
}
inProgress.delete(name)
visited.add(name)
sorted.push(name)
}
Object.keys(packages).forEach(visit)
return sorted
}
// --- Определение "волн" (параллельная сборка) ---
function buildWaves(packages, order) {
const waves = []
const built = new Set()
while (built.size < order.length) {
const wave = order.filter(name =>
!built.has(name) &&
packages[name].deps.every(dep => built.has(dep))
)
if (wave.length === 0) break
waves.push(wave)
wave.forEach(name => built.add(name))
}
return waves
}
// --- Кэш сборки (как Turborepo) ---
class BuildCache {
constructor() {
this.cache = new Map()
this.hits = 0
this.misses = 0
}
_computeHash(pkgName, packages) {
const pkg = packages[pkgName]
// Хэш зависит от файлов и хэшей зависимостей
const depHashes = pkg.deps.map(dep => packages[dep].hash || 'unbuilt').join(',')
return pkgName + ':' + pkg.files.join(',') + ':' + depHashes
}
shouldRebuild(pkgName, packages) {
const pkg = packages[pkgName]
const newHash = this._computeHash(pkgName, packages)
if (this.cache.get(pkgName) === newHash) {
this.hits++
return false // не нужно пересобирать
}
this.misses++
return true
}
markBuilt(pkgName, packages) {
const hash = this._computeHash(pkgName, packages)
this.cache.set(pkgName, hash)
packages[pkgName].hash = hash
packages[pkgName].built = true
}
get stats() {
return {
hits: this.hits,
misses: this.misses,
total: this.hits + this.misses,
hitRate: this.hits + this.misses > 0
? Math.round(this.hits / (this.hits + this.misses) * 100) + '%'
: '0%'
}
}
}
// --- Симуляция сборки ---
function build(packages, changedPackages = []) {
const order = buildOrder(packages)
const waves = buildWaves(packages, order)
const cache = new BuildCache()
// Симулируем изменение файлов
changedPackages.forEach(name => {
packages[name].hash = null // инвалидируем кэш
})
console.log('Build plan:')
waves.forEach((wave, i) => {
console.log(` Wave ${i + 1} (parallel): ${wave.join(', ')}`)
})
console.log('\nBuilding...')
let rebuilt = 0
let skipped = 0
for (const wave of waves) {
for (const pkgName of wave) {
const needsRebuild = cache.shouldRebuild(pkgName, packages)
if (needsRebuild) {
console.log(` [BUILD] ${pkgName}`)
cache.markBuilt(pkgName, packages)
rebuilt++
} else {
console.log(` [SKIP] ${pkgName} (cached)`)
skipped++
}
}
}
return { rebuilt, skipped, waves: waves.length }
}
// --- Демонстрация ---
// Сбрасываем состояние
Object.values(packages).forEach(p => { p.built = false; p.hash = null })
console.log('=== Первая сборка (без кэша) ===')
const result1 = build(packages)
console.log(`\nResult: ${result1.rebuilt} rebuilt, ${result1.skipped} skipped`)
console.log('\n=== Вторая сборка (без изменений) ===')
// Сбрасываем флаги но хэши остались в packages
Object.values(packages).forEach(p => p.built = false)
const result2 = build(packages)
console.log(`\nResult: ${result2.rebuilt} rebuilt, ${result2.skipped} skipped`)
console.log('\n=== Третья сборка (изменился @mono/shared) ===')
Object.values(packages).forEach(p => p.built = false)
const result3 = build(packages, ['@mono/shared']) // изменился shared
console.log(`\nResult: ${result3.rebuilt} rebuilt, ${result3.skipped} skipped`)
console.log('(shared изменился → все зависящие тоже пересобираются)')monorepo/
├── packages/
│ ├── shared/ # Общие типы и утилиты
│ │ ├── src/
│ │ ├── tsconfig.json
│ │ └── package.json
│ ├── api/ # Backend
│ │ ├── src/
│ │ ├── tsconfig.json
│ │ └── package.json
│ └── web/ # Frontend
│ ├── src/
│ ├── tsconfig.json
│ └── package.json
├── tsconfig.base.json # Общая конфигурация
└── tsconfig.json # Корневой (project references)Каждый пакет должен иметь composite: true:
// packages/shared/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true,
"declarationMap": true,
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["src/**/*"]
}// packages/api/tsconfig.json
{
"compilerOptions": {
"composite": true,
"declaration": true
},
"references": [
{ "path": "../shared" }
]
}
// packages/web/tsconfig.json
{
"references": [
{ "path": "../shared" }
]
}
// Корневой tsconfig.json
{
"references": [
{ "path": "packages/shared" },
{ "path": "packages/api" },
{ "path": "packages/web" }
],
"files": [] // пустой — все файлы в пакетах
}// tsconfig.base.json
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "bundler",
"resolveJsonModule": true
}
}Каждый пакет наследует:
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"target": "ES2022"
}
}// packages/api/tsconfig.json
{
"compilerOptions": {
"paths": {
"@monorepo/shared": ["../shared/src/index.ts"],
"@monorepo/shared/*": ["../shared/src/*"]
}
}
}// packages/api/src/routes/users.ts
import type { User, ApiResponse } from '@monorepo/shared'// packages/shared/package.json
{
"name": "@monorepo/shared",
"version": "1.0.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
}
}
}// packages/api/package.json
{
"dependencies": {
"@monorepo/shared": "workspace:*" // pnpm
"@monorepo/shared": "*" // npm/yarn
}
}// turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"], // сначала собрать зависимости
"outputs": ["dist/**"]
},
"typecheck": {
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["build"]
}
}
}# Собрать всё (в правильном порядке):
turbo build
# Только изменённые пакеты:
turbo build --filter=...web # web и его зависимости# Собрать всё в правильном порядке:
tsc --build
# С watch:
tsc --build --watch
# Принудительная пересборка:
tsc --build --force
# Очистить выходные файлы:
tsc --build --cleanСимуляция монорепо: граф зависимостей пакетов, топологическая сборка, отслеживание изменений как в Turborepo
// Симулируем систему сборки монорепо.
// Демонстрируем: project references, порядок сборки, кэширование как в Turborepo.
// --- Структура монорепо ---
const packages = {
'@mono/shared': {
path: 'packages/shared',
deps: [], // нет зависимостей
files: ['types.ts', 'utils.ts', 'constants.ts'],
built: false,
hash: null
},
'@mono/config': {
path: 'packages/config',
deps: [],
files: ['index.ts'],
built: false,
hash: null
},
'@mono/api-client': {
path: 'packages/api-client',
deps: ['@mono/shared'],
files: ['client.ts', 'endpoints.ts'],
built: false,
hash: null
},
'@mono/ui': {
path: 'packages/ui',
deps: ['@mono/shared', '@mono/config'],
files: ['Button.tsx', 'Input.tsx', 'Modal.tsx'],
built: false,
hash: null
},
'@mono/web': {
path: 'apps/web',
deps: ['@mono/ui', '@mono/api-client', '@mono/shared'],
files: ['App.tsx', 'pages/Home.tsx'],
built: false,
hash: null
},
'@mono/mobile': {
path: 'apps/mobile',
deps: ['@mono/ui', '@mono/api-client'],
files: ['App.tsx'],
built: false,
hash: null
}
}
// --- Топологическая сортировка ---
function buildOrder(packages) {
const sorted = []
const visited = new Set()
const inProgress = new Set()
function visit(name) {
if (visited.has(name)) return
if (inProgress.has(name)) throw new Error('Circular dependency: ' + name)
inProgress.add(name)
const pkg = packages[name]
for (const dep of pkg.deps) {
if (!packages[dep]) throw new Error('Unknown dependency: ' + dep)
visit(dep)
}
inProgress.delete(name)
visited.add(name)
sorted.push(name)
}
Object.keys(packages).forEach(visit)
return sorted
}
// --- Определение "волн" (параллельная сборка) ---
function buildWaves(packages, order) {
const waves = []
const built = new Set()
while (built.size < order.length) {
const wave = order.filter(name =>
!built.has(name) &&
packages[name].deps.every(dep => built.has(dep))
)
if (wave.length === 0) break
waves.push(wave)
wave.forEach(name => built.add(name))
}
return waves
}
// --- Кэш сборки (как Turborepo) ---
class BuildCache {
constructor() {
this.cache = new Map()
this.hits = 0
this.misses = 0
}
_computeHash(pkgName, packages) {
const pkg = packages[pkgName]
// Хэш зависит от файлов и хэшей зависимостей
const depHashes = pkg.deps.map(dep => packages[dep].hash || 'unbuilt').join(',')
return pkgName + ':' + pkg.files.join(',') + ':' + depHashes
}
shouldRebuild(pkgName, packages) {
const pkg = packages[pkgName]
const newHash = this._computeHash(pkgName, packages)
if (this.cache.get(pkgName) === newHash) {
this.hits++
return false // не нужно пересобирать
}
this.misses++
return true
}
markBuilt(pkgName, packages) {
const hash = this._computeHash(pkgName, packages)
this.cache.set(pkgName, hash)
packages[pkgName].hash = hash
packages[pkgName].built = true
}
get stats() {
return {
hits: this.hits,
misses: this.misses,
total: this.hits + this.misses,
hitRate: this.hits + this.misses > 0
? Math.round(this.hits / (this.hits + this.misses) * 100) + '%'
: '0%'
}
}
}
// --- Симуляция сборки ---
function build(packages, changedPackages = []) {
const order = buildOrder(packages)
const waves = buildWaves(packages, order)
const cache = new BuildCache()
// Симулируем изменение файлов
changedPackages.forEach(name => {
packages[name].hash = null // инвалидируем кэш
})
console.log('Build plan:')
waves.forEach((wave, i) => {
console.log(` Wave ${i + 1} (parallel): ${wave.join(', ')}`)
})
console.log('\nBuilding...')
let rebuilt = 0
let skipped = 0
for (const wave of waves) {
for (const pkgName of wave) {
const needsRebuild = cache.shouldRebuild(pkgName, packages)
if (needsRebuild) {
console.log(` [BUILD] ${pkgName}`)
cache.markBuilt(pkgName, packages)
rebuilt++
} else {
console.log(` [SKIP] ${pkgName} (cached)`)
skipped++
}
}
}
return { rebuilt, skipped, waves: waves.length }
}
// --- Демонстрация ---
// Сбрасываем состояние
Object.values(packages).forEach(p => { p.built = false; p.hash = null })
console.log('=== Первая сборка (без кэша) ===')
const result1 = build(packages)
console.log(`\nResult: ${result1.rebuilt} rebuilt, ${result1.skipped} skipped`)
console.log('\n=== Вторая сборка (без изменений) ===')
// Сбрасываем флаги но хэши остались в packages
Object.values(packages).forEach(p => p.built = false)
const result2 = build(packages)
console.log(`\nResult: ${result2.rebuilt} rebuilt, ${result2.skipped} skipped`)
console.log('\n=== Третья сборка (изменился @mono/shared) ===')
Object.values(packages).forEach(p => p.built = false)
const result3 = build(packages, ['@mono/shared']) // изменился shared
console.log(`\nResult: ${result3.rebuilt} rebuilt, ${result3.skipped} skipped`)
console.log('(shared изменился → все зависящие тоже пересобираются)')Реализуй `createWorkspace(packages)` — систему управления пакетами монорепо. `packages` — объект `{ name: { deps: string[], version: string } }`. Методы: `getDependents(name)` — возвращает пакеты которые зависят от `name` (прямые и транзитивные), `getBuildOrder()` — возвращает порядок сборки (топологическая сортировка), `getAffected(changed)` — возвращает все пакеты, которые нужно пересобрать при изменении пакетов из массива `changed`.
getBuildOrder: классический DFS с visit(). getDependents: BFS — начни с targetName, находи прямых зависимых через filter deps.includes(current), добавляй в очередь. getAffected: для каждого changed вызови getDependents и объедини в Set, затем отфильтруй getBuildOrder().
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке