Skip to content

fix: one tsconfig and one disposal per federation build context - #139

Merged
Aukevanoost merged 4 commits into
mainfrom
issues/138
Sep 23, 2026
Merged

Aukevanoost merged 4 commits into
mainfrom
issues/138

Conversation

@Aukevanoost

@Aukevanoost Aukevanoost commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Closes #138.

native-federation/native-federation-core#145 builds shared mappings apart from exposed modules, so setup()/build() now run once per mapping bundle (mapping-bundle, or mapping-<pkg> with build: 'separate' | 'package') plus once for mapping-or-exposed. Each run gets its own Angular context. The adapter still assumed a single context, and broke in two ways.

1. Stale mappings in watch mode

Every context shared the one federation tsconfig, and each setup() replaced its files with only that context's own entry points. Two things in @angular/build (checked in 22.1.8) turn that into a silent failure:

  • The compiler plugin re-reads the tsconfig on every rebuild, so the mapping context rebuilt without its own entry points.
  • SourceFileCache.invalidate never clears typeScriptFileCache, so a file that dropped out of the program was served from its old emit, with no error.

A union of all entry points in files would fix the staleness, but Angular emits a context's whole program. Every context would then compile everything: N+1 full AOT compiles where core ran one before #145.

Fix: each context now compiles against a generated tsconfig that extends the federation tsconfig and lists only its own entry points. The generated files live at node_modules/.cache/native-federation/<project>/tsconfig/<hash>.<bundle>.json, and the user's tsconfig is never rewritten any more.

  • Default typeRoots are pinned to what the federation tsconfig resolves to. TypeScript derives them from the leaf config's own directory, which is now the cache dir, and would miss e.g. a pnpm workspace package's own node_modules/@types.
  • That needs TypeScript's config API, so the workspace's own compiler is loaded via createRequire. The adapter still doesn't depend on typescript at runtime.
  • json5 is dropped; only the old rewrite used it.

2. Mapping contexts never disposed

The builder disposed only mapping-or-exposed before the app build. The mapping contexts' compiler plugins therefore never reset Angular's shared TS compilation state (#47).

  • A mappings-only host has no mapping-or-exposed context at all. That dispose() threw, and the .catch(() => undefined) hid it, so nothing was disposed.
  • A no-arg dispose() is no option, because it stops esbuild, which the app build still needs.

Fix: the adapter gets disposeFederationContexts(), which disposes every mapping and exposed context and leaves esbuild running. The swallowing .catch is gone.

3. Stale files in tsconfig.federation.json

The files list in tsconfig.federation.json is now dead weight.

  • init and update22 stop seeding it.
  • A files key in the federation tsconfig itself gets a one-time build warning. An inherited files does not warn, since the app tsconfig it extends often has files: ["src/main.ts"].
  • A new 22.2.0 migration (update22-2) removes files from each project's tsconfig.federation.json and leaves other tsconfigs alone. It edits with jsonc-parser, already in the tree via @angular-devkit/* and now declared directly, so comments and formatting survive.

Verification

  • tsc --noEmit clean, knip clean, lint 0 errors (34 pre-existing warnings in unrelated files).
  • 251/251 tests pass. The context-tsconfig spec uses a real temp workspace and asserts what TypeScript parses from each generated config.
  • End to end on a copy of angular-examples/angular/tractor-store, on Angular 22.1.8 with packed core main and packed adapter:
    • Repro (explore with ignoreUnusedDeps: false, so the logging mapping is reachable only through files): on this PR's base, two edits to libs/logging during ng build --watch both rebuilt without error but kept serving the old bundle. With this PR, both edits land.
    • Production builds of host (mappings only) and explore pass.
    • ng update @angular-architects/native-federation --migrate-only --from 22.1.3 --to 22.2.0 removed exactly the files block from all four projects. The builds that followed pass without the warning.
  • At runtime, with the published core 4.7.0 and orchestrator 4.6.1: all four production builds served together in headless Chrome, through home → product (decide) → add to basket → basket (checkout). There were no console errors or failed requests, and each shared mapping was loaded once, from the host.

Before release

  • This PR waits for Angular 22.2, which releases in September.
  • The version must be ≥ 22.2.0, or ng update won't run the migration.

Core now builds shared mappings apart from exposed modules, one Angular context per mapping
bundle plus mapping-or-exposed, all against the one federation tsconfig. Rewriting its `files`
per context left the other contexts compiling without their entry points: Angular re-reads the
tsconfig on every rebuild and serves a dropped file from the shared SourceFileCache, so a
mapping edited in watch mode kept its old content without an error. A union of all entry
points would make every context compile everything instead.

Each context now compiles against a generated tsconfig in the cache dir that extends the
federation tsconfig and lists only its own entry points. The federation tsconfig is no longer
rewritten, the schematics stop seeding `files`, and a `files` key left in an existing one gets
a one-time warning. Default typeRoots are pinned to what the federation tsconfig resolves to,
since TypeScript derives them from the leaf config's directory.

Refs #138
Only 'mapping-or-exposed' was disposed, but core now builds each mapping bundle as a context
of its own, so their compiler plugins never reset Angular's shared TS compilation state before
the app build (#47). A mappings-only host has no 'mapping-or-exposed' at all: that dispose
threw and the catch hid it, leaving every context undisposed. A full dispose() is no option
either, since it stops esbuild, which the app build still needs.

The adapter now offers disposeFederationContexts(), which disposes every mapping and exposed
context and leaves esbuild running.

Refs #138
Each build context now supplies its own `files`, so the list the schematics used to seed only
goes stale and triggers a build warning. The 22.2.0 migration removes it from every project's
tsconfig.federation.json, editing in place so the file keeps its comments and formatting.
Other tsconfigs are left alone.

Refs #138
@Aukevanoost
Aukevanoost merged commit 2aaa6fa into main Sep 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Two federation build contexts after core #145

1 participant