Paths e aliases no tsconfig

1. Introdução aos Paths no TypeScript

Todo desenvolvedor TypeScript já enfrentou o problema dos imports relativos longos e frágeis. Caminhos como ../../../utils/formatDate ou ../../../../services/api são comuns em projetos com estrutura de diretórios profunda. Esses imports não apenas poluem o código visualmente, mas também quebram facilmente quando movemos arquivos de lugar.

Os aliases no TypeScript resolvem esse problema permitindo mapear caminhos curtos e semânticos para diretórios específicos do projeto. Através da propriedade paths no tsconfig.json, podemos definir atalhos como @utils/formatDate ou @services/api, tornando o código mais limpo, seguro e fácil de manter.

2. Configuração básica de paths

A configuração de paths no tsconfig.json segue uma estrutura simples: cada chave representa o alias desejado, e o valor é um array de caminhos reais que o TypeScript deve usar para resolver esse alias.

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".", // Diretório raiz para resolução
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

O curinga * é essencial nessa configuração. Ele captura qualquer subcaminho após o alias e o substitui no padrão de destino. Por exemplo, @/utils/date será resolvido para ./src/utils/date.

A combinação com baseUrl define o ponto de partida para todos os caminhos em paths. Sem o baseUrl, os caminhos seriam relativos ao local do tsconfig.json, o que pode causar confusão em projetos com múltiplos arquivos de configuração.

// Agora podemos importar assim:
import { formatDate } from '@/utils/date';
import { api } from '@/services/api';

// Em vez de:
import { formatDate } from '../../../utils/date';
import { api } from '../../services/api';

3. Múltiplos aliases e diretórios

Projetos maiores se beneficiam de aliases específicos para cada domínio da aplicação:

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@components/*": ["./src/components/*"],
      "@utils/*": ["./src/utils/*"],
      "@services/*": ["./src/services/*"],
      "@hooks/*": ["./src/hooks/*"],
      "@types/*": ["./src/types/*"]
    }
  }
}

É possível também mapear um alias para múltiplos caminhos de fallback. O TypeScript tentará cada caminho na ordem definida:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@shared/*": [
        "./src/shared/*",
        "./src/legacy/shared/*" // fallback
      ]
    }
  }
}

Para aliases aninhados ou subdiretórios específicos, podemos ser mais granulares:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@components/ui/*": ["./src/components/ui/*"],
      "@components/forms/*": ["./src/components/forms/*"],
      "@utils/date": ["./src/utils/date/index.ts"], // alias específico
      "@utils/validation": ["./src/utils/validation/index.ts"]
    }
  }
}

Boas práticas de nomenclatura incluem usar o prefixo @ para evitar conflitos com pacotes npm, ou usar nomes descritivos como utils/, components/ sem prefixo.

4. Como o TypeScript resolve os aliases

O TypeScript resolve os aliases seguindo uma ordem específica:

  1. Primeiro, calcula o caminho base a partir de baseUrl
  2. Depois, aplica o padrão definido em paths
// Com baseUrl: "." e paths: { "@/*": ["./src/*"] }
// O import: import { x } from '@/utils/helper'
// Será resolvido para: ./src/utils/helper

É importante entender que paths não afeta a saída do JavaScript compilado. O TypeScript usa os aliases apenas para verificação de tipos e autocomplete no editor. O código JavaScript gerado mantém os caminhos originais com os aliases, o que causa erros em tempo de execução se não houver uma ferramenta de build configurada para resolvê-los.

A verificação de tipos funciona perfeitamente com aliases — o TypeScript entende os caminhos mapeados e fornece IntelliSense completo.

5. Integração com ferramentas de build e runtime

Para que os aliases funcionem em tempo de execução, precisamos configurar cada ferramenta separadamente:

Node.js com ts-node:

// Instalar: npm install tsconfig-paths
// Executar: node -r tsconfig-paths/register dist/index.js

// Ou no package.json:
{
  "scripts": {
    "start": "node -r tsconfig-paths/register dist/index.js"
  }
}

Webpack:

// webpack.config.js
module.exports = {
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src/'),
      '@components': path.resolve(__dirname, 'src/components/'),
      '@utils': path.resolve(__dirname, 'src/utils/')
    }
  }
};

Jest:

// jest.config.js
module.exports = {
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
    '^@components/(.*)$': '<rootDir>/src/components/$1',
    '^@utils/(.*)$': '<rootDir>/src/utils/$1'
  }
};

ESLint:

// .eslintrc.js
module.exports = {
  settings: {
    'import/resolver': {
      typescript: {
        alwaysTryTypes: true,
        project: './tsconfig.json'
      }
    }
  }
};

6. Aliases em monorepos e Project References

Em monorepos com Project References, a sincronização de paths entre projetos requer atenção especial:

// packages/shared/tsconfig.json
{
  "compilerOptions": {
    "composite": true,
    "baseUrl": ".",
    "paths": {
      "@shared/*": ["./src/*"]
    }
  }
}

// apps/web/tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@shared/*": ["../shared/src/*"],
      "@web/*": ["./src/*"]
    }
  },
  "references": [
    { "path": "../shared" }
  ]
}

Para evitar conflitos de alias entre pacotes, use prefixos únicos como @meuprojeto/shared e @meuprojeto/web. Isso mantém os aliases organizados e evita ambiguidades.

7. Problemas comuns e soluções

Erro "Cannot find module" mesmo com paths configurado:
- Verifique se o baseUrl está correto
- Confirme que o padrão * está sendo usado corretamente
- Reinicie o editor/TypeScript server (Ctrl+Shift+P → "TypeScript: Restart TS server")

Aliases não funcionam em tempo de execução:
- Lembre-se: paths é apenas para TypeScript em tempo de design
- Configure a ferramenta de build (Webpack, ts-node, etc.) para resolver os aliases

Conflitos com módulos npm:
- Use prefixos como @/ ou ~/ para diferenciar de pacotes npm
- Evite nomes genéricos como utils ou helpers

Dicas para debugar:
- Use tsc --traceResolution para ver como o TypeScript resolve cada módulo
- Verifique o arquivo compilado JavaScript para confirmar os caminhos gerados

8. Boas práticas e considerações finais

Quando usar aliases:
- Projetos com mais de 3 níveis de profundidade de diretórios
- Múltiplos módulos que compartilham imports comuns
- Monorepos com vários pacotes

Quando evitar aliases:
- Projetos pequenos com estrutura plana
- Imports que são usados apenas em um ou dois arquivos próximos

Manutenção:
- Centralize todos os aliases em um único tsconfig.json
- Evite criar aliases para cada subdiretório — mantenha um número gerenciável
- Documente os aliases no README do projeto

Alternativas:
- Use barrel files (index.ts) para organizar exports e reduzir a profundidade dos imports
- Considere Node.js subpath imports (campo imports no package.json) como alternativa moderna

Checklist para configurar paths em novos projetos:
1. Definir baseUrl no tsconfig.json
2. Configurar paths com os aliases necessários
3. Configurar Webpack/Vite/etc. com resolve.alias
4. Configurar Jest com moduleNameMapper
5. Configurar ESLint com import/resolver
6. Testar com um import de exemplo
7. Documentar os aliases para a equipe

Os aliases no TypeScript são uma ferramenta poderosa para melhorar a legibilidade e manutenibilidade do código. Quando configurados corretamente e integrados com as ferramentas de build, eles eliminam a fragilidade dos imports relativos longos e tornam o desenvolvimento mais produtivo.

Referências