Repository navigation
fix(esbuild): keep the node_modules fallback for types in context tsconfigs (#166) - #168
JasinRefiku wants to merge 2 commits into
Conversation
…sconfigs (native-federation#166) The per-context tsconfig pins typeRoots to the federation tsconfig's default roots, because TypeScript derives them from the leaf config's directory, which is now the cache dir. With typeRoots set, TypeScript skips its node_modules lookup for `types` entries, so packages that ship their own typings, like @angular/localize, failed with TS2688 and every `$localize` with TS2304. When an entry resolves only through that fallback for the federation tsconfig, the node_modules folders it walks are now appended after the @types roots, nearest first. Nothing changes when typeRoots are explicit, when every entry resolves from @types, or next to the `*` wildcard, which would otherwise take every package in node_modules as a type library. Fixes native-federation#166
There was a problem hiding this comment.
Hi thanks for the contribution! however, rather than widening typeRoots, write the resolved types into the context tsconfig as absolute paths. TypeScript loads those directly, so typeRoots stays as it is:
function resolveTypes(ts: typeof TypeScript, options: TypeScript.CompilerOptions, federationTsConfig: string) {
// What createProgram uses as the containing file for `types`.
const containingFile = path.join(path.dirname(federationTsConfig), '__inferred type names__.ts');
return options.types?.map(name =>
name === '*'
? name
: (ts.resolveTypeReferenceDirective(name, containingFile, options, ts.sys)
.resolvedTypeReferenceDirective?.resolvedFileName ?? name)
);
}compilerOptions: { typeRoots: federation.typeRoots.map(toPosix), types: federation.types?.map(toPosix) },This will help because widening typeRoots changes every /// <reference types> lookup in the program, including ones in dependencies. Under pnpm that can swap in a hoisted package of another version. It also puts all @types roots before all node_modules roots, which isn't TypeScript's nearest-first order.It also covers cases the PR doesn't at the moment. Finally, there's no special case for explicit typeRoots or *. Unresolved entries pass through unchanged, and nothing is written when types is unset.
Also, try to keep the diagnose specs and add the exports-only and relative cases. Also cut the comment down to one line that references #166 (.claude/rules/comments.md).
…tive-federation#166) Resolve each `types` entry from the federation tsconfig's directory and write the resolved file into the context tsconfig, so typeRoots stay pinned. Covers packages typed only through `exports` and relative entries. When none of the pinned type roots exists on disk, add the context tsconfig's directory as one, since TypeScript loads absolute `types` entries only through an existing type root.
|
Switched to resolving One addition: TypeScript loads an absolute I've updated the PR description to match. |
Fixes #166.
Problem
writeContextTsConfigwrites one tsconfig per build context into the cache dir. Itextendsthe federation tsconfig and pinscompilerOptions.typeRootstots.getEffectiveTypeRoots(...). That pin came in with #138 (65a0dc3): without an explicittypeRoots, TypeScript builds the default roots from the leaf config's directory, which is nownode_modules/.cache/native-federation/tsconfig/. A project-localprojects/mfe1/node_modules/@types(a pnpm workspace package, say) would then drop out.The pin also switches off TypeScript's
node_modulesfallback fortypesentries. InresolveTypeReferenceDirective(node_modules/typescript/lib/typescript.js, TS 6.0.3):@types/<name>.node_modulesup from the containing directory. It is skipped whenoptions.typeRootsis set and the containing file is__inferred type names__.ts, which is the case for everytypesentry:So a package that ships its own typings, like
@angular/localize("typings": "./types/localize.d.ts"), never resolves: TS2688 for the type library, then TS2304 for each$localize. TypeScript 5.9 has the same check, so this is not specific to TS 6. The adapter just setstypeRootson every build.Change
The type roots stay pinned as before.
readFederationTsConfignow also resolves eachtypesentry the way the federation tsconfig's own program does:ts.resolveTypeReferenceDirectivewith its options, from__inferred type names__.tsin its directory. The context tsconfig gets the absolute path of the resolved file, which TypeScript loads directly.typeRootsis not widened, so/// <reference types>lookups elsewhere in the program behave as they do today.*and entries that don't resolve are written unchanged. With explicittypeRoots, TypeScript skips the fallback for the federation tsconfig as well, so an entry resolves or fails the same way in both.types: nothing is written, and the context tsconfig is identical to today's.typesentry only through a type root that exists (primaryLookupskips missing roots, and the fallback is off becausetypeRootsis set). In a workspace without anynode_modules/@types, every entry would fail with TS2688. In that case the context tsconfig's own directory is appended as a type root. It holds only the generated.jsonfiles, so it adds no type libraries, including under*.This also covers what the first version of this PR listed as known limits: packages typed only through
exports, and relative entries such as"./src/types".Alternatives considered
node_modulesfolders totypeRoots(the first version of this PR). This changes every/// <reference types>lookup in the program, dependencies included. Under pnpm a hoisted copy of another version can win. It also missesexports-only and relative entries.typeRootswhen the federation tsconfig sets them (the issue's first suggestion). This brings back what Two federation build contexts after core #145 #138 guarded against: anything in anode_modulesbetween the federation tsconfig and the workspace root is missed.node_modules(the issue's workaround). This breaks*on TS 6. On TS 5.x with notypesset, it auto-includes every package as a type library.Testing
pnpm test,pnpm typecheck,pnpm lintand knip pass, apart from one spec that also fails onmain.sourcemap-ignorelist-plugin.spec.ts› "marks node_modules sources of a bundle emitted into the federation cache" fails on macOS becauseos.tmpdir()is under the/var→/private/varsymlink. It fails the same way ond2fb764.write-context-tsconfig.spec.ts, under "type libraries intypes". Each builds a realts.createProgramfrom the generated config and checks its pre-emit diagnostics:@acme/localize(typingsin its ownpackage.json) in the workspacenode_modulesresolves. Before the fix this gave the issue's TS2688 + TS2304.projects/mfe1/node_modulesresolves.node_modules/@typesanywhere, the context tsconfig's directory is added as a type root and the entry resolves.exportsresolves (moduleResolution: bundler)../src/env) resolves from the federation tsconfig, not the cache dir.@typespackages in the workspace and inprojects/mfe1/node_modules/@typesstill resolve.*next to a self-typed package works. It failed before, and with the first version of this PR.typeRootsare kept, and an entry they don't resolve is written unchanged.types, none are written.typesisn't written, and 4 if the extra type root is left out. The existing Two federation build contexts after core #145 #138 specs are unchanged and pass.