Skip to content

ignoreUnusedDeps does not scan files referenced in exposes, causing shared dependencies to be silently dropped #1104

@Muntazir86

Description

@Muntazir86

With what library do you have an issue?

native-federation

Reproduction of the bug/regression with instructions

When ignoreUnusedDeps: true is enabled, the removeUnusedDeps scanner traces the dependency graph starting only from src/main.ts (hardcoded in builder.js). It uses @softarc/sheriff-core's getProjectData(main, ...) to discover used packages.

The problem: Files referenced in federation.config.jsexposes are not passed to the scanner as additional entry points. If an exposed module imports a shared dependency that is not also imported somewhere in the main.ts → routing chain, that dependency is silently filtered out of remoteEntry.json.

Steps to reproduce:

  1. Create a remote with ignoreUnusedDeps: true in federation.config.js
  2. Expose a component via exposes (e.g., './FaxWrapper': './src/app/wrapper/wrapper.component.ts')
  3. In that exposed component, import a shared library entry point (e.g., import { MyService } from 'my-lib/services/results')
  4. Do not import my-lib/services/results anywhere in the main app routing tree (the component is only loaded by the host via federation)
  5. Build the remote
  6. Inspect remoteEntry.jsonmy-lib/services/results is missing from the shared list, even though the exposed component uses it

The exposed component IS compiled (it appears in remoteEntry.jsonexposes), but its dependencies are not recognized as "used" by the scanner.

Root cause in source code

In builder.js — entry point is hardcoded to src/main.ts:

// @angular-architects/native-federation/src/builders/build/builder.js line 116
const entryPoint = path.join(path.dirname(options.tsConfig), 'src/main.ts');

In remove-unused-deps.js — only main.ts is scanned:

// @softarc/native-federation/src/lib/core/remove-unused-deps.js line 12-14
const fileInfos = getProjectData(main, cwd(), {
    includeExternalLibraries: true,
});

Only main.ts is used as the scanner entry point. The config.exposes values are never fed into the scanner.

Then filterShared keeps only packages found by the scanner — even explicitly added shared entries are dropped:

// remove-unused-deps.js line 22-25
function filterShared(config, usedPackageNamesWithTransient) {
    const filteredSharedNames = Object.keys(config.shared).filter(
        (shared) => usedPackageNamesWithTransient.has(shared)
    );
    // ...
}

This means even if you manually add the package to the shared config, ignoreUnusedDeps will override and remove it.

Expected behavior

The removeUnusedDeps scanner should also scan files referenced in federation.config.jsexposes, since those are compiled entry points whose shared dependencies must be available at runtime. Alternatively, explicitly added shared entries should not be filtered out by ignoreUnusedDeps.

Suggested fix

In remove-unused-deps.js, also pass exposed module paths to getProjectData (or run separate scans for each exposed file and merge the results):

// Pseudo-code
const exposedFiles = Object.values(config.exposes);
const allEntryPoints = [main, ...exposedFiles];
// Scan each and merge usedDeps

Or at minimum, preserve explicitly configured shared entries even when ignoreUnusedDeps is active.

Workaround

Add a side-effect import in bootstrap.ts (which IS reachable from main.ts) for any dependency only used by exposed modules:

// Side-effect import: ensures ignoreUnusedDeps scanner discovers this entry point
import 'my-lib/services/results';

Versions

  • @angular-architects/native-federation: 19.0.4
  • @softarc/native-federation: 19.0.7
  • Angular: 19
  • Node: 22.x

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions