Skip to content

tsconfig aliases

TypeScript’s compilerOptions.paths lets you write import App from "@/App" instead of import App from "../../App". Vite doesn’t read that out of the box — without help, your TS compiles but the dev server reports “module not found”. @mongez/vite reads tsconfig.json and mirrors paths into resolve.alias so the two agree.

Default behaviour

mongezVite(); // linkTsconfigPaths: true, tsconfigAlias: true
tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"components/*": ["src/components/*"],
"lib": ["src/lib/index.ts"]
}
}
}

After the config hook runs, config.resolve.alias is:

[
{ find: "@", replacement: "<cwd>/src" },
{ find: "components", replacement: "<cwd>/src/components" },
{ find: "lib", replacement: "<cwd>/src/lib/index.ts" },
]

Now both tsc and Vite resolve:

import App from "@/App"; // → <cwd>/src/App
import Button from "components/Button"; // → <cwd>/src/components/Button
import { foo } from "lib"; // → <cwd>/src/lib/index.ts

How the transform works

For each [find, value] entry in compilerOptions.paths:

  1. find has its trailing /* stripped ("@/*""@").
  2. value is read as String(value) — for a single-element array ["src/*"] this gives "src/*". Its trailing /* is also stripped.
  3. The replacement is run through path.resolve(process.cwd(), ...) to produce an absolute path.
{
find: "@",
replacement: path.resolve(process.cwd(), "src"),
}

Opting out

Set linkTsconfigPaths: false to skip:

mongezVite({ linkTsconfigPaths: false });

Use this when:

  • You already use vite-tsconfig-paths or another plugin that does the same thing.
  • You want to define resolve.alias by hand in vite.config.ts.
  • Your tsconfig.json paths don’t translate cleanly (the transform is intentionally simple — see “Gotchas”).

No-overwrite semantics

If you already declared resolve.alias in vite.config.ts, the plugin does not touch it:

export default defineConfig({
resolve: {
alias: { "@": "/elsewhere" },
},
plugins: [mongezVite()],
});
// → resolve.alias stays { "@": "/elsewhere" }, even though tsconfig has
// "@/*": ["src/*"]

This is intentional — explicit config beats inferred config.

When the plugin does nothing

  • linkTsconfigPaths: false.
  • tsconfigAlias: false (separate but tied option — both must be truthy).
  • tsconfig.json doesn’t exist in process.cwd().
  • tsconfig.json exists but compilerOptions.paths is missing or empty.
  • config.resolve.alias is already set by the user.

Gotchas

  • Only the first array entry survives — the transform reads String(value) rather than iterating. For "foo/*": ["src/foo/*", "lib/foo/*"], only the join-form is read. Most setups only have one entry per key, so this is rarely a problem.
  • /* is stripped exactly once — the transform calls .replace("/*", "") (first occurrence). Nested globs like "foo/*/*" would have only the first /* stripped.
  • No extends-following. If your tsconfig.json extends a base config, the plugin reads only the top file. Re-declare your paths at the top level or wire it differently.
  • baseUrl is ignored. The transform always resolves against process.cwd(). If your paths are written relative to a different baseUrl, the resulting aliases won’t match.
  • Windows path separators. path.resolve uses native separators (\ on Windows). Vite handles both, so this isn’t user-visible — but tests that string-match the replacement need to normalise.