fix: resolve tsconfig per file & map package imports from outDir to rootDir - #847
Open
sahiljadhav7 wants to merge 4 commits into
Open
sahiljadhav7 wants to merge 4 commits into
sahiljadhav7 wants to merge 4 commits into
Conversation
## Problem
tsx loaded a single tsconfig found from the current working directory.
Files not included by it were compiled with no tsconfig, so workspace
packages lost their own compiler options (e.g. `jsx`), and running from
a directory without a tsconfig ignored the entry's tsconfig. Path
aliases of that one tsconfig also applied to every non-dependency
importer, so a workspace package's alias could silently resolve to a
file in another package.
## Changes
- `createTsconfigLookup()` replaces the single parsed tsconfig with two
lookups:
- `compile(file)`: nearest tsconfig that includes the file (TypeScript
language server behavior)
- `resolve(importer)`: nearest tsconfig to the importer, for `paths` &
`allowJs`. Doesn't require inclusion, so scripts outside `include`,
`--eval`, stdin and REPL keep resolving aliases as before
- An explicit `--tsconfig` / `TSX_TSCONFIG_PATH` keeps the single-config
behavior
- ESM & CJS loaders use the lookup
Fixes privatenumber#696
Fixes privatenumber#482
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
## Problem
Package imports (`#`) that target the tsconfig outDir, e.g.
`"imports": { "#~/*": "./dist/*" }`, resolved to the compiled output.
Without a build it failed with ERR_MODULE_NOT_FOUND, and with a stale
build it silently loaded outdated code. TypeScript maps these targets
back to rootDir.
## Changes
- Map `#` import targets inside outDir/declarationDir to rootDir
(tsconfig directory when rootDir isn't set), mirroring TypeScript's
`tryLoadInputFileForPath`
- The source is tried before the compiled target, so a stale build
isn't loaded
- Uses the importer's tsconfig, in both ESM & CJS loaders
Fixes privatenumber#714
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
## Problem Package imports targeting outDir were mapped to rootDir with whichever tsconfig was nearest the importer, even one outside the package (e.g. a parent tsconfig whose outDir contains it). With an explicit tsconfig, imports from dependencies could also be mapped. Both could load an unrelated source file instead of the package's own file. ## Changes Match TypeScript's tryLoadInputFileForPath guards: - Only map when the tsconfig is inside the importing package - Never map targets inside node_modules Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Guard behavior that per-file tsconfig resolution must keep: - `--eval` & stdin resolve path aliases from the cwd tsconfig - scripts outside `include` still resolve path aliases - an explicit `--tsconfig` applies over a nearer tsconfig - package imports map from outDir to the tsconfig directory when rootDir isn't set Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #696, fixes #714, fixes #482
What was wrong
Monorepos (#696, #482). tsx loaded a single tsconfig, found by walking up from the current directory. Files outside that tsconfig got no tsconfig at all, so a workspace package lost its own settings (e.g.
jsx: react-jsx→ "React is not defined"). Running from a directory without a tsconfig also meant the entry file's own tsconfig was ignored. Worse, the app'spathswere applied to other packages' imports. A dependency's~/lib/labelcould quietly load the app's~/lib/labelfile with no error.Package imports (#714). With
"imports": { "#~/*": "./dist/*" }, tsx looked indist/whiletscmaps back tosrc/. With no build you gotERR_MODULE_NOT_FOUND. With an old build lying around, tsx silently ran the stale output.What changed
getTsconfig(file, { includes: true }), already in get-tsconfig v5).allowJscome from the tsconfig nearest the importing file. Scripts outsideinclude,--eval, stdin and the REPL keep resolving aliases just as before.--tsconfig/TSX_TSCONFIG_PATHbehave exactly as before: one config for the whole run.#imports that land inoutDir/declarationDirresolve to therootDirsource first, so stale builds are never picked up. Like TypeScript, this only happens when the tsconfig belongs to the importing package and the target isn't innode_modules. WithoutrootDir, the tsconfig's directory is the source root.Tests
Added tests in
tests/specs/tsconfig.ts, run for both ESM and CJS packages. They cover:#imports mapped to source, including the stale-build case;node_modulesguards;--eval, stdin, scripts outsideinclude, explicit--tsconfig, and norootDir.Each test failed before its fix. For the compatibility tests, I reintroduced each regression on purpose and checked that the right tests failed.
Full suite locally on Node 24.2.0: the same 8 tests fail on
masterand on this branch, so nothing new breaks. The failures seem to come from running 24.2 instead of the pinned 24.15.Notes for review
includeused to get no tsconfig. They now get the tsconfig that actually owns them. This is the intended fix, but it is a visible behaviour change.node_modulesguard on aliases. Per-file lookup covers that use case without applying the root config to every dependency.