microsoft / microsoft/TypeScript

[API] No module resolution API: no counterpart to ts.resolveModuleName

オープン
#64,069 コメント 0 件 リアクション 0 件 担当者 1 名 GitHub で見る

@andrewbranch がすでに取り組んでいます。

2026年8月28日 から。

API Request
主要言語
Go
スター
111k
フォーク
14.3k
平均マージ
2日 4時間
マージ済み PR(30日)
132

説明

### Description

The 7.x API has no counterpart to `ts.resolveModuleName`, and no other way to ask "what does this specifier resolve to from this file?". The compiler does the resolution internally when it builds a program, but a consumer cannot invoke it on its own.

That matters for tools that resolve outside of, or ahead of, a program:

- Finding a package's declaration entry from its directory, so that `exports` conditions, `typesVersions` and the `types`/`typings` fallbacks all apply — the thing `ts.resolveModuleName(manifest.name, path.join(dir, "__entry__.ts"), options, host)` answered in one call.
- Reporting where an import would resolve to, without materialising a program for it.
- Deciding which files to load before opening a project.

The roadmap in #63875 covers `createProgram`, `createSourceFile`, `transpileModule` and `parseCommandLine` under "top-level utility APIs", but resolution is not among them. microsoft/typescript-go#455 named "resolution behavior" as one of the critical use cases the API was meant to serve, so I may simply have missed where this is tracked — if there is an existing issue, this can be closed as a duplicate.

### Why a workaround isn't sufficient

Reimplementing it means reimplementing Node's `exports` algorithm. I have done so for the narrow case of a package resolving its own name, and it is about 40 lines that handle condition maps, arrays and the `.`/subpath split:

```ts
function selectExportTarget(target: unknown, conditions: ReadonlySet): string | undefined {
if (typeof target === "string") return target;
if (Array.isArray(target)) {
for (const alternative of target) {
const selected = selectExportTarget(alternative, conditions);
if (selected) return selected;
}
return undefined;
}
if (target && typeof target === "object") {
for (const [condition, value] of Object.entries(target)) {
if (conditions.has(condition)) {
const selected = selectExportTarget(value, conditions);
if (selected) return selected;
}
}
}
return undefined;
}
```

That covers the fixtures I have and nothing more. It does not handle `typesVersions`, symlink realpath resolution (pnpm layouts), extension substitution, or `paths`. Every consumer that needs resolution will write a different subset, each wrong in a different way, and none will agree with the compiler that ultimately type-checks the code.

### Suggested shape

Something close to the 6.x signature, answering from the same implementation the compiler uses:

```ts
resolveModuleName(
moduleName: string,
containingFile: string,
compilerOptions: CompilerOptions,
): { resolvedFileName: string; isExternalLibraryImport: boolean; extension: string } | undefined
```

A snapshot-scoped variant (`project.resolveModuleName(...)`) would work equally well for my case and might fit the existing model better, since options and the file system are already established there.

### Use case

Porting OpenRewrite's JavaScript/TypeScript parser from the 6.x API. One component enumerates the public types a package exports, and locates each package's declaration entry by resolving the package's own name from inside its directory. On 6.x that is a single `resolveModuleName` call; on 7.x it is the hand-rolled subset above, which loses `exports` maps that name declarations only under a condition the subset does not model.

Verified against `7.0.2` and `typescript@next` (`7.1.0-dev.20260827.1`).

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

評価

この issue はまだ評価されていません。

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。