В крупных проектах структура каталогов быстро усложняется. Появляются десятки директорий:
src/
├── components/
├── pages/
├── layouts/
├── services/
├── store/
├── hooks/
├── utils/
├── assets/
└── shared/
Без алиасов импорт модулей начинает выглядеть громоздко:
import Button from '../. ./. ./. ./components/ui/Button';
import api from '../. ./. ./services/api';
import formatDate from '../. ./. ./. ./. ./utils/date/formatDate';
Подобные конструкции создают сразу несколько проблем:
Механизм resolve.alias решает эту проблему, позволяя
создавать псевдонимы для директорий и файлов.
Алиасы настраиваются внутри resolve.alias.
const path = require('path');
module.exports = {
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
};
После этого можно использовать сокращённые пути:
import Header from '@/components/Header';
import api from '@/services/api';
Webpack заменяет @ на абсолютный путь к директории
src.
В маленьком приложении относительные пути ещё терпимы. В монорепозиториях и enterprise-проектах они становятся серьёзной архитектурной проблемой.
Пример типичного импорта без алиасов:
import Modal from '../. ./. ./. ./. ./. ./shared/ui/Modal';
После перемещения файла путь может полностью сломаться.
С alias импорт становится стабильным:
import Modal from '@shared/ui/Modal';
Физическое расположение текущего файла перестаёт влиять на импорт.
В реальных проектах обычно создаётся целая система алиасов.
const path = require('path');
module.exports = {
resolve: {
alias: {
'@components': path.resolve(__dirname, 'src/components'),
'@pages': path.resolve(__dirname, 'src/pages'),
'@layouts': path.resolve(__dirname, 'src/layouts'),
'@services': path.resolve(__dirname, 'src/services'),
'@utils': path.resolve(__dirname, 'src/utils'),
'@assets': path.resolve(__dirname, 'src/assets'),
'@store': path.resolve(__dirname, 'src/store'),
},
},
};
Использование:
import HomePage from '@pages/HomePage';
import Sidebar from '@components/navigation/Sidebar';
import authService from '@services/authService';
alias фактически реализует систему абсолютных
импортов.
Без alias:
../. ./. ./components/Button
С alias:
@components/Button
Такой подход:
Для alias почти всегда применяется path.resolve.
const path = require('path');
Пример:
path.resolve(__dirname, 'src/components')
Результат:
/Users/project/src/components
Webpack получает абсолютный путь файловой системы.
Неправильный вариант:
alias: {
'@components': './src/components',
}
Проблемы:
Правильный вариант:
alias: {
'@components': path.resolve(__dirname, 'src/components'),
}
Alias может указывать не только на директорию, но и на конкретный файл.
resolve: {
alias: {
'@config': path.resolve(__dirname, 'src/config/index.js'),
},
}
Использование:
import config from '@config';
Webpack позволяет заменять один модуль другим.
Пример:
resolve: {
alias: {
lodash: path.resolve(__dirname, 'src/custom-lodash.js'),
},
}
Теперь:
import _ from 'lodash';
будет импортировать:
src/custom-lodash.js
Популярная практика — замена библиотек для уменьшения bundle size.
Пример:
resolve: {
alias: {
react: 'preact/compat',
'react-dom/test-utils': 'preact/test-utils',
'react-dom': 'preact/compat',
},
}
Такой подход позволяет существенно уменьшить размер сборки.
Алиасы часто становятся частью архитектурных правил.
Пример структуры:
src/
├── app/
├── processes/
├── pages/
├── widgets/
├── features/
├── entities/
└── shared/
Настройка:
alias: {
'@app': path.resolve(__dirname, 'src/app'),
'@processes': path.resolve(__dirname, 'src/processes'),
'@pages': path.resolve(__dirname, 'src/pages'),
'@widgets': path.resolve(__dirname, 'src/widgets'),
'@features': path.resolve(__dirname, 'src/features'),
'@entities': path.resolve(__dirname, 'src/entities'),
'@shared': path.resolve(__dirname, 'src/shared'),
}
Это особенно распространено в архитектуре Feature-Sliced Design.
В монорепозиториях alias помогают организовывать связи между пакетами.
Структура:
packages/
├── ui/
├── core/
├── utils/
└── app/
Настройка:
alias: {
'@ui': path.resolve(__dirname, '../ui/src'),
'@core': path.resolve(__dirname, '../core/src'),
}
Импорт:
import Button from '@ui/Button';
При использовании TypeScript настройка должна дублироваться в
tsconfig.json.
Webpack:
resolve: {
alias: {
'@components': path.resolve(__dirname, 'src/components'),
},
}
TypeScript:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@components/*": ["src/components/*"]
}
}
}
Если этого не сделать:
При использовании Babel необходимо синхронизировать alias.
Установка:
npm install babel-plugin-module-resolver --save-dev
Настройка:
module.exports = {
plugins: [
[
'module-resolver',
{
alias: {
'@components': './src/components',
},
},
],
],
};
Тестовая среда также должна понимать алиасы.
Настройка:
module.exports = {
moduleNameMapper: {
'^@components/(.*)$': '<rootDir>/src/components/$1',
},
};
Без этого тесты не смогут находить модули.
ESLint требует отдельной настройки резолвинга.
Установка:
npm install eslint-import-resolver-webpack --save-dev
Настройка:
settings: {
'import/resolver': {
webpack: {
config: 'webpack.config.js',
},
},
},
Для корректной работы автодополнения VSCode ориентируется на:
jsconfig.json;tsconfig.json.Пример:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
Наиболее популярный алиас — @.
alias: {
'@': path.resolve(__dirname, 'src'),
}
Импорт:
import App from '@/App';
Причины популярности:
Webpack поддерживает точное совпадение имени через
$.
Пример:
alias: {
react$: path.resolve(__dirname, 'src/custom-react.js'),
}
Теперь:
import React from 'react';
будет заменён.
Но:
import something from 'react/utils';
заменён не будет.
Alias имеет высокий приоритет при резолвинге модулей.
Webpack сначала проверяет:
Пример:
alias: {
utils: path.resolve(__dirname, 'src/utils'),
}
Даже если существует пакет utils в
node_modules, Webpack выберет alias.
Плохая практика:
alias: {
path: path.resolve(__dirname, 'src/path'),
}
Это может конфликтовать со встроенным Node.js модулем
path.
Лучше использовать уникальные префиксы:
@path
@utils
@shared
Избыточное число алиасов ухудшает поддержку проекта.
Плохой пример:
alias: {
'@a': ...,
'@b': ...,
'@c': ...,
'@d': ...,
}
Разработчики перестают понимать структуру.
Хорошая практика:
Сам по себе alias не влияет на tree shaking.
Но неправильная подмена модулей может нарушить оптимизацию.
Пример проблемы:
alias: {
lodash: path.resolve(__dirname, 'src/lodash-wrapper.js'),
}
Если wrapper экспортирует всё содержимое библиотеки, размер bundle может увеличиться.
Alias работают и в import().
const module = await import('@components/Modal');
Webpack корректно разрешит путь.
Alias полностью совместимы с:
Пример:
const AdminPage = lazy(() => import('@pages/AdminPage'));
resolve.modules и alias решают разные
задачи.
Позволяет искать модули в дополнительных директориях.
resolve: {
modules: [
path.resolve(__dirname, 'src'),
'node_modules',
],
}
Импорт:
import Button from 'components/Button';
Создаёт явный псевдоним.
import Button from '@components/Button';
Alias считается более безопасным и предсказуемым.
В крупных проектах список alias часто выносится в отдельный файл.
// aliases.js
const path = require('path');
module.exports = {
'@components': path.resolve(__dirname, 'src/components'),
'@services': path.resolve(__dirname, 'src/services'),
};
Использование:
const aliases = require('./aliases');
module.exports = {
resolve: {
alias: aliases,
},
};
Иногда alias создаются автоматически на основе структуры директорий.
Пример:
const fs = require('fs');
const path = require('path');
const folders = fs.readdirSync('./src');
const aliases = folders.reduce((acc, folder) => {
acc[`@${folder}`] = path.resolve(__dirname, 'src', folder);
return acc;
}, {});
Подход удобен в огромных проектах, но ухудшает прозрачность конфигурации.
Распространённые соглашения:
@components
@shared
@core
@app
@pages
@features
@assets
Нежелательные варианты:
@c
@x
@tmp
@test1
Alias должны быть:
Многие инструменты уже используют alias по умолчанию.
@
указывает на src.
~
@
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
}
Webpack настроен:
@components
TypeScript не настроен.
Результат:
Alias иногда маскируют циклические импорты.
Пример:
@features/auth
↓
@shared/api
↓
@features/auth
Из-за абсолютных путей цикл может быть менее заметен.
Плохой пример:
@components/forms/ui/buttons/base
Alias должен обозначать модуль верхнего уровня, а не превращаться в замену полного пути.
Оптимальный подход обычно включает:
@
@shared
@features
@entities
@pages
@widgets
Дополнительно:
const path = require('path');
module.exports = {
resolve: {
extensions: ['.js', '.ts', '.jsx', '.tsx'],
alias: {
'@': path.resolve(__dirname, 'src'),
'@app': path.resolve(__dirname, 'src/app'),
'@pages': path.resolve(__dirname, 'src/pages'),
'@widgets': path.resolve(__dirname, 'src/widgets'),
'@features': path.resolve(__dirname, 'src/features'),
'@entities': path.resolve(__dirname, 'src/entities'),
'@shared': path.resolve(__dirname, 'src/shared'),
'@assets': path.resolve(__dirname, 'src/assets'),
'@styles': path.resolve(__dirname, 'src/styles'),
'@config': path.resolve(__dirname, 'src/config'),
},
},
};
Пример импортов:
import App from '@app/App';
import Header from '@widgets/Header';
import LoginForm from '@features/auth/LoginForm';
import UserCard from '@entities/user/UserCard';
import Button from '@shared/ui/Button';
import logo from '@assets/logo.svg';