typescript json 71 lines · 4 tabs

TypeScript path aliases (tsconfig + bundler)

Shared by codesnips Jan 2026
4 tabs
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { fileURLToPath, URL } from 'node:url';

const resolvePath = (relative: string): string =>
  fileURLToPath(new URL(relative, import.meta.url));

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: [
      // Most specific prefix first so '@shared' wins over '@'.
      { find: '@shared', replacement: resolvePath('../shared/src') },
      { find: '@', replacement: resolvePath('./src') },
    ],
  },
  build: {
    sourcemap: true,
    outDir: 'dist',
  },
});
4 files · typescript, json Explain with highlit

Path aliases let a codebase import from @/services/user instead of brittle relative chains like ../../../services/user. TypeScript understands these aliases at type-check time, but the compiler does not rewrite import specifiers, so every runtime and tooling layer that resolves modules must be taught the same mapping. This snippet shows the three places the same alias table has to be declared to stay consistent: the TypeScript config, the Vite bundler, and the Jest test runner.

In tsconfig.json, baseUrl anchors resolution at the project root and the paths map defines each alias. @/* points at src/* for the app's internal imports, while @shared/* reaches into a sibling package — a common monorepo setup. The paths entries only affect how tsc and editors resolve types; they are purely a compile-time hint and produce no output on their own. This is the pitfall that trips people up: type-checking passes, but the built bundle throws Cannot find module because the bundler never learned the mapping.

In vite.config.ts, resolve.alias mirrors the same table for the actual runtime resolver. Using fileURLToPath with import.meta.url produces absolute paths that work regardless of the working directory. The order matters when aliases share a prefix — more specific keys like @shared should resolve before broader ones. Rather than hand-maintaining two lists, teams often generate resolve.alias from tsconfig via a plugin, but the explicit form shown here makes the relationship obvious.

In jest.config.ts, moduleNameMapper performs the same job with regular expressions, since Jest resolves modules through Node without any bundler. The ^@/(.*)$ pattern captures the tail and rewrites it under <rootDir>/src. Each alias needs its own regex, and forgetting one produces test-only failures that never appear in the app build.

The broader lesson is that path aliases are not a single feature but a convention that every module resolver in the toolchain must agree on. Keeping the three tables synchronized — or generating them from one source — is what makes aliases reliable instead of a source of environment-specific breakage.


Related snips

Share this code

Here's the card — post it anywhere.

TypeScript path aliases (tsconfig + bundler) — share card
Link copied