federation.config.js

Applies to

Every host and remote declares a federation.config.js that is passed to withNativeFederation. This page is the single reference for every field, every feature flag and every knob you can turn to shape the output of a Native Federation build.

The withNativeFederation Helper

The helper normalizes your config — it applies defaults, resolves the skip list, reads mapped paths from your tsconfig, and returns a NormalizedFederationConfig for the builder to consume:

import { withNativeFederation, shareAll } from '@softarc/native-federation/config';

export default withNativeFederation({
  name: 'shell',
  exposes: { /* ... */ },
  shared: { ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' }) },
});

Top-level Options

Option Type Default Description
name string '' The remote's name. Used as the key in the host manifest and in the generated remoteEntry.json.
exposes Record<string, string> {} Map of public keys (e.g. './component') to file paths. Every entry becomes a module a host can load via loadRemoteModule.
shared SharedExternalsConfig all deps Packages to share between host and remotes. If omitted, the core shares every dependency found in package.json with sensible defaults. See Sharing Dependencies.
sharedMappings string[] all tsconfig paths Paths mapped in your tsconfig that should be treated as shared (monorepo-internal libraries).
platform 'browser' | 'node' 'browser' Default platform for shared externals that don't set their own.
chunks boolean true Default code-splitting behavior for shared dependencies. Set to false to bundle each shared package as a single file.
skip string[] see skip list Packages (or mapped paths) to exclude from sharing. Merged with the built-in skip list.
externals string[] [] Extra externals to expose to your bundler via federationBuilder.externals on top of the shared ones.
shareScope string undefined Optional share scope name, propagated to every shared external.
features { mappingVersion?, ignoreUnusedDeps?, denseChunking?, integrityHashes? } see below Feature flags — details below.

name

The remote's stable identifier. It is written into remoteEntry.json, used as the manifest key hosts reference, and as the namespace for loadRemoteModule calls. It also determines the cache folder — node_modules/.cache/native-federation/<name>.

Use npm-style names: 'mfe1', '@org/mfe1', or 'team/mfe1'. Stick to one remote per team or bounded context — that's the grain the mental model is designed for.

If name is missing or empty, the build logs a warning and falls back to 'shell' for the cache folder — which will collide with other nameless projects in the same workspace.

exposes

A remote's exposes object declares which internal modules can be loaded into a host. Keys are the public specifiers that hosts use when calling loadRemoteModule; values are paths to the source file, relative to the workspace root.

exposes: {
  './Component': './projects/mfe1/src/bootstrap.ts',
  './routes':    './projects/mfe1/src/routes.ts',
}

Each entry ends up in remoteEntry.json as:

{
  "exposes": [
    { "key": "./Component", "outFileName": "Component-5EECHNWY.js" }
  ]
}

A host then imports it as mfe1/./Component via the import map. Hosts that don't expose modules simply omit the field.

shared & the share helpers

The shared object lists every npm package that should be extracted into its own ESM bundle and reused between host and remotes. Declare each entry by hand, or use the share / shareAll helpers. If the property is omitted, withNativeFederation falls back to shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto', platform: 'browser' }).

import { withNativeFederation, shareAll } from '@softarc/native-federation/config';

export default withNativeFederation({
  name: 'mfe1',
  shared: {
    ...shareAll({
      singleton: true,
      strictVersion: true,
      requiredVersion: 'auto',
    }),
  },
});

Every per-package option (singleton, strictVersion, requiredVersion, includeSecondaries, build, chunks, platform, shareScope) is documented on Sharing Dependencies.

Empty vs. populated shared

An explicit empty object (shared: {}) means share nothing: the bundler inlines every dependency into the remote, which lets esbuild aggressively tree-shake — great for a single MFE in isolation, bad when you have many remotes pulling in the same libraries independently.

shareAll with overrides

shareAll walks your package.json's dependencies and applies the same defaults to every one. Since v21.1, the second argument lets you deviate for specific packages without rewriting the whole thing:

shared: {
  ...shareAll(
    { singleton: true, strictVersion: true, requiredVersion: 'auto' },
    {
      overrides: {
        '@angular/core': {
          singleton: true,
          strictVersion: true,
          requiredVersion: 'auto',
          includeSecondaries: { keepAll: true },
        },
        'rxjs': {
          singleton: true,
          strictVersion: true,
          requiredVersion: 'auto',
          includeSecondaries: { resolveGlob: true },
        },
      },
    }
  ),
}

sharedMappings

Paths declared in your tsconfig.json (or tsconfig.base.json) are treated as shared libraries by default — perfect for monorepo-internal packages:

{
  "compilerOptions": {
    "paths": {
      "shared-lib": ["libs/shared-lib/index.ts"]
    }
  }
}

Use sharedMappings to limit the set of mapped paths considered, or skip to drop specific ones. Wildcard paths (@org/lib/*) are supported but only when features.ignoreUnusedDeps is enabled — without it, the builder can't know which expansions are actually imported and strips them to stay safe.

skip

withNativeFederation merges your skip with the built-in DEFAULT_SKIP_LIST. Each entry can be a string, a regular expression or a predicate — the full package name (including any secondary entry point) is matched against all three forms:

skip: [
  'rxjs/ajax',
  'rxjs/fetch',
  'rxjs/testing',
  'rxjs/webSocket',
  /^@types\//,
]

"Skip" doesn't remove the package. The package is still installed and still bundled into the micro-frontend — it just isn't extracted into a shared external. A skipped package continues to run locally in that remote; it just isn't de-duplicated across remotes.

The default skip list

Exported from @softarc/native-federation/config as DEFAULT_SKIP_LIST. Out of the box it covers:

chunks

By default, large shared packages are split into chunks that load on demand. Toggle this globally at the top level — or per-package via the chunks field on a shared entry:

// disable globally
chunks: false,

// disable only for one package (requires build: 'package')
shared: {
  ...shareAll(
    { singleton: true, strictVersion: true, requiredVersion: 'auto' },
    {
      overrides: {
        'large-lib': {
          singleton: true,
          strictVersion: true,
          requiredVersion: 'auto',
          chunks: false,
          build: 'package',
        },
      },
    }
  ),
},

Per-package chunks is ignored when build is 'default' — all default-mode externals share one build step, so they share one chunks setting. Switch the package to build: 'package' (or 'separate') to get an isolated bundle with its own chunk configuration.

Build modes on a shared entry

The build field on each shared entry controls how the core groups packages when handing them to the bundler. There are three modes:

Mode Behavior When to use
'default' Every default external on a given platform is bundled in a single build step. Produces one browser-shared (or node-shared) meta file in the cache and the shortest build time. The baseline. Use unless you have a specific reason not to.
'separate' Each entry gets its own build step, producing one meta file per external. Edge cases where a single package needs different bundler settings from the rest — rarely needed.
'package' The package and all its secondaries are built together as an isolated package bundle. Required whenever you want per-package chunks, or when a single package needs its own bundler pass (custom chunking, isolated polyfills, wildcard exports that must not bleed into the shared bundle).

Feature Flags

All feature flags live under features on the config. ignoreUnusedDeps and mappingVersion are on by default (opt-out); denseChunking and integrityHashes are off by default (opt-in).

Flag Default Effect
ignoreUnusedDeps true Drops shared externals that aren't actually imported by the entry points. Required for wildcard mapped paths.
denseChunking false Groups chunks by bundle name in remoteEntry.json so each shared package references its chunk bundle by name rather than listing chunks individually. Produces a smaller, more cache-friendly remoteEntry.json.
mappingVersion true Emits a real semver for every shared mapped path (monorepo-internal library) — picked up from the nearest package.json walking up from the entry file to the workspace root. Also sets requiredVersion: '~<version>' and strictVersion: true on the resulting shared entry, so version mismatches across remotes are detected at runtime. Set to false to fall back to the unversioned version: '' shape.
integrityHashes false Emit SRI hashes for every shared external, exposed module and chunk under a top-level integrity map in remoteEntry.json. The orchestrator copies these onto the import map so the browser (or es-module-shims) can verify each module before executing it. See Subresource Integrity.

When to leave ignoreUnusedDeps on (and when to override it)

The feature is on by default because most builds benefit from it: unused secondaries never enter remoteEntry.json, build output stays small, and glob-expanded libraries (RxJS, Material) don't explode. But it has a subtle interaction with cross-remote version resolution — if two remotes share different versions of Angular and one of them hasn't used @angular/core/rxjs-interop, the orchestrator can end up splitting the framework across versions. Opt out per-package with includeSecondaries: { keepAll: true } for tightly-coupled framework libraries. See Downsides of treeshaking shared packages for the full scenario.

platform & shareScope

platform at the top level sets the default target ('browser' or 'node') for any shared external that doesn't declare one itself. Mix both in a single config — the builder splits the work into four buckets (browser-shared, node-shared, browser-<pkg>, node-<pkg>) and bundles each in its own step.

shareScope, if set, is propagated to every shared external that doesn't already have its own scope. It's passed through to remoteEntry.json so the orchestrator can honour it at load time.

Common Recipes

Share everything the root package.json declares

shared: {
  ...shareAll({
    singleton: true,
    strictVersion: true,
    requiredVersion: 'auto',
  }),
},
skip: [
  'rxjs/ajax',
  'rxjs/fetch',
  'rxjs/testing',
  'rxjs/webSocket',
],
features: { ignoreUnusedDeps: true },

Hand-pick a small set of shared packages

shared: {
  ...share({
    '@angular/core': {
      singleton: true,
      strictVersion: true,
      requiredVersion: 'auto',
      includeSecondaries: { keepAll: true },
    },
    'rxjs': {
      singleton: true,
      strictVersion: true,
      requiredVersion: 'auto',
      includeSecondaries: { resolveGlob: true, skip: 'rxjs/internal/testing/*' },
    },
  }),
},
features: { ignoreUnusedDeps: true },

Avoid cross-version splits on a framework library

shared: {
  ...share({
    '@angular/core': {
      singleton: true,
      strictVersion: true,
      requiredVersion: 'auto',
      includeSecondaries: { keepAll: true },
    },
  }),
},
features: { ignoreUnusedDeps: true },

Isolate one package with its own chunking strategy

shared: {
  ...shareAll(
    { singleton: true, strictVersion: true, requiredVersion: 'auto' },
    {
      overrides: {
        'very-large-lib': {
          singleton: true,
          strictVersion: true,
          requiredVersion: 'auto',
          build: 'package',
          chunks: false,
        },
      },
    }
  ),
},

A minimal remote with no sharing (smallest possible bundle)

export default withNativeFederation({
  name: 'mfe1',
  exposes: { './Component': './projects/mfe1/src/bootstrap.ts' },
  shared: {},
  skip: [],
});

Full Example

import { withNativeFederation, shareAll } from '@softarc/native-federation/config';

export default withNativeFederation({
  name: 'shell',

  exposes: {
    './header': './shell/header',
  },

  shared: {
    ...shareAll({
      singleton: true,
      strictVersion: true,
      requiredVersion: 'auto',
      includeSecondaries: false,
    }),
  },

  skip: [
    'rxjs/ajax',
    'rxjs/fetch',
    'rxjs/testing',
    'rxjs/webSocket',
  ],

  features: {
    ignoreUnusedDeps: true,
    denseChunking: true,
  },
});

For details on each shared entry's options — singleton, strictVersion, requiredVersion, includeSecondaries, chunks, build, platform — see Sharing Dependencies.