fix(bundler): add exposed modules to the federation tsconfig, and scaffold it - #124
Merged
Conversation
updateFederationTsConfig filtered out every entry point whose fileName starts with '.', which is exactly how core hands over exposes -- their `file` is passed through verbatim from federation.config and is workspace-root-relative. Only shared mappings (absolute) ever made it into `include`, so exposed modules had to be listed by hand or the angular-compiler plugin failed with "File 'x' not found in TypeScript compilation". The extraction refactor in b044cf5 dropped the half of createFederationTsConfig that assigned those local entry points to `files`. Restore the behaviour by resolving relative fileNames against the workspace root instead of discarding them, and append them to `include` rather than overwriting `files` -- the config is now the user's own tsconfig, not a generated copy. The optimizedMappings gate moves into the function: mappings are still only added once ignoreUnusedDeps has pruned them, but exposes are added unconditionally. Refs #113
The init schematic never created a federation tsconfig and never set `tsConfig` on the generated build/serve targets, so the federation build fell back to the app tsconfig and then wrote its entry points into it. A fresh clone also hit ENOENT because the builder updates that file but never creates it (#101). Generate `<projectRoot>/tsconfig.federation.json` extending the app tsconfig -- it drives esbuild's module resolution, so it needs the workspace paths -- with an empty program the builder fills in per build. Point both the build and serve targets at it. Refs #101, #113
The federation tsconfig is now the builder's to rewrite only when the target points at one. Without `tsConfig` the builder falls back to the Angular target's own tsconfig, whose include already covers the exposes; rewriting that stripped its comments for no gain. `files` replaces `include` as the key the builder owns: it is an explicit file list, is not filtered by the inherited `exclude`, and rewriting it wholesale prunes entry points that were renamed or removed. The schematic seeds it from the component it exposes, so the first build is a no-op, and a build without entry points of its own falls back to main.ts rather than handing the compiler an empty program. Drops the `optimizedMappings` filter. With `ignoreUnusedDeps: false` core hands over every tsconfig path mapping, used or not, and all of them are bundled — leaving them out of the program failed the build with "not found in TypeScript compilation". update22 backfills the tsconfig for projects federated earlier; its collection version moves to 22.1.1 so `ng update` reaches them.
Exposes from federation.config take precedence over the option in both places core consults it, so the schema descriptions now say so and point at the federation tsconfig's include for adding files to the program. Also types the array's items as string, matching schema.d.ts.
managedTsConfig carried a path that was never read — the bundler only tested it for truthiness and rewrote tsConfigPath, which the path always equalled. manageTsConfig says what the flag actually decides. Corrects the rationale recorded with it: the fallback tsconfig is off limits not because its include already covers the exposes, but because `files` there is Angular's. Projects scaffolded with the older files/include shape keep main.ts in `files` and only .d.ts in `include`, so replacing files would drop the app's own entry point from the program.
A tsconfig the NF target declares is read to have its files replaced, so an absent one surfaced as a bare ENOENT from inside the bundler, naming a path with no context. Throw before the read instead. The check sits after the nothing-to-compile return, so a build with no entry points of its own stays a no-op rather than failing on a file it never needed.
…tion-tsconfig The module has not created anything since the schematic took over scaffolding; it replaces `files` in a tsconfig that must already exist. The file name now matches its export and contrasts with the schematic's generate- step.
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.
Exposed modules never made it into the federation tsconfig, so the angular-compiler plugin could not find them in the TypeScript program. Users had to list them by hand, and there was nothing pointing them at that — the message blames
files/includeon a file the schematic never created in the first place.The bug
updateFederationTsConfigfiltered out every entry point whosefileNamestarts with.:That is exactly how core hands over exposes.
config.exposes[key].fileis passed through verbatim fromfederation.config(normalizeExposesonly wraps strings into{ file }) and is workspace-root-relative —'./projects/mfe1/src/bootstrap.ts'. Shared mappings are absolute, so only they ever reachedinclude.This is a regression. Before
9ae5548,createFederationTsConfigassigned those local entry points totsconfig.files; the extraction refactor inb044cf5kept only the mappings half.Repro
Drop
files: ["src/bootstrap.ts"]fromprojects/mfe1/tsconfig.federation.jsonin the playground and build:Nothing re-adds it. (It also takes esbuild down with
fatal error: all goroutines are asleep - deadlock!— filed separately, not addressed here.)Changes
fix(bundler)— resolve relative entry points against the workspace rootNo filtering: every entry point that gets bundled goes into the program. With
ignoreUnusedDeps: falsecore hands over every tsconfig path mapping, used or not, and skipping those failed the build with the same "not found in TypeScript compilation" error this PR set out to fix.feat(schematics)— scaffold<projectRoot>/tsconfig.federation.jsonGenerated by
ng addand wired into the build and serve targets. It extends the app tsconfig because it also drives esbuild's module resolution and so needs the workspace paths:{ "extends": "./tsconfig.app.json", "files": ["src/app/app.ts"], "include": ["src/**/*.d.ts"] }extends, the include glob and the seededfilesare all derived fromprojectRoot/projectSourceRootand the exposed component, not hardcoded. Guards: skips if the file exists, skips if the project is already on the NF builder, falls back to theesbuildtarget'stsConfig, throws if neither has one. This also closes #101 — the builder updated that file but never created it, so a fresh clone hit ENOENT.Note that
files: []on its own is a TypeScript error (TS18002); it is legal here only because the config also carriesextends. Both keys have to stay.fix(bundler)— only manage a tsconfig the target declares itselfThe two commits above rewrote whatever tsconfig the build resolved to. For a project federated before this PR that is the app's
tsconfig.app.json, sincetsConfigon the NF target is what the builder falls back from.Why the fallback is off limits — it is not just about comments. The builder replaces
files, and on the Angular target's tsconfig that key is Angular's own. Current CLI templates (checked across@schematics/angular20.3 → 22.0) scaffoldinclude: ["src/**/*.ts"]with nofiles, so a rewrite there would be additive noise plus the loss of the two comment lines. But projects carried forward from older CLIs — the playground's ownmfe1,mfe2andhostamong them — have:Replacing
filesthere evictssrc/main.tsfrom the program:includematches no.ts, andmain.tsis not reachable from an expose because the import runs the other way. That same file is what the nested@angular/build:applicationtarget compiles with, so the app build dies with the very "not found in TypeScript compilation" error this PR set out to fix.ng updatenever rewrites tsconfig shape, so this is exactly the population the fallback exists to serve.So the rewrite is gated on the NF target declaring a
tsConfigof its own. Everyone else's tsconfig is left untouched, andupdate22backfills the dedicated one; its collection version moves to22.1.1song updateactually reaches projects already on 22.x. All four of its steps are idempotent.Ownership split. The schematic owns
extendsandinclude; the builder ownsfiles, and replaces it each build rather than appending:{ "extends": "./tsconfig.app.json", // schematic "include": ["src/**/*.d.ts"], // schematic "files": ["src/app/app.ts", "../../libs/ui/src/index.ts"] // builder, per build }filesrather thaninclude: it is an explicit file list (entry points are never globs), it is not filtered by theexcludeinherited fromtsconfig.app.json, and replacing it wholesale means an expose that was renamed or removed leaves nothing stale behind. Because the schematic seeds it from the component it just exposed,ng addfollowed by a build leaves a clean working tree.A build with no entry points of its own — a plain host, no exposes and no shared mappings — falls back to the project's
main.tsrather than handing the Angular compiler an empty program.Follow-ups
docs(builders)— documententryPointsin both schemas. Neither had a description. It reads as an override and is not one: core resolves it as the fallback toexposesin the only place it consumes it (getUsedDependenciesFactorytakes exposes first and falls back to the option), and the tsconfig'sfilestreats it the same way. So it has no effect at all on a project that exposes anything, and the additive escape hatch isincludein the federation tsconfig, which the builder never touches. Both entries now say so, and declareitems: { type: "string" }to matchschema.d.ts.refactor(bundler)— the ownership flag is a boolean.managedTsConfigcarried a path that was never read: the bundler only tested it for truthiness before rewritingtsConfigPath, which the path always equalled.manageTsConfig?: booleansays what it actually decides. Behaviour is unchanged, including for a target that deliberately pointstsConfigattsconfig.app.json— that is still managed.fix(bundler)— fail with the path when the managed tsconfig is missing. Nothing upstream validates that file: core reads the workspace roottsconfig.base.json/tsconfig.jsonfor shared mappings and only forwardsfedOptions.tsConfigto the adapter, never opening it. So an absent file surfaced as a bareENOENTfrom inside the bundler, naming a path with no context — #101's symptom for the config states the schematic cannot reach (a hand-editedangular.json, a gitignored tsconfig, a deleted file). It now throws before the read, naming the path as written inangular.json. The check sits after the nothing-to-compile return, so a build with no entry points of its own stays a no-op rather than failing on a file it never needed.refactor(bundler)—create-federation-tsconfig.ts→update-federation-tsconfig.ts. It has not created anything since the schematic took over scaffolding; it replacesfilesin a file that must already exist. The name now matches its export and contrasts with the schematic'sgenerate-step.Verification
182 tests pass,
tsc --noEmitclean, eslint clean.The tsconfig mechanism is checked end-to-end against a real filesystem and real TypeScript config resolution (
ts.parseJsonConfigFileContentover a scaffolded workspace: root tsconfig withpaths, antsconfig.app.jsonwith comments, an expose, a mapped lib, an ambient.d.ts):app.ts,globals.d.tslibs/ui/src/index.ts,app.ts,globals.d.tsmain.tsfallbackcompilerOptions.pathsresolve through the two-levelextendschain in all four, andtsconfig.app.jsoncomes out byte-identical with its comments intact.New coverage: 8 tests for the schematic step and 5 for the
update22backfill, neither of which had any.update-federation-tsconfig.spec.tsis reworked aroundfiles, including a regression test for theignoreUnusedDeps: falsecase and one for the missing-file guard. Both sides of the ownership gate are covered inangular-bundler.spec.ts.Worth re-running before merge: the earlier end-to-end pass in
angular-examples/angular/nxwas done against the first two commits, when entry points landed ininclude. They now land infiles, so that run wants repeating on the current head.Out of scope: mapping paths that released 22.1.x already appended to users'
tsconfig.app.jsonincludeare left alone — they are indistinguishable from user-authored entries.