fix: Unvalidated remote types, sharedMappings skip list, and strict mf-runtime - #1122
Merged
Merged
Conversation
…mote types
The manifest is fetched at runtime and parseConfig only defaults `type`, it
never validates it. A typo such as `{"type": "esm"}` therefore reached
loadRemoteModule intact, matched neither the 'script' nor the 'module' branch,
and left `loadRemoteEntryOptions` and `key` unassigned. loadRemoteEntry(undefined)
then resolved without doing anything and lookupExposedModule(undefined, ...)
failed with a TypeError naming neither the remote nor the bad type.
Resolve the options into an internal union before use, so both variables are
provably assigned, and throw an error naming the remote and the offending value
instead. This also clears the 9 --strict errors in the file; all fixes are
internal narrowing, and the generated .d.ts is unchanged for both entry points.
Also drop a stale @ts-expect-error from test-setup.ts. The library had no spec,
so the file was never compiled and the unused directive went unnoticed.
With the unsupported-remote-type fix in place the library is strict-clean, so the setting no longer has to be relaxed. The only remaining violation was the `globalThis.ngJest` assignment in test-setup.ts, now written as an indexed access instead of a suppression so it holds under either setting. This lets apps/shell drop its own `strict: false`. That was never about the demo's code — the shell resolves @angular-architects/module-federation to libs/mf-runtime/src through the inherited tsconfig `paths`, and a strict program cannot consume loosely compiled sources. The demo is the thing people read and copy, so it should be checked the same way their app will be; it also restores Angular's null-related template checks, which derive from strictNullChecks. libs/mf and libs/mf-tools are still `strict: false` — nothing in the demo depends on them, so that is separate work.
`Array.prototype.filter` returns a new array. The result was discarded, so the call was a no-op and sharedMappings reached mappings.register() unfiltered. Assign it back. BREAKING CHANGE: a package that is both listed in `sharedMappings` and on the skip list is no longer mapped or shared. Affected names are the ones assembled into `skip`: tslib, zone.js, @angular-architects/module-federation, @angular-architects/module-federation-runtime, the three @softarc/* entries, @angular/router/upgrade, @angular/common/upgrade, and anything in the caller's own `skip` option. In practice these are not things people put in sharedMappings, which exists for monorepo libraries resolved through tsconfig `paths`, but a config relying on the old behaviour will silently stop sharing that package. The skip list is still only consulted for an explicit `sharedMappings` array. Omitting it maps every non-wildcard tsconfig path, skip-listed or not; the new spec pins that asymmetry rather than changing it.
The three demo app specs have never run: all of them failed to compile with TS2307 on '@angular/core/testing'. That subpath exists only in the package's `exports` map -- there is no testing/ directory and no top-level fallback the way there is for the package root -- and tsconfig.base.json sets `moduleResolution: node`, which predates `exports` and cannot see it. Switch the spec configs to bundler resolution, which reads `exports`. It requires `module` to be es2015 or later, so that moves to esnext; jest-preset-angular emits CommonJS either way, which is why the suites now run. Only the specs are affected. The app builds go through Angular's builder, which brings its own resolution, and no other project imports an exports-only subpath -- libs/mf-tools only ever imports the @angular/core root, which still resolves through the package's top-level `typings`.
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.
Three defects found while building the webpack Module Federation demo, plus the test-config fix needed to run any of it. Each commit stands alone and can be reviewed in order.
1.
loadRemoteModulecould call through with unassigned variableslibs/mf-runtime/src/lib/loader/dynamic-federation.tsThe
type === 'script' / 'module'chain had noelse. The manifest is a runtime-fetched JSON file andparseConfigonly defaultstype, it never validates it — so a typo such as{"mfe1": {"type": "esm", ...}}survived into the config, matched neither branch, and leftloadRemoteEntryOptionsandkeyunassigned.loadRemoteEntry(undefined)then resolved without doing anything andlookupExposedModule(undefined, ...)failed with aTypeErrornaming neither the remote nor the bad type.Options are now resolved into an internal union before use, so both variables are provably assigned, and an unsupported type throws an error naming the remote and the offending value.
This also clears the 9
--stricterrors in the file. All fixes are internal narrowing — the exported types are untouched, verified by diffing the ng-packagr.d.tsoutput before and after: byte-identical for both entry points.Side effect worth knowing: the old code mutated the caller's options object (
options.type = ...). It no longer does.2.
libs/mf-runtimecompiles withstrict: trueWith the above in place the library is strict-clean, so
apps/shellcan drop its ownstrict: false. That was never about the demo's code — the shell resolves@angular-architects/module-federationtolibs/mf-runtime/srcthrough the inherited tsconfigpaths, and a strict program cannot consume loosely compiled sources. The demo is what people read and copy, so it should be checked the way their app will be. It also restores Angular's null-related template checks, which derive fromstrictNullChecks.libs/mfandlibs/mf-toolsare stillstrict: false— nothing in the demo depends on them, so that is separate work.3.⚠️ breaking
sharedMappingsnever honoured the skip listArray.prototype.filterreturns a new array; the result was discarded, so the call was a no-op andsharedMappingsreachedmappings.register()unfiltered.Behaviour change: a package both listed in
sharedMappingsand on the skip list is no longer mapped or shared. Affected names aretslib,zone.js,@angular-architects/module-federation,@angular-architects/module-federation-runtime, the three@softarc/*entries,@angular/{router,common}/upgrade, and anything in the caller's ownskip. In practice these are not things people put insharedMappings— it exists for monorepo libraries resolved through tsconfigpaths— but a config relying on the old behaviour will silently stop sharing that package.Found while writing the tests, not changed here: the skip list is only consulted for an explicit
sharedMappingsarray. Omit it andSharedMappings.registertakes its share-all branch, mapping every non-wildcard tsconfig path — skip-listed or not. The new spec pins that asymmetry rather than changing it; worth a separate decision.4. The three demo app specs had never run
All three failed to compile with
TS2307on@angular/core/testing. That subpath exists only in the package'sexportsmap — notesting/directory, and no top-level fallback the way there is for the package root — andtsconfig.base.jsonsetsmoduleResolution: node, which predatesexportsand cannot see it. (libs/mf-toolspasses because it only imports the@angular/coreroot, which still resolves through the package's top-leveltypings.)The spec configs move to bundler resolution. Only specs are affected; app builds go through Angular's builder with its own resolution.
Verification
.d.tsdiffed at each step, unchanged throughout.nx serve shell(Cypress cannot load thetype="module"output), re-run after each item: manifest fetch, bothloadRemoteModulecall shapes,remoteEntryfrom:4201/:4202, and the sharedAuthServicesingleton reading back across build boundaries. The only console error is the documented, inertimport.metaone.nx run-many -t lint test buildis green across all 8 projects, andnx format:checkis clean. This is the first commit where the full gate passes.