fix(tsconfig): let project references take priority over their parent#1151
Conversation
Align with TypeScript's `isSourceOfProjectReferenceRedirect`: when a parent tsconfig has `references`, a referenced sub-project that includes the file always wins, even when the parent's `include` / default `**/*` also covers it. Previously the parent claimed ownership first and references were only consulted as a fallback, so a solution-style root (only `references`, no `include` / `files`) hid the referenced sub-project's `compilerOptions.paths`. Closes #1086.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1151 +/- ##
==========================================
- Coverage 93.14% 93.14% -0.01%
==========================================
Files 22 22
Lines 4159 4158 -1
==========================================
- Hits 3874 3873 -1
Misses 285 285 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Merging this PR will not alter performance
Comparing Footnotes
|
Verification — broader probe across 15 scenariosI built a side-by-side probe comparing
Result: 13 / 15 agree with TypeScript 6.0 (current stable).
Confirmed against
|
## 🤖 New release * `oxc_resolver`: 11.19.1 -> 11.19.2 * `oxc_resolver_napi`: 11.19.1 -> 11.19.2 <details><summary><i><b>Changelog</b></i></summary><p> ## `oxc_resolver` <blockquote> ## [11.19.2](v11.19.1...v11.19.2) - 2026-05-25 ### <!-- 1 -->🐛 Bug Fixes - *(tsconfig)* apply later-wins semantics for extends array ([#1156](#1156)) (by @Boshen) - *(tsconfig)* walk past a tsconfig that doesn't claim the file ([#1154](#1154)) (by @Boshen) - *(tsconfig)* let project references take priority over their parent ([#1151](#1151)) (by @Boshen) - *(tsconfig)* resolve `rootDirs` against the config that declared them ([#1150](#1150)) (by @Boshen) - *(tsconfig)* resolve `baseUrl` / `paths` against the canonical tsconfig path ([#1148](#1148)) (by @Boshen) - strip query fragments when calling `find_tsconfig` ([#1147](#1147)) (by @Boshen) - avoid panic in resolve_file for parentless paths ([#1053](#1053)) (by @Boshen) - *(dts)* strip ./ prefix from package entry when matching typesVersions ([#1051](#1051)) (by @Boshen) - *(dts)* expand Declaration to TypeScript|Declaration for package entry resolution ([#1050](#1050)) (by @Boshen) - *(dts)* prefer declaration extensions over JS in exports-resolved paths ([#1047](#1047)) (by @Boshen) - avoid wasm/wasi dead-code lint in NodePath ([#1043](#1043)) (by @Boshen) - *(napi)* replace panics with error returns to prevent WASM traps ([#1055](#1055)) (by @Boshen) ### <!-- 2 -->🚜 Refactor - remove clear_cache test that dynamically creates fixtures (by @Boshen) - move resolve and misc fixtures into fixtures/integration (by @Boshen) - replace ignored doctest with link to example (by @Boshen) - consolidate fixture directories for better test file mapping (by @Boshen) - replace `url` crate with `percent-encoding` ([#1065](#1065)) (by @Boshen) ### <!-- 4 -->⚡ Performance - *(cache)* pack CachedPathImpl::meta into a CachedMeta byte ([#1144](#1144)) (by @Boshen) - *(cache)* store canonical path as Box<Path> instead of PathBuf ([#1143](#1143)) (by @Boshen) - *(alias)* fast-reject alias entries by cached first byte ([#1142](#1142)) (by @Boshen) ### <!-- 6 -->🧪 Testing - *(tsconfig)* port lookup scenarios from typescript-go ([#1155](#1155)) (by @Boshen) - add 28 tests to improve coverage (92% → 93%) ([#1082](#1082)) (by @Boshen) ### Contributors * @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>
…#1161) ## Summary The default `include = **/*` was matching every path regardless of where the tsconfig sat, so a referenced sub-project with no `files`/`include` of its own claimed files *outside* its own directory and shadowed the parent's `compilerOptions.paths`. Combined with the solution-style heuristic treating omitted `files`/`include` the same as explicit `[]`, a root tsconfig with `references` and `paths` but no `include` lost its own paths for files it owns via the default glob. ## Repro ``` repro/ ├── tsconfig.json compilerOptions: { paths: { "@app/*": ["./src/*"] } }, references: [{ path: "./pkg" }] ├── index.ts import { util } from "@app/util"; ├── src/util.ts └── pkg/ ├── tsconfig.json compilerOptions: { composite: true } └── thing.ts ``` `tsc 6.0.3` and `tsgo 7.0.0-dev` both resolve `@app/util` → `src/util.ts`. oxc-resolver 11.19.1 did too; 11.19.2 returns `Cannot find module`. ## Root cause Two interacting bugs: 1. `is_glob_matches(_, GlobPattern::All)` short-circuited `**/*` to `true` regardless of which tsconfig owned the pattern. Fine when the only caller was the walked-up parent tsconfig, broken after #1151 started asking *references* the same question — their `**/*` then matched files outside the reference's directory. 2. `claims_ownership_of`'s solution-style check used `is_none_or(Vec::is_empty)`, conflating omitted and explicit-empty `files`/`include`. Per the TS spec only an explicit `[]` means "own no files"; an omitted `include` defaults to `**/*` and should fall through. This mirrors typescript-go's structure: `getFileNamesFromConfigSpecs` expands the default `**/*` via `ReadDirectory(basePath, ...)`, which is naturally scoped to the project directory — no equivalent "literal `**/*` matches everywhere" code path exists there. ## Changes - `src/tsconfig.rs` — `GlobPattern::All` now checks `path.starts_with(self.directory())`; `is_solution_style` requires explicit `Some([])` for both `files` and `include`. Dead `GLOB_ALL_PATTERN` constant removed. - `src/tests/tsconfig_project_references.rs` — `root_paths_apply_to_default_include_files` covers the repro. - `fixtures/tsconfig/cases/project-references-default-include/` — minimal fixture matching the issue. All 294 existing tests still pass. Closes #1159.
> [!IMPORTANT] > **This is a minor release.** Two changes alter default behavior compared to `1.0.3`. Please read this section before upgrading. Everything else is additive (new features, fixes, deps). ##⚠️ Notable behavior changes ### 1. `experimental.lazyBarrel` is now enabled by default (#9632) **What changed.** `experimental.lazyBarrel` now defaults to `true`. When a barrel module is recognized as side-effect-free, Rolldown skips compiling the re-exported modules that are never actually used. **Impact.** For codebases with large barrel files (component libraries such as Ant Design, `@mui/icons-material`, etc.) this is a meaningful build-time speedup, and for the vast majority of projects the emitted output is unchanged. In rare cases where a barrel is *incorrectly* treated as side-effect-free, the optimization could drop a module that was being relied on for its side effects. **How to opt out (backward compatible).** ```js // rolldown.config.js export default { experimental: { lazyBarrel: false }, } ``` > Note: this opt-out flag is planned to be removed in a future release. If you have a case where you must turn it off, please open an issue so we can fix the underlying detection instead. --- ### 2. `tsconfig` project-reference resolution now aligns with TypeScript Upgrading `oxc_resolver` (`11.19.1` → `11.20.0` in #9549, then `→ 11.21.0` in #9634) changes how a *solution-style* `tsconfig.json` (one that only lists `references` and delegates the real settings to `tsconfig.app.json` / `tsconfig.node.json`, as Vite scaffolds) is resolved, bringing it **in line with how TypeScript (`tsc`) itself behaves**: - **Reference match priority** (oxc-resolver [#1151](oxc-project/oxc-resolver#1151)): when the root has `references`, a referenced project that includes the file now **takes precedence over the root**, instead of the root matching it first (this is what TypeScript already does). So that project's `compilerOptions.paths` now apply. - **`allowJs`** (oxc-resolver [#1198](oxc-project/oxc-resolver#1198)): whether a `.js`/`.jsx`/`.mjs`/`.cjs` file is included is now decided by **each referenced project's own** `allowJs`, not the root's (again matching TypeScript). So `tsconfig.app.json` with `allowJs: true` + `paths` now resolves aliases for `.js` files even when the root doesn't set `allowJs`. For most projects this is a fix (the standard Vite `paths` aliases now resolve, closes #8468), but it **is** a behavior change if you relied on the previous behavior, where the root's `paths` / `allowJs` took precedence. **If you relied on the old "root wins" behavior.** There is no exact toggle back, because the old behavior was the bug being fixed. The recommended path is to align your config with TypeScript: declare the `paths` / `allowJs` on the referenced project that actually owns the files. If you must keep the old precedence while still using `references`: a referenced project's match wins, and **the first matching `references` entry takes priority** (the root is only a fallback when no reference claims the file). So extract the old root settings into their own config and list it **first**: ```jsonc // tsconfig.json (solution root) { "files": [], "references": [ { "path": "./tsconfig.base.json" }, // old root paths/allowJs — listed first, so it wins { "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" } ] } ``` `tsconfig.base.json` should carry the `paths` you previously declared on the root, plus `allowJs: true` if it needs to claim `.js` files (the extension is checked against each config's own `allowJs`). With no `include`, it defaults to `**/*` under its directory and claims every file first. Alternatively, bypass reference resolution entirely by pointing the top-level `tsconfig` option at a single config: `export default { tsconfig: './tsconfig.app.json' }`. --- ## [1.1.0] - 2026-06-03 ### 🚀 Features - enable `experimental.lazyBarrel` by default (#9632) by @shulaoda - `import.meta.glob` support `caseSensitive` option (#9594) by @btea - add `SOURCEMAP_BROKEN` warning for renderChunk hook (#9601) by @sapphi-red - add `SOURCEMAP_BROKEN` warning for transform hook (#9600) by @sapphi-red - add `@__NO_SIDE_EFFECTS__` hint for invalid `@__PURE__` before function declarations (#9505) by @Copilot - code-splitting: support group-local `includeDependenciesRecursively` (#9587) by @hyf0 ### 🐛 Bug Fixes - report TSCONFIG_ERROR instead of UNHANDLEABLE_ERROR for a missing tsconfig file (#9633) by @shulaoda - browser: add missing exports and ensure consistency with `rolldown` package (#9629) by @sapphi-red - should build test-dev-server when test-node (#9610) by @situ2001 - chunk-optimizer: refuse asymmetric merge for cyclic dynamic entries (#9320) (#9322) by @aminpaks - dev: handle the remaining errors in dev (#9570) by @h-a-n-a - handle slash-normalized ids with preserveModulesRoot (#9595) by @IWANABETHATGUY - json: preserve .default access on JSON default imports (#9568) by @IWANABETHATGUY - testing: remove unintended trigger_full_build from test harness (#9573) by @hyf0 ### 🚜 Refactor - js-regex: use regress native replace/replace_all (#9607) by @IWANABETHATGUY - remove never-constructed `ImportStatus` variants (#9606) by @Boshen ### 📚 Documentation - clarify that `RolldownBuild::close` method should be called in most cases (#9619) by @sapphi-red ### ⚡ Performance - avoid unnecessary intermediate sourcemaps (#9599) by @sapphi-red ### 🧪 Testing - add unit test for collapsing module sourcemap (#9626) by @sapphi-red - cover vite-alias regex capture-group expansion (#9602) (#9608) by @IWANABETHATGUY ### ⚙️ Miscellaneous Tasks - deps: update oxc_resolver to 11.21.0 (#9634) by @shulaoda - update invalid option diagnostic link to point to Rolldown docs (#9631) by @sapphi-red - deps: update vite+ to v0.1.24 (#9628) by @renovate[bot] - deps: update oxc resolver to v11.20.0 (#9549) by @renovate[bot] - deps: update dependency vite-plus to v0.1.24 (#9470) by @renovate[bot] - deps: update npm packages (#9614) by @renovate[bot] - deps: upgrade oxc to 0.134.0 (#9625) by @shulaoda - deps: update crate-ci/typos action to v1.47.0 (#9620) by @renovate[bot] - deps: update rollup submodule for tests to v4.61.0 (#9623) by @rolldown-guard[bot] - deps: update github actions (#9613) by @renovate[bot] - deps: update pnpm to v11.4.0 (#9616) by @renovate[bot] - deps: update rust crates (#9615) by @renovate[bot] - deps: update test262 submodule for tests (#9624) by @rolldown-guard[bot] - deps: update dependency @napi-rs/cli to v3.7.0 (#9588) by @renovate[bot] - deps: update dependency rust to v1.96.0 (#9596) by @renovate[bot] - re-enable WASI testing with proper infrastructure (#9397) by @Boshen ### ❤️ New Contributors * @aminpaks made their first contribution in [#9322](#9322) Co-authored-by: shulaoda <165626830+shulaoda@users.noreply.github.com>
…ior (#9641) ## What Updates `packages/rolldown/src/options/docs/tsconfig.md` to describe how a tsconfig's `references` are resolved as of **v1.1.0**. Docs-only, no behavior or code change. ## Why The reference resolution behavior changed in v1.1.0 through the `oxc_resolver` upgrades: - **#9549** (`oxc_resolver` → `11.20.0`) brought in oxc-resolver [#1151](oxc-project/oxc-resolver#1151): a referenced project that includes the file takes precedence over the root, instead of the root claiming it first. - **#9634** (`oxc_resolver` → `11.21.0`) brought in oxc-resolver [#1198](oxc-project/oxc-resolver#1198): whether a `.js`/`.jsx`/`.mjs`/`.cjs` file is included is decided by each referenced project's own `allowJs`, not the root's. The existing docs still described the previous model (root matches first, references consulted only as a fallback), which no longer holds. A solution-style `tsconfig.json` (only `references`, as Vite scaffolds) now resolves the way TypeScript does, which is also what made the standard Vite `paths` aliases resolve correctly again (#8468). ## Changes - Rewrote the `references` resolution paragraph: a referenced project that includes the file takes precedence over the root, each referenced project uses its own `allowJs` (so a `.js`/`.jsx`/`.mjs`/`.cjs` file is only included where that project enables it), and the root is used only when no referenced project includes the file.
Summary
Align with TypeScript's
isSourceOfProjectReferenceRedirectsemantics: when a parent tsconfig hasreferences, a referenced sub-project that includes the file always wins, even when the parent'sinclude/ default**/*glob also covers it.Previously the parent claimed ownership first and references were only consulted as a fallback. The biggest practical impact: a solution-style root (only
references, noinclude/files) defaulted its include to**/*, claimed every file, and hid the referenced sub-project'scompilerOptions.paths— exactly the bug in #1086 / rolldown/rolldown#8468.Changes
src/tsconfig.rs—resolve_tsconfig_solutionno longer requires that the file is not included in the parent; references are checked first whenever they exist.fixtures/tsconfig/cases/project-references-priority/— minimal repro: solution-style root + referencedtsconfig.app.jsonwithpaths; verified to matchtsgo/tsserverbehavior.src/tests/tsconfig_project_references.rs— new testreferenced_paths_win_over_root_with_no_pathscovering the fixture.src/tests/tsconfck.rs—part_of_solution's tworeferenced-extends-originalexpectations updated from the root tsconfig to the matching referenced sub-projects. Verified against TypeScript 5.5.4tsserver(projectInforequest); tsconfck's own behavior diverges from TypeScript here and we deliberately follow TypeScript per Align tsconfig project references resolution priority with typescript #1086.Verification
Probed all 18
part_of_solutioncases againsttsserver: 16/18 now match exactly (the two unrelated divergences pre-date this change and concern empty-include defaulting + solution-style fallback to inferredProject; left for follow-up).Closes #1086. Helps rolldown/rolldown#8468.