feat(tsconfig): support package.json imports field in extends#1199
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1199 +/- ##
==========================================
- Coverage 93.77% 93.77% -0.01%
==========================================
Files 22 22
Lines 4212 4227 +15
==========================================
+ Hits 3950 3964 +14
- Misses 262 263 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 04a4896cb8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
04a4896 to
ebe4f3c
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ebe4f3c81a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Merging this PR will not alter performance
Warning Please fix the performance issues or acknowledge them on CodSpeed. Performance Changes
Tip Investigate this regression by commenting Comparing Footnotes
|
5746f35 to
cd1f0f2
Compare
Merge activity
|
## What
Resolve `#`-prefixed Node.js subpath imports in tsconfig `extends` through the nearest `package.json` `imports` field:
```jsonc
// package.json
{ "imports": { "#base": "./common.tsconfig.json" } }
// tsconfig.json
{ "extends": "#base" }
```
Previously this failed with `TsconfigNotFound`: `get_extended_tsconfig_path` only handled `/`, `.`/`..`, and bare package specifiers (the last via `exports`/`node_modules`, which never consults `imports`).
## Why
TypeScript supports this — it's used in monorepos to avoid brittle `../../../tsconfig.json` paths. Verified against `typescript-go`: `tsgo --showConfig` resolves the fixture to the same targets this PR asserts (`#string` → es2020, `#conditional` node-branch → es6).
Reported in rolldown/rolldown#9611 (labeled `bug: upstream`).
## How
- New `Some(b'#')` arm in `get_extended_tsconfig_path` that routes through `load_package_imports` — the same wrapper the resolver uses for `require("#x")`, so it gets the correct package-scope lookup (`LOOKUP_PACKAGE_SCOPE`) and result verification (`resolve_esm_match`).
- Extracted `tsconfig_extends_resolver` so the `#` (imports) and bare-package (`exports`/`node_modules`) arms share one lookup config (conditions `["node", "import"]`) — `extends: "pkg"` and `extends: "#pkg"` now agree on which condition wins.
- An undefined `#import` (or a missing target) is reported as `TsconfigNotFound`, consistent with how a missing bare-package or relative `extends` target is reported — and with `tsgo`, which emits `File '#missing' not found`.
## Tests
One fixture (`fixtures/tsconfig/cases/extends-imports/`) and one test (`test_extend_imports`) covering, via its `package.json` `imports` map:
- `#string` → string target → `target: ES2020`
- `#conditional` → `{ node, default }` object → `node` wins over `default` → `target: ES2015`
- `extends: "#missing"` (not in `imports`) → `TsconfigNotFound`
All cross-checked against `tsgo` (es2020 / es6 / `File '#missing' not found`).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
cd1f0f2 to
4a9f78c
Compare
## 🤖 New release * `oxc_resolver`: 11.20.0 -> 11.21.0 * `oxc_resolver_napi`: 11.20.0 -> 11.21.0 <details><summary><i><b>Changelog</b></i></summary><p> ## `oxc_resolver` <blockquote> ## [11.21.0](v11.20.0...v11.21.0) - 2026-06-03 ### <!-- 0 -->🚀 Features - *(tsconfig)* support package.json imports field in extends ([#1199](#1199)) (by @Boshen) ### <!-- 1 -->🐛 Bug Fixes - *(tsconfig)* apply each referenced project's own `allowJs` ([#1198](#1198)) (by @shulaoda) - make symlink_metadata VPath-aware for Yarn PnP ([#1183](#1183)) (by @Boshen) ### <!-- 4 -->⚡ Performance - borrow relative main field instead of allocating a "./" prefix ([#1187](#1187)) (by @Boshen) - *(cache)* move package.json path into parse instead of cloning ([#1186](#1186)) (by @Boshen) - eliminate symlink stat syscalls by reusing canonicalization ([#1184](#1184)) (by @Boshen) - reduce resolution syscalls by unifying stat and lstat ([#1182](#1182)) (by @Boshen) ### <!-- 9 -->💼 Other - add baselines for each package manager x node_modules layout ([#1176](#1176)) (by @Boshen) ### Contributors * @shulaoda * @Boshen * @renovate[bot] </blockquote> </p></details> --- This PR was generated with [release-plz](https://github.com/release-plz/release-plz/). Co-authored-by: oxc-guard[bot] <276638029+oxc-guard[bot]@users.noreply.github.com>
What
Resolve
#-prefixed Node.js subpath imports in tsconfigextendsthrough the nearestpackage.jsonimportsfield:Previously this failed with
TsconfigNotFound:get_extended_tsconfig_pathonly handled/,./.., and bare package specifiers (the last viaexports/node_modules, which never consultsimports).Why
TypeScript supports this — it's used in monorepos to avoid brittle
../../../tsconfig.jsonpaths. Verified againsttypescript-go:tsgo --showConfigresolves the fixture to the same targets this PR asserts (#string→ es2020,#conditionalnode-branch → es6).Reported in rolldown/rolldown#9611 (labeled
bug: upstream).How
Some(b'#')arm inget_extended_tsconfig_paththat routes throughload_package_imports— the same wrapper the resolver uses forrequire("#x"), so it gets the correct package-scope lookup (LOOKUP_PACKAGE_SCOPE) and result verification (resolve_esm_match).tsconfig_extends_resolverso the#(imports) and bare-package (exports/node_modules) arms share one lookup config (conditions["node", "import"]) —extends: "pkg"andextends: "#pkg"now agree on which condition wins.#import(or a missing target) is reported asTsconfigNotFound, consistent with how a missing bare-package or relativeextendstarget is reported — and withtsgo, which emitsFile '#missing' not found.Tests
One fixture (
fixtures/tsconfig/cases/extends-imports/) and one test (test_extend_imports) covering, via itspackage.jsonimportsmap:#string→ string target →target: ES2020#conditional→{ node, default }object →nodewins overdefault→target: ES2015extends: "#missing"(not inimports) →TsconfigNotFoundAll cross-checked against
tsgo(es2020 / es6 /File '#missing' not found).🤖 Generated with Claude Code