fix(js,ts): an import from the app's root maps through the app's own tsconfig paths (#2740)

bigbluebutton's client imports nearly everything from its root,
`import Icon from '/imports/ui/components/common/icon/component'`, which its
tsconfig maps with `"paths": { "/*": ["*"] }`. tsc maps a rooted name through
`paths` (only `./` and `../` count as relative there) and never reads it
from `baseUrl`. ownConfigCandidates refused every specifier starting with
`/`, so all 2,375 of these imports stayed unresolved and every name they
import linked by bare-name matching, often to the wrong one of several
same-named components.

A rooted specifier now goes through the nearest config's `paths` patterns
only; `baseUrl` is still never applied to it, and a protocol-relative
`//host/x` stays out. On bigbluebutton the unresolved refs fall from 26,189
to 22,565 and its rooted imports from 2,375 unresolved to 1 (a module that
isn't in the repository); 13 control repos are byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-10-21 10:08:27 +00:00
committed by GitHub
co-authored by Claude Opus 5.5
parent ada1445458
commit aa01692620
3 changed files with 135 additions and 3 deletions
+11 -3
View File
@@ -1381,17 +1381,25 @@ function resolveFromOwnConfig(
return null;
}
/** Where the importing app's own config reads a bare specifier from (see resolveFromOwnConfig), or null when it reads none. */
/**
* Where the importing app's own config reads a bare specifier from (see
* resolveFromOwnConfig), or null when it reads none. A rooted one,
* `/imports/ui/components/icon/component`, is a path: tsc maps it through
* `paths` (bigbluebutton's `"/*": ["*"]`, kept from its Meteor days) and
* never reads it from `baseUrl`.
*/
function ownConfigCandidates(
importPath: string,
fromFile: string,
language: Language,
context: ResolutionContext
): string[] | null {
if (!ESM_IMPORT_LANGUAGES.has(language) || !importPath || importPath.startsWith('/') || /^[A-Za-z][\w+.-]*:/.test(importPath)) return null;
if (!ESM_IMPORT_LANGUAGES.has(language) || !importPath || importPath.startsWith('//') || /^[A-Za-z][\w+.-]*:/.test(importPath)) return null;
const config = context.getNearestAliases?.(fromFile) ?? context.getProjectAliases?.();
if (!config) return null;
const candidates = aliasAndBaseUrlCandidates(importPath, config, context.getProjectRoot());
const candidates = importPath.startsWith('/')
? applyAliases(importPath, config, context.getProjectRoot())
: aliasAndBaseUrlCandidates(importPath, config, context.getProjectRoot());
return candidates.length > 0 ? candidates : null;
}