Difficulty: Advanced
Path aliases transform long, fragile relative imports like `import { Button } from '../../../components/ui/Button'` into clean absolute-style imports like `import { Button } from '@/components/ui/Button'`. This dramatically improves code readability and makes refactoring easier since moving a file does not break its imports if you use aliases consistently. TypeScript supports path aliases natively through the `paths` and `baseUrl` options in tsconfig.json.
The `paths` option is a map of import patterns to file locations. Patterns can use wildcards: `@/*` matches anything starting with `@/`, and `*` captures the rest of the path. The value is an array of possible locations, checked in order. The `baseUrl` option sets the base directory for non-relative module resolution. In TypeScript 4.1+, `paths` can be used without `baseUrl`, but setting `baseUrl: "."` is still common for clarity.
Critically, TypeScript path aliases only affect the type checker - they do not transform import paths in the compiled output. This means your bundler or runtime must also understand these aliases. For Vite projects, you configure `resolve.alias` in vite.config.ts. For Webpack, you use `resolve.alias` in webpack.config.js. For Jest, you use `moduleNameMapper`. For Node.js without a bundler, you need packages like `tsconfig-paths` or the newer `--experimental-specifier-resolution` flag.
Common alias patterns include `@/*` for the src directory root, `@components/*` for the components directory, `@utils/*` for utilities, and `@types/*` for shared type definitions. Some teams use shorter aliases like `~/*` instead of `@/*`. The key is consistency - pick a pattern and use it throughout the project. Avoid creating too many aliases, as they add cognitive overhead and require maintaining parallel configurations.
// tsconfig.json paths configuration
// {
// "compilerOptions": {
// "baseUrl": ".",
// "paths": {
// "@/*": ["src/*"],
// "@components/*": ["src/components/*"],
// "@utils/*": ["src/utils/*"],
// "@hooks/*": ["src/hooks/*"],
// "@types/*": ["src/types/*"]
// }
// }
// }
// Before aliases:
// import { Button } from '../../../components/ui/Button';
// import { formatDate } from '../../utils/date';
// import { useAuth } from '../../../hooks/useAuth';
// After aliases:
// import { Button } from '@components/ui/Button';
// import { formatDate } from '@utils/date';
// import { useAuth } from '@hooks/useAuth';
// Demonstrating the transformation
const imports = [
{ before: '../../../components/ui/Button', after: '@components/ui/Button' },
{ before: '../../utils/date', after: '@utils/date' },
{ before: '../../../hooks/useAuth', after: '@hooks/useAuth' }
];
imports.forEach(({ before, after }) => {
console.log(`${before}`);
console.log(` -> ${after}`);
});
Path aliases replace deeply nested relative imports with clean, absolute-style paths. This makes the codebase more readable and resilient to file moves.
// vite.config.ts - must mirror tsconfig paths
// import { defineConfig } from 'vite';
// import path from 'path';
//
// export default defineConfig({
// resolve: {
// alias: {
// '@': path.resolve(__dirname, 'src'),
// '@components': path.resolve(__dirname, 'src/components'),
// '@utils': path.resolve(__dirname, 'src/utils'),
// }
// }
// });
// Simulating alias resolution for both TS and Vite
interface AliasConfig {
pattern: string;
tsPath: string;
vitePath: string;
}
const aliases: AliasConfig[] = [
{ pattern: '@/*', tsPath: 'src/*', vitePath: 'path.resolve(__dirname, "src")' },
{ pattern: '@components/*', tsPath: 'src/components/*', vitePath: 'path.resolve(__dirname, "src/components")' },
{ pattern: '@utils/*', tsPath: 'src/utils/*', vitePath: 'path.resolve(__dirname, "src/utils")' }
];
console.log('TypeScript paths:');
aliases.forEach(a => console.log(` "${a.pattern}": ["${a.tsPath}"]`));
console.log('\nVite resolve.alias:');
aliases.forEach(a => console.log(` "${a.pattern.replace('/*', '')}": ${a.vitePath}`));
Path aliases require dual configuration - tsconfig.json for TypeScript type checking, and vite.config.ts (or webpack.config.js) for runtime resolution.
paths, baseUrl, module aliases, Vite resolve.alias, tsconfig paths, import mapping