В больших проектах импорты с относительными путями быстро становятся нечитаемыми:
// Без алиасов — боль
import { UserService } from '../../../services/UserService'
import { formatDate } from '../../../../utils/date'
import { Button } from '../../components/ui/Button'При переносе файла все пути нужно исправлять вручную.
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"],
"@hooks/*": ["src/hooks/*"],
"@types/*": ["src/types/*"],
"@api/*": ["src/api/*"]
}
}
}Теперь импорты становятся чистыми:
// С алиасами
import { UserService } from '@/services/UserService'
import { formatDate } from '@utils/date'
import { Button } from '@components/ui/Button'baseUrl задаёт корневую директорию для разрешения не-относительных импортов. При "baseUrl": "." (корень проекта) или "baseUrl": "src" можно импортировать без @:
{ "baseUrl": "src" }// При baseUrl: "src" — работает без @
import { Button } from 'components/Button'
import { api } from 'api/client'TypeScript понимает алиасы, но **бандлер также нужно настроить** — иначе runtime не знает, что @/ означает src/.
Vite (vite.config.ts):
import { defineConfig } from 'vite'
import path from 'path'
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})webpack (webpack.config.js):
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
}**Next.js** — поддерживает @/* из коробки (начиная с Next.js 13).
Плагин vite-tsconfig-paths читает paths из tsconfig автоматически:
npm install -D vite-tsconfig-paths// vite.config.ts
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [tsconfigPaths()],
})Теперь синхронизировать tsconfig и vite.config не нужно.
1. **Читаемость** — @/services/auth vs ../../../services/auth
2. **Рефакторинг** — переместить файл, не меняя импорты
3. **Единый стиль** — все пути начинаются с @/ по всему проекту
4. **IDE поддержка** — автодополнение работает по алиасам
| Алиас | Папка |
|-------|-------|
| @/* | src/* |
| @components/* | src/components/* |
| @utils/* | src/utils/* |
| ~/* | src/* (альтернатива @) |
Симуляция резолвера path aliases: как бандлер преобразует @-импорты в реальные пути
// Path aliases — это просто замена одной части пути на другую.
// Симулируем, как Vite/webpack резолвит алиасы во время сборки.
// --- Конфигурация алиасов (как в tsconfig paths + vite alias) ---
const pathAliases = {
'@': '/project/src',
'@components': '/project/src/components',
'@utils': '/project/src/utils',
'@hooks': '/project/src/hooks',
'@api': '/project/src/api',
'@types': '/project/src/types',
}
// --- Резолвер алиасов ---
function resolveAlias(importPath, aliases) {
// Сортируем алиасы по длине (длинные имеют приоритет)
const sortedAliases = Object.entries(aliases)
.sort(([a], [b]) => b.length - a.length)
for (const [alias, realPath] of sortedAliases) {
// Алиас с /* в конце
if (importPath.startsWith(alias + '/')) {
return realPath + importPath.slice(alias.length)
}
// Точное совпадение
if (importPath === alias) {
return realPath
}
}
// Относительный путь — не трогаем
if (importPath.startsWith('.')) {
return importPath
}
return null // node_modules или неизвестный модуль
}
// --- Анализ зависимостей файла ---
function analyzeImports(fileContent, currentFile, aliases) {
const importRegex = /imports+.*?froms+['"]([^'"]+)['"]/g
const imports = []
let match
while ((match = importRegex.exec(fileContent)) !== null) {
const rawPath = match[1]
const resolved = resolveAlias(rawPath, aliases)
imports.push({
raw: rawPath,
resolved: resolved || rawPath,
isAlias: resolved !== null && !rawPath.startsWith('.'),
isRelative: rawPath.startsWith('.'),
})
}
return imports
}
// --- Симуляция файла с алиасами ---
const exampleFile = `
import { useState } from 'react'
import { Button } from '@components/ui/Button'
import { formatDate } from '@utils/date'
import { useAuth } from '@hooks/useAuth'
import { fetchUsers } from '@api/users'
import { UserType } from '@/types/user'
import { helpers } from './helpers'
import lodash from 'lodash'
`
console.log('=== Резолвинг path aliases ===')
const imports = analyzeImports(exampleFile, '/project/src/pages/Users.tsx', pathAliases)
imports.forEach(({ raw, resolved, isAlias, isRelative }) => {
const type = isAlias ? '[ALIAS]' : isRelative ? '[RELATIVE]' : '[EXTERNAL]'
console.log(`${type} "${raw}"`)
if (isAlias) {
console.log(` -> "${resolved}"`)
}
})
// --- Сравнение: с алиасами vs без ---
console.log('\n=== Сравнение путей ===')
const deepFilePath = '/project/src/features/dashboard/widgets/Chart.tsx'
function getRelativePath(from, to) {
// Упрощённая симуляция
const depth = from.split('/').length - 4
return '../'.repeat(depth) + to.replace('/project/src/', '')
}
const imports_to_resolve = [
'/project/src/components/ui/Button',
'/project/src/utils/formatDate',
'/project/src/hooks/useAuth',
]
console.log('Файл: src/features/dashboard/widgets/Chart.tsx')
console.log('\nБез алиасов:')
imports_to_resolve.forEach(imp => {
console.log(' import from "' + getRelativePath(deepFilePath, imp) + '"')
})
console.log('\nС алиасами (@/ = src/):')
imports_to_resolve.forEach(imp => {
const aliasPath = '@/' + imp.replace('/project/src/', '')
console.log(` import from "${aliasPath}"`)
})В больших проектах импорты с относительными путями быстро становятся нечитаемыми:
// Без алиасов — боль
import { UserService } from '../../../services/UserService'
import { formatDate } from '../../../../utils/date'
import { Button } from '../../components/ui/Button'При переносе файла все пути нужно исправлять вручную.
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"],
"@hooks/*": ["src/hooks/*"],
"@types/*": ["src/types/*"],
"@api/*": ["src/api/*"]
}
}
}Теперь импорты становятся чистыми:
// С алиасами
import { UserService } from '@/services/UserService'
import { formatDate } from '@utils/date'
import { Button } from '@components/ui/Button'baseUrl задаёт корневую директорию для разрешения не-относительных импортов. При "baseUrl": "." (корень проекта) или "baseUrl": "src" можно импортировать без @:
{ "baseUrl": "src" }// При baseUrl: "src" — работает без @
import { Button } from 'components/Button'
import { api } from 'api/client'TypeScript понимает алиасы, но **бандлер также нужно настроить** — иначе runtime не знает, что @/ означает src/.
Vite (vite.config.ts):
import { defineConfig } from 'vite'
import path from 'path'
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
})webpack (webpack.config.js):
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
}**Next.js** — поддерживает @/* из коробки (начиная с Next.js 13).
Плагин vite-tsconfig-paths читает paths из tsconfig автоматически:
npm install -D vite-tsconfig-paths// vite.config.ts
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [tsconfigPaths()],
})Теперь синхронизировать tsconfig и vite.config не нужно.
1. **Читаемость** — @/services/auth vs ../../../services/auth
2. **Рефакторинг** — переместить файл, не меняя импорты
3. **Единый стиль** — все пути начинаются с @/ по всему проекту
4. **IDE поддержка** — автодополнение работает по алиасам
| Алиас | Папка |
|-------|-------|
| @/* | src/* |
| @components/* | src/components/* |
| @utils/* | src/utils/* |
| ~/* | src/* (альтернатива @) |
Симуляция резолвера path aliases: как бандлер преобразует @-импорты в реальные пути
// Path aliases — это просто замена одной части пути на другую.
// Симулируем, как Vite/webpack резолвит алиасы во время сборки.
// --- Конфигурация алиасов (как в tsconfig paths + vite alias) ---
const pathAliases = {
'@': '/project/src',
'@components': '/project/src/components',
'@utils': '/project/src/utils',
'@hooks': '/project/src/hooks',
'@api': '/project/src/api',
'@types': '/project/src/types',
}
// --- Резолвер алиасов ---
function resolveAlias(importPath, aliases) {
// Сортируем алиасы по длине (длинные имеют приоритет)
const sortedAliases = Object.entries(aliases)
.sort(([a], [b]) => b.length - a.length)
for (const [alias, realPath] of sortedAliases) {
// Алиас с /* в конце
if (importPath.startsWith(alias + '/')) {
return realPath + importPath.slice(alias.length)
}
// Точное совпадение
if (importPath === alias) {
return realPath
}
}
// Относительный путь — не трогаем
if (importPath.startsWith('.')) {
return importPath
}
return null // node_modules или неизвестный модуль
}
// --- Анализ зависимостей файла ---
function analyzeImports(fileContent, currentFile, aliases) {
const importRegex = /imports+.*?froms+['"]([^'"]+)['"]/g
const imports = []
let match
while ((match = importRegex.exec(fileContent)) !== null) {
const rawPath = match[1]
const resolved = resolveAlias(rawPath, aliases)
imports.push({
raw: rawPath,
resolved: resolved || rawPath,
isAlias: resolved !== null && !rawPath.startsWith('.'),
isRelative: rawPath.startsWith('.'),
})
}
return imports
}
// --- Симуляция файла с алиасами ---
const exampleFile = `
import { useState } from 'react'
import { Button } from '@components/ui/Button'
import { formatDate } from '@utils/date'
import { useAuth } from '@hooks/useAuth'
import { fetchUsers } from '@api/users'
import { UserType } from '@/types/user'
import { helpers } from './helpers'
import lodash from 'lodash'
`
console.log('=== Резолвинг path aliases ===')
const imports = analyzeImports(exampleFile, '/project/src/pages/Users.tsx', pathAliases)
imports.forEach(({ raw, resolved, isAlias, isRelative }) => {
const type = isAlias ? '[ALIAS]' : isRelative ? '[RELATIVE]' : '[EXTERNAL]'
console.log(`${type} "${raw}"`)
if (isAlias) {
console.log(` -> "${resolved}"`)
}
})
// --- Сравнение: с алиасами vs без ---
console.log('\n=== Сравнение путей ===')
const deepFilePath = '/project/src/features/dashboard/widgets/Chart.tsx'
function getRelativePath(from, to) {
// Упрощённая симуляция
const depth = from.split('/').length - 4
return '../'.repeat(depth) + to.replace('/project/src/', '')
}
const imports_to_resolve = [
'/project/src/components/ui/Button',
'/project/src/utils/formatDate',
'/project/src/hooks/useAuth',
]
console.log('Файл: src/features/dashboard/widgets/Chart.tsx')
console.log('\nБез алиасов:')
imports_to_resolve.forEach(imp => {
console.log(' import from "' + getRelativePath(deepFilePath, imp) + '"')
})
console.log('\nС алиасами (@/ = src/):')
imports_to_resolve.forEach(imp => {
const aliasPath = '@/' + imp.replace('/project/src/', '')
console.log(` import from "${aliasPath}"`)
})Реализуй класс `PathResolver`, который управляет алиасами путей. Методы: `addAlias(alias, realPath)` — регистрирует алиас, `resolve(importPath)` — возвращает реальный путь (заменяет алиас) или оригинальный путь если алиас не найден, `getAll()` — возвращает объект со всеми алиасами. Длинные алиасы должны иметь приоритет над короткими.
В resolve(): Object.entries(this._aliases).sort(([a], [b]) => b.length - a.length) отсортирует алиасы по длине. Замена: return realPath + importPath.slice(alias.length). В getAll(): return { ...this._aliases } чтобы вернуть копию.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке