Repository: TypeScript
Issue: Improve support for internal packages by resolving path aliases
Labels: Suggestion,Awaiting More Feedback

### 🔍 Search Terms

Internal packages
Path alias
Resolve paths

### ✅ Viability Checklist

- [X] This wouldn't be a breaking change in existing TypeScript/JavaScript code
- [X] This wouldn't change the runtime behavior of existing JavaScript code
- [X] This could be implemented without emitting different JS based on the types of the expressions
- [X] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- [X] This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- [X] This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals

### ⭐ Suggestion

Add a `tsconfig.json` to the `exports` field in `package.json` so that internal packages can share their TypeScript config especially their path aliases. 

```json
// ./libs/math/package.json
{
  "name": "@repo/libs-math",
  "version": "1.0.0",
  "private": true,
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "exports": {
    // exports local tsconfig.json so consumers can resolve local path aliases
    "./tsconfig.json": "./tsconfig.json",
    ".": {
      "import": "./src/index.ts",
      "require": "./src/index.ts",
      "types": "./src/index.ts"
    }
  }
}
```

### 📃 Motivating Example

Please find a reproduction example here: https://github.com/zirkelc/typescript-internal-packages

> Install all dependencies with `pnpm install` in the project root. Then open `apps/foo/src/index.ts` and you will see that TypeScript cannot resolve the dependency to `@repo/libs-with-alias` correctly.

---

I'm using [internal packages](https://turbo.build/repo/docs/handbook/sharing-code/internal-packages) to share code in my monorepo. An internal package is package which is NOT published to NPM and which is NOT explicitly compiled with TypeScript before it can be used by other packages. It exports the source TypeScript `*.ts` files from the `package.json` via the `main`, `types`, and `exports` fields:

```json
// ./libs/math
{
  "name": "@repo/libs-math",
  "private": true,
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "exports": {
    ".": {
      "import": "./src/index.ts",
      "require": "./src/index.ts",
      "types": "./src/index.ts"
    }
  }
}
```

```ts
// ./libs/math/src/index.ts
export const add = (a: number, b: number) => {
	return a + b;
};
```

This internal package is meant to be installed by other packages in the same repo:

```json
// ./apps/foo
{
  "name": "@repo/apps-foo",
  "dependencies": {
    "@repo/libs-math": "workspace:*"
  }
}
```

And can be used like a normal dependency which resolve to the original uncompiled TypeScript code:

```ts
// ./apps/foo/src/index.ts
import { add } from '@repo/libs-math'
//              ^?  Imports the typescript code from the internal package
```

Everything good so far. This pattern works well as long as none of the internal packages is using TypeScript path aliases. However, let's re-structure the internal package and use path aliases like `~/math`:

```json
// ./libs/math/tsconfig.json
{
  "extends": "@tsconfig/node20/tsconfig.json",
  "compilerOptions": {
    "paths": {
      "~/math": ["./src/math/index.ts"],
    },
  },
  "include": [ "src"]
}
```

The path alias `~/math` points to the sub-folder `./src/math/index.ts`. The previous `./src/index.ts` becomes a simple barrel file  exports

```ts
// ./libs/math/src/index.ts
export * from '~/math'
// ^? Exports from local path alias pointing to ./libs/math/src/math/index.ts
```

Now, the internal package `@repo/libs-math` uses path aliases which are defined its own local `tsconfig.json`. However, the previous import in `./apps/foo` will now start to fail:

```ts
// ./apps/foo/src/index.ts
import { add } from '@repo/libs-math'
//               ^? Module '"@repo/libs-math"' has no exported member 'add'
```

TypeScript doesn't find the function anymore, even though go to definition to `@repo/libs-math` still works.

I assume the reason is the following: TypeScript resolves the imports starting from the app `./apps/foo/src/index.ts` to the internal package `./libs/math/src/index.ts`. However, TypeScript still works inside the `./apps/foo/tsconfig.json` and is not aware of the path aliases defined by the `./libs/math/tsconfig.json`. It would need a way to recognize it is in a different package now and must look for the _right_ `tsconfig.json` in `./libs/math`.

That's where my idea of exporting the `tsconfig.json` in the `package.json` comes in. TypeScript would notice it is working in a different package when it was moving from `./apps/foo/src/index.ts` to `./libs/math/src/index.ts`. Then it sees this new package has a local TypeScript config exported from the local `./libs/math/package.json`. It sees the path aliases and applies these aliases for all paths inside `./libs/math/*`.


### 💻 Use Cases

There are two possible workarounds: 
1. global path alias: create a root-level `tsconfig.paths.json` which defines all path aliases for the entire project. Each package-level `tsconfig.json` extends from this config. There are two disadvantages to this workaround:
- packages cannot define package-level path aliases as they would overwrite the inherited root-level aliases
- each package can import from every other package via path aliases. that means you could accidentally import from the internal package `@repo/libs-math` via a typescript path alias instead of a node package (workspace) import.

2. project references: typescript project references can solve this issue, but they add a lot of complexity due intermediate build steps and overhead in extra multiple `tsconfig.json`. The package `@repo/apps-foo` has a node dependency to `@repo/libs-math` via `package.json`. For project references, this dependency must be replicated again in the `tsconfig.json` via `references`. So the dependency graph of `package.json` must be repeated for every new internal package.