Skip to Content

Path aliases

If your tsconfig.json uses compilerOptions.paths (e.g. @/* โ†’ ./src/*), the emit will contain those alias paths verbatim, and they wonโ€™t resolve at runtime. @ttsc/paths rewrites them into relative imports during compile.

The problem

// src/main.ts (source) import { value } from "@lib/value";

After a plain ttsc build:

// dist/main.js (broken) import { value } from "@lib/value"; // โ† Node can't resolve this

The fix

npm install -D @ttsc/paths

@ttsc/paths ships the ttsc.plugin auto-discovery marker in its package.json, so the install alone activates the rewrite for any project whose tsconfig declares compilerOptions.paths, no plugins entry needed. The explicit entry below is equivalent and takes precedence; to turn a discovered plugin off without uninstalling it, declare the entry with enabled: false (see Plugin protocol).

// tsconfig.json { "compilerOptions": { "rootDir": "src", "outDir": "dist", "paths": { "@/*": ["./src/*"], "@lib/*": ["./src/modules/*"] }, "plugins": [{ "transform": "@ttsc/paths" }] } }

Now the emit is:

// dist/main.js import { value } from "./modules/value.js";

What it does and doesnโ€™t do

  • Reads the same compilerOptions.paths, rootDir, and outDir that ttsc already uses. When rootDir is omitted, output paths anchor at the tsconfigโ€™s directory โ€” exactly where TypeScript-Go anchors its own emit.
  • When several patterns match one specifier, an exact pattern wins, then the longest literal prefix: the same order tsc uses to resolve the import.
  • Rewrites JavaScript-family emit (.js, .mjs, .cjs, .jsx) and .d.ts declarations.
  • Resolves extensionless path targets against .ts, .tsx, .mts, .cts, then .js, .jsx, .mjs, and .cjs source files.
  • Preserves the extension of assets the compiler copies rather than transpiles: an exact alias to a .json source pulled in by resolveJsonModule rewrites to ./data.json, the file TypeScript-Go actually copies, keeping any import ... with { type: "json" } attribute intact โ€” never an invented ./data.js.
  • No separate plugin config.
  • Only rewrites specifiers that match compilerOptions.paths; relative, absolute, and non-matching package specifiers are left alone.
  • Uses TypeScript-Goโ€™s emitted runtime suffix for matched targets (.js, .mjs, .cjs, or .jsx depending on the source file and jsx mode).
  • Does not change source files: only the emit.

Common alternatives (and why this is simpler)

  • tsc-alias: post-build script. Works, but itโ€™s a second pass. @ttsc/paths runs inside the compile.
  • Path remapping at runtime via tsconfig-paths/register: adds runtime cost and a startup step. @ttsc/paths produces normal relative imports.
  • Custom bundler config: if a bundler owns your build, thatโ€™s fine. @ttsc/paths is for projects that ship as plain Node packages.

Inspired by

typescript-transform-paths. Same goal, different compiler.

See also

Last updated on