CLI tool
justkey007/tsc-alias avatar
justkey007/tsc-alias

tsc-alias: rewriting TypeScript path aliases into relative paths after the build

Replace alias paths with relative paths after typescript compilation

1,144 stars76 forksTypeScriptMIT

At a glance

What is it?
tsc-alias is a build-time tool that takes the alias paths TypeScript resolves at compile time and rewrites them to relative paths in the emitted files, so the output runs on plain Node without a runtime resolver. This looks at how it works, the options that matter, and where it stops.
Who is it for?
tsc-alias fits a Node or serverless project that uses `paths` aliases in tsconfig, that runs its output on plain Node or in a container without a resolver hook, and that wants the fix applied at build time rather than at run time. It does not fit a project bundled by a tool that already understands aliases, and it is the wrong tool if you need the alias to survive into the output for another tool to resolve.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 6 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.

Editorial analysis

TypeScript resolves aliases, but does not rewrite them

The problem this tool exists for is a specific gap in how TypeScript compiles.

When you define `paths` in your tsconfig, TypeScript resolves those aliases at compile time so that your editor and your type checker work. What it does not do is rewrite the import specifiers in the emitted JavaScript. The output still says `import { x } from '@app/utils'`, and if that file is run by plain Node, the module resolution fails, because Node has no idea what `@app` means.

The usual answers to that are runtime resolvers. The readme names one explicitly, tsconfig-paths, and the comparison section of this project is a single line: compile time, no runtime dependencies.

That is the whole argument. tsc-alias runs after `tsc` and rewrites the emitted import paths into relative ones, so the output is self-contained and runs anywhere Node runs, with nothing registered at startup and nothing for the consumer to install. For a library that is published to npm and consumed by other applications, that matters twice over, since a runtime resolver would become a dependency your consumers need as well.

The tool also handles a case TypeScript does not: aliases that reference other projects outside your tsconfig project, by allowing a relative path to be given for the `baseUrl`.

What the options actually change

The API documentation lists the options as a table, and reading it explains how the tool behaves.

`project`, with the short form `p`, is the path to tsconfig.json and defaults to `tsconfig.json`. `outDir` is described as running in a folder leaving the outDir of the tsconfig.json, a relative path to the tsconfig, and it defaults to `tsconfig.compilerOptions.outDir`. `declarationDir` works the same way but for the declaration directory. So the tool reads the tsconfig, works out where the compiler put the files, and looks for aliases in those.

`watch` observes file changes, and it defaults to false. `followReferences` makes tsc-alias follow the TypeScript project references defined in tsconfig.json and run alias replacement on all referenced projects; it also defaults to false, which means a monorepo with project references needs this turned on or the referenced packages keep their aliases.

`replacers` is a list of files to import as extra replacers, and it defaults to an empty array, so the tool ships with a fixed set of rules and you can add your own.

`fileExtensions` overwrites the file extensions the tool will use to scan and resolve files, and it defaults to undefined. `output` is the output object the tool sends logs to, defaulting to a new Output constructed with the verbose flag. `verbose` and `debug` control how much is written to the terminal, and `silent` is explicitly described as deprecated and no longer having any effect, which is a useful detail because it means an old config setting is harmless.

Two lines in package.json

The installation section is short, and it gives you a global and a local install.

sh
npm install -g tsc-alias
sh
npm install --save-dev tsc-alias

The global install is the quick way to try it, and the devDependency is the one that belongs in a real project, since the build must work on a clean checkout and on a CI machine.

The integration is the two-step build script. The first form names the project explicitly:

json
"scripts": {
  "build": "tsc --project tsconfig.json && tsc-alias -p tsconfig.json",
}

The second form relies on the defaults and adds a watch variant:

json
"scripts": {
  "build": "tsc && tsc-alias",
  "build:watch": "tsc && (concurrently \"tsc -w\" \"tsc-alias -w\")"
}

The ordering matters, and the `&&` makes it explicit: the compiler has to run first, because tsc-alias rewrites the files the compiler produced. The watch form runs the two in parallel under concurrently, with tsc-alias in its own watch mode observing the changes.

The CLI has its own help output, which is where the flag names live:

sh
tsc-alias --help

ESM is why resolveFullPaths exists

The option that matters most in a modern project is `resolveFullPaths`, described as attempting to replace incomplete import paths, those not ending in `.js`, with fully resolved paths for ECMAScript Modules compatibility. It defaults to false.

The reason is that ECMAScript modules require explicit file extensions in import specifiers. If your source says `import { x } from './utils'`, TypeScript will happily compile it, and the emitted ESM will still say `'./utils'`, which Node will refuse to resolve. Turning on `resolveFullPaths` makes the tool write the full path, including the extension, into the emitted import.

The companion option is `resolveFullExtension`, which allows you to specify the extension of incomplete import paths and works with `resolveFullPaths`. Its default is `'.js' | '.mjs' | '.cjs'`, which is the set of extensions a Node package can carry, and it is a reasonable default for a package that does not know whether it is being consumed as CommonJS or as ESM.

Together, these two options are the difference between an output that runs under `node` and an output that throws a module not found error on the first import. A project that ships ESM should set `resolveFullPaths: true`, and a project that ships CommonJS should leave it off unless it also wants extensions written in.

Configuration lives in the tsconfig, and replacers are pluggable

Everything the CLI accepts can also be set in the tsconfig.json, under a `tsc-alias` key, and the documentation gives a configuration example.

json
{
  "compilerOptions": {
    ...
  },
  "tsc-alias": {
    "verbose": false,
    "resolveFullPaths": true,
    "replacers": {
      "exampleReplacer": {
        "enabled": true,
        "file": "./exampleReplacer.js"
      },
      "otherReplacer": {
        "enabled": true,
        "file": "./otherReplacer.js"
      }
    },
    "fileExtensions": {
      "inputGlob": "{js,jsx,mjs}",
      "outputCheck": ["js", "json", "jsx", "mjs"]
    }
  }
}

The `replacers` entry is the extension point worth understanding. Each replacer is a file you write, with an `enabled` flag and a path, and the tool imports it and lets it do its own replacement. That is how a project handles an alias pattern the tool does not know about, or a mapping it wants to control, without forking the tool.

The `fileExtensions` block controls the scan. `inputGlob` is the pattern of files to rewrite, `{js,jsx,mjs}` in the example, and `outputCheck` is the list of extensions used when checking whether a resolved path exists, `js`, `json`, `jsx` and `mjs` here. Getting `outputCheck` wrong is a classic source of the tool reporting that it cannot find a file that you can see, which is why the issue section in the readme recommends running with `--debug` to check that the config is correctly loaded and all source files are found.

The API for tools that compile in-process

The library is usable from Node as well as from the command line, and the second API shape is the interesting one for anyone embedding it in another build tool.

The batch form is a single call:

typescript
import { replaceTscAliasPaths } from 'tsc-alias';

replaceTscAliasPaths(options?);

The single file form returns a function instead. You prepare it with `prepareSingleFileReplaceTscAliasPaths()`, passing the same options you would pass to `replaceTscAliasPaths()`, and it returns a promise of a function that receives the file contents and path and returns the transformed contents, synchronously.

typescript
import { prepareSingleFileReplaceTscAliasPaths } from 'tsc-alias';

const runFile: SingleFileReplacer = await prepareSingleFileReplaceTscAliasPaths(options?);

function treatFile(filePath: string) {
  const fileContents = fs.readFileSync(filePath, 'utf8');
  const newContents = runFile({fileContents, filePath});
  // do stuff with newContents
}

The point of that shape is that the tool does not need to own the filesystem. A bundler, a test runner or a custom compiler can hold the file contents in memory, hand them over, and get the rewritten source back. That is how tsc-alias can be used in a pipeline that never writes the intermediate files to disk.

The dependencies behind this are modest and visible in the manifest: chokidar for watching, commander for the CLI, get-tsconfig for reading the configuration, globby for globbing, mylas for the alias resolution, normalize-path for path handling, and plimit-lit for limiting concurrency. The Node engine requirement is `>=16.20.2`.

Where tsc-alias is the wrong tool

The limits are all about when you should not be rewriting paths at all.

The first is bundlers. If your build ends in a bundler that understands `paths` and resolves the aliases itself, running tsc-alias first is redundant work, and in some setups it is harmful, because the bundler then has to deal with relative paths it expected to be aliases. The tool's value is highest exactly where there is no other resolver: plain Node, a container running the compiled output, or a serverless deployment of a compiled package.

The second is the opposite case, where you want the alias to survive. Some tools resolve aliases at run time and expect to see them, and some bundler plugins key their behaviour off the alias pattern. Rewriting the specifiers removes that information, and there is no option to keep both.

The third is scope. The tool rewrites alias paths. It does not bundle, does not transpile, does not generate a module map, and does not verify at run time that the resulting paths exist. The documentation's own troubleshooting order, check the FAQ, look for related issues, then run with `--debug` to confirm the config is loaded and the source files are found, tells you that the failure modes are configuration and file discovery problems, which is what you would expect of a path rewriting tool rather than a module system.

The fourth is packaging. The manifest pins `supportedArchitectures` for pnpm to the current OS and linux, and the current CPU and x64. For a project that publishes prebuilt binaries for several platforms, that is a constraint on how the package can be installed, and it is worth checking before you adopt the tool into a published package.

On maintenance, the release line is active: v1.9.5 on 2026-09-11, v1.9.6 and v1.9.7 both on 2026-09-30, with the last push to the repository on 2026-10-17. The licence is MIT, and the repository carries a `SECURITY.md`, a jest config, a pnpm workspace with a `projects/` directory, and husky for commit hooks.

Editorial conclusion

tsc-alias fits a Node or serverless project that uses `paths` aliases in tsconfig, that runs its output on plain Node or in a container without a resolver hook, and that wants the fix applied at build time rather than at run time. It does not fit a project bundled by a tool that already understands aliases, and it is the wrong tool if you need the alias to survive into the output for another tool to resolve. Before you add it, check that your `outDir` layout matches the tool's assumptions, decide whether `resolveFullPaths` should be on for ESM, and if you publish a package note that the pnpm build configuration only lists the current OS and linux with x64 and the current CPU architecture.

Frequently asked questions

How do I use tsc-alias in a TypeScript project?

Install it as a dev dependency with `npm install --save-dev tsc-alias` and add it after the compiler in your build script, for example `"build": "tsc --project tsconfig.json && tsc-alias -p tsconfig.json"`. The compiler must run first, because tsc-alias rewrites the emitted files.

What is the difference between tsc-alias and tsconfig-paths?

The project documents one line of difference: compile time, with no runtime dependencies. tsc-alias rewrites the aliases into relative paths after compilation.

What does resolveFullPaths do?

It attempts to replace incomplete import paths, those not ending in `.js`, with fully resolved paths for ECMAScript Modules compatibility, and it defaults to false. It works with resolveFullExtension, whose default is `'.js' | '.mjs' | '.cjs'`.

How do I configure tsc-alias for a monorepo with project references?

Set the followReferences option to true, which makes tsc-alias follow the TypeScript project references defined in tsconfig.json and run alias replacement on all referenced projects. It defaults to false, and the options can be set in the tsconfig.json under the `tsc-alias` key.

Can I use tsc-alias from a Node script instead of the command line?

Yes. `replaceTscAliasPaths(options?)` rewrites the output directory in one call, and `prepareSingleFileReplaceTscAliasPaths(options?)` returns a promise of a function that takes the file contents and path and returns the transformed contents synchronously, which is the form to use when the files are held in memory.

Official sources

  1. Issues
  2. justkey007/tsc-alias on GitHub
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/justkey007-tsc-alias.svg)](https://hysenlabs.com/projects/justkey007-tsc-alias)

Was this useful?