npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@softarc/native-federation

v4.3.2

Published

Native Federation is a "browser-native" implementation of the successful mental model behind webpack Module Federation. It can be **used with any framework and build tool** for implementing **Micro Frontends** and plugin-based architectures.

Readme

@softarc/native-federation

Native Federation is a "browser-native" implementation of the successful mental model behind webpack Module Federation. It can be used with any framework and build tool for implementing Micro Frontends and plugin-based architectures.

[!WARNING] This is our v4 version. For the v3 version, check out the module-federation-plugin repository.

Features

  • ✅ Mental Model of Module Federation
  • ✅ Future Proof: Independent of build tools like webpack and frameworks
  • ✅ Embraces Import Maps -- an emerging browser technology -- and EcmaScript modules
  • ✅ Easy to configure
  • ✅ Blazing Fast: The reference implementation not only uses the fast esbuild; it also caches already built shared dependencies (like Angular itself). However, as mentioned above, feel free to use it with any other build tool.

Stack

This library allows to augment your build process, to configure hosts (Micro Frontend shells) and remotes (Micro Frontends), and to load remotes at runtime.

While this core library can be used with any framework and build tool, there is a higher level API on top of it. It hooks into the Angular CLI and provides a builder and schematics:

Stack

Please find the Angular-based version here.

Please find the vite plugin here.

Also, other higher level abstractions on top of this core library are possible.

About the Mental Model

The underlying mental model allows for runtime integration: Loading a part of a separately built and deployed application into your host. This is needed for Micro Frontend architectures but also for plugin-based solutions.

For this, the mental model introduces several concepts:

  • Remote: The remote is a separately built and deployed application. It can expose EcmaScript modules that can be loaded into other applications.
  • Host: The host loads one or several remotes on demand. For your framework's perspective, this looks like traditional lazy loading. The big difference is that the host doesn't know the remotes at compilation time.
  • Shared Dependencies: If several remotes and the host use the same library, you might not want to download it several times. Instead, you might want to just download it once and share it at runtime. For this use case, the mental model allows for defining such shared dependencies.
  • Version Mismatch: If two or more applications use a different version of the same shared library, we need to prevent a version mismatch. To deal with it, the mental model defines several strategies, like falling back to another version that fits the application, using a different compatible one (according to semantic versioning) or throwing an error.

Example

Credits

Big thanks to:

  • Zack Jackson for originally coming up with the great idea of Module Federation and its successful mental model
  • Florian Rappl for a good discussion about these topics during a speakers dinner in Nuremberg
  • Michael Egger-Zikes for contributing to our Module Federation efforts and bringing in valuable feedback
  • The Angular CLI-Team, esp. Alan Agius and Charles Lyding, for working on the experimental esbuild builder for Angular

Using this Library

Installing the Library

npm i @softarc/native-federation

As Native Federation is tooling agnostic, we need an adapter to make it work with specific build tools. The package @softarc/native-federation-esbuild contains a simple adapter that uses esbuild.

npm i @softarc/native-federation-esbuild

You can also provide your own adapter by providing a function aligning with the NFBuildAdapter [src] type.

Augment your Build Process

[!WARNING] The esbuild adapter is currently under construction, check the progress here: https://github.com/native-federation/esbuild-adapter

Just call three helper methods provided by our federationBuilder in your build process to adjust it for Native Federation.

import * as esbuild from 'esbuild';
import * as path from 'path';
import * as fs from 'fs';
import { esBuildAdapter } from '@softarc/native-federation-esbuild';
import { federationBuilder } from '@softarc/native-federation';


const projectName = 'shell';
const tsConfig = 'tsconfig.json';
const outputPath = `dist/${projectName}`;

/*
 *  Step 1: Initialize Native Federation
*/
await federationBuilder.init({
    options: {
        workspaceRoot: path.join(__dirname, '..'),
        outputPath,
        tsConfig,
        federationConfig: `${projectName}/federation.config.js`,
        verbose: false,
    },

    /*
      * As this core lib is tooling-agnostic, you
      * need a simple adapter for your bundler.
      * It's just a matter of one function.
    */
    adapter: esBuildAdapter
});

/*
  * Step 2: Trigger your build process
  *
  * You can use any tool for this. Here, we go
  * with a very simple esbuild-based build.
  *
  * Just respect the externals in
  * `federationBuilder.externals`.
*/

[...]

await esbuild.build({
    [...]
    external: federationBuilder.externals,
    [...]
});

[...]

/*
  * Step 3: Let the build method do the additional tasks
  *   for supporting Native Federation
*/

await federationBuilder.build();

The method federationBuilder.build bundles the shared and exposed parts of your app.

Configuring Hosts

The withNativeFederation function sets up a configuration for your applications. This is an example configuration for a host.

The fromPackageJson helper (recommended)

fromPackageJson is the recommended way to share your dependencies. It shares all dependencies found in your package.json and exposes a small fluent builder so you can fine-tune the result. The base options you pass are applied to every shared dependency; you then chain .skip(...), .override(...) and .patch(...) as needed and finish with .get():

// shell/federation.config.js

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

export default withNativeFederation({
  name: 'host',

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

[!TIP] If you omit the shared property entirely, Native Federation applies exactly this fromPackageJson configuration for you (with singleton, strictVersion and requiredVersion: 'auto'). So the snippet above is also a good description of the default behavior.

The builder returned by fromPackageJson offers three chainable methods, each of which returns the builder so you can combine them:

  • .skip(externals) — exclude packages from sharing (added on top of the default skip list).
  • .override(externals) — replace the configuration for specific packages entirely. Use this when a package needs a completely different set of options.
  • .patch(externals, cfg) — merge a partial configuration onto specific shared externals, keeping the base options for everything you don't touch.
// shell/federation.config.js

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

export default withNativeFederation({
  name: 'host',

  shared: fromPackageJson({
    singleton: true,
    strictVersion: true,
    requiredVersion: 'auto',
  })
    // Don't share these dependencies at all
    .skip(['my-lib', 'some-dev-only-lib'])
    // Give a package a completely different configuration
    .override({
      'package-a/themes/xyz': {
        singleton: true,
        strictVersion: true,
        requiredVersion: 'auto',
        includeSecondaries: { skip: '@package-a/themes/xyz/*' },
        build: 'package',
      },
    })
    // Tweak a few options on specific packages while keeping the base config
    .patch(['package-b'], {
      singleton: false,
      includeSecondaries: { skip: 'package-b/icons/*' },
      build: 'package',
    })
    .get(),
});

By default the closest package.json (relative to your federation.config.js) is used. You can point at a different one by passing its path as the second argument: fromPackageJson(baseCfg, projectPath).

Alternative: the shareAll helper

shareAll is the older, object-spread style alternative to fromPackageJson. It also shares all dependencies defined in your package.json, but instead of a fluent builder it returns a plain object that you spread into shared:

// shell/federation.config.js

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

export default withNativeFederation({
  name: 'host',

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

The options passed to shareAll are applied to all dependencies found in your package.json. This might come in handy in a monorepo scenario and when doing some experiments / troubleshooting.

You can also add overrides to shareAll for specific packages, via the second argument:

// shell/federation.config.js

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

export default withNativeFederation({
  name: 'host',

  shared: {
    ...shareAll(
      {
        singleton: true,
        strictVersion: true,
        requiredVersion: 'auto',
      },
      {
        overrides: {
          'package-a/themes/xyz': {
            singleton: true,
            strictVersion: true,
            requiredVersion: 'auto',
            includeSecondaries: { skip: '@package-a/themes/xyz/*' },
            build: 'package',
          },
          'package-b': {
            singleton: false,
            strictVersion: true,
            requiredVersion: 'auto',
            includeSecondaries: { skip: 'package-b/icons/*' },
            build: 'package',
          },
        },
      }
    ),
  },
});

Share Helper

The helper function share adds some additional options for the shared dependencies:

shared: share({
    "package-a": {
        singleton: true,
        strictVersion: true,
        requiredVersion: 'auto',
        includeSecondaries: true
    },
    [...]
})

The added options are requiredVersion: 'auto' and includeSecondaries.

requiredVersion: 'auto'

If you set requiredVersion to 'auto', the helper takes the version defined in your package.json.

This helps to solve issues with not (fully) met peer dependencies and secondary entry points.

By default, it takes the package.json that is closest to the caller (normally the federation.config.js). However, you can pass the path to another package.json using the second optional parameter. Also, you need to define the shared library within the dependencies in your package.json.

Instead of setting requiredVersion to auto time and again, you can also skip this option and call setInferVersion(true) before:

setInferVersion(true);

includeSecondaries

If set to true, all secondary entry points are added too. In the case of @angular/common this is also @angular/common/http, @angular/common/http/testing, @angular/common/testing, @angular/common/http/upgrade, and @angular/common/locales. This exhaustive list shows that using this option for @angular/common is not the best idea because normally, you don't need most of them.

includeSecondaries is true by default.

However, this option can come in handy for quick experiments or if you want to quickly share a package like @angular/material that comes with a myriad of secondary entry points.

Even if you share too much, Native Federation will only load the needed ones at runtime. However, please keep in mind that shared packages can not be tree-shaken.

To skip some secondary entry points, you can assign a configuration option instead of true:

shared: share({
    "@angular/common": {
        singleton: true,
        strictVersion: true,
        requiredVersion: 'auto',
        includeSecondaries: {
            skip: ['@angular/common/http/testing']
        }
    },
    [...]
})

Wildcard (*) export entry points are not expanded by default. To resolve them into concrete secondary entry points, enable the resolveGlob property:

shared: share({
      "package-a": {
        singleton: true,
        strictVersion: true,
        requiredVersion: "auto",
        includeSecondaries: {resolveGlob: true}
      },
    [...]
})

Resolving globs can create a bundle for every valid exported file it finds, so it is recommended to keep the ignoreUnusedDeps feature enabled (it is on by default) to drop the ones you don't use. If you want to specifically skip certain parts of the glob export, you can also use the wildcard in the skip section:

shared: share({
      "package-a/themes/xyz": {
        singleton: true,
        strictVersion: true,
        requiredVersion: "auto",
        includeSecondaries: {skip: "package-a/themes/xyz/*", resolveGlob: true}
      },
    [...]
})

Finally, it's also possible to break out of the ignoreUnusedDeps feature for specific externals if desired, for example when sharing a whole suite of interconnected external dependencies like @angular/core. This can be handy when you want to avoid the chance of cross-version secondary entrypoints being used by the different micro frontends. E.g. mfe1 uses @angular/core v20.1.0 and mfe2 uses @angular/core/rxjs-interop v20.0.8, then you might want consistent use of v20.1.0 so rxjs-interop should be exported by mfe1. The keepAll prop allows you to enforce this:

shared: share({
      "@angular/core": {
        singleton: true,
        strictVersion: true,
        requiredVersion: "auto",
        includeSecondaries: {keepAll: true}
      },
    [...]
})

The API for configuring and using Native Federation is very similar to the one provided by our Module Federation plugin @angular-architects/module-federation. Hence, most of the articles on it are also valid for Native Federation.

Sharing

The shareAll-helper used here shares all dependencies found in your package.json. Hence, they only need to be loaded once (instead of once per remote and host). If you don't want to share all of them, you can opt-out of sharing by using the skip option:

export default withNativeFederation({
  [...]

  // Don't share my-lib
  skip: [
    'my-lib'
  ]

  [...]
})

Sharing Mapped Paths (Monorepo-internal Libraries)

Paths mapped in your tsconfig.json are shared by default too. While they are part of your (mono) repository, they are treated like libraries:

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

If you don't want to share (all of) them, put their names into the skip array (see above).

Determining which internal libraries are shared

In Nx/monorepo setups, Native Federation shares all libraries from your tsconfig path mappings by default.

If you only want to share selected mapped paths, you can use sharedMappings in your federation.config.js:

module.exports = withNativeFederation({
  shared: {
    ...shareAll({
      singleton: true,
      strictVersion: true,
      requiredVersion: 'auto',
    }),
  },
  sharedMappings: ['@my-org/auth-lib', '@my-org/ui/*'],
});

Notes:

  • sharedMappings is optional. If you omit it, all mapped paths are shared.
  • You can use wildcard suffixes (for example, @my-org/ui/*) to include multiple mapped paths.
  • skip still applies and can be used to exclude mapped paths even if they were selected via sharedMappings.
  • Mapped paths are read from the workspace root tsconfig file: tsconfig.base.json if present, otherwise tsconfig.json.
  • The workspace root is detected by searching upward from the current working directory until a package.json is found.

The mappingVersion feature flag controls whether mapped paths get a version. It is enabled by default: Native Federation reads the version from the mapped library's nearest package.json and shares it with strict versioning, just like a published library.

If your mapped paths point at plain internal source that isn't distributed as a versioned, buildable library, you can disable it. The mapped paths are then shared without a version constraint.

module.exports = withNativeFederation({
  shared: {
    ...shareAll({
      singleton: true,
      strictVersion: true,
      requiredVersion: 'auto',
    }),
  },
  sharedMappings: ['@my-org/auth-lib', '@my-org/ui/*'],
  features: {
    mappingVersion: false,
  },
});

Code-Splitting for Shared Dependencies

By default, Native Federation enables code-splitting (chunking) for shared dependencies. This means large libraries can be split into smaller chunks which reduces the overall size, improving initial load times.

You can configure code-splitting at two levels:

Global Setting

Use the chunks option in your federation.config.js to control the default behavior for all shared dependencies:

module.exports = withNativeFederation({
  // Disable code-splitting globally
  chunks: false,

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

When chunks is set to false at the config level, all shared dependencies, shared mappings and exposed modules will be bundled as single files without code-splitting.

Per-Package Setting

You can also override the code-splitting behavior for individual packages in the shared configuration:

module.exports = withNativeFederation({
  shared: {
    ...shareAll(
      {
        singleton: true,
        strictVersion: true,
        requiredVersion: 'auto',
      },
      {
        overrides: {
          // Disable code-splitting for a specific package
          'large-lib': {
            singleton: true,
            strictVersion: true,
            requiredVersion: 'auto',
            chunks: false,
            build: 'package', // necessary for isolated bundles
          },
        },
      }
    ),
  },
});

Note: When setting chunks on individual packages, consider also setting build: 'package' to avoid your explicit chunk settings being ignored since all 'default' bundles are bundled in a single build step.

Dense Chunking

The denseChunking feature flag optimizes the remoteEntry.json file structure for better performance:

module.exports = withNativeFederation({
  shared: {
    ...shareAll({
      singleton: true,
      strictVersion: true,
      requiredVersion: 'auto',
    }),
  },
  features: {
    denseChunking: true,
  },
});

When enabled, instead of listing each chunk as a separate shared dependency, chunks are grouped by bundle name in a dedicated chunks object. Each shared dependency gets a bundle property linking it to its chunk bundle. This results in a smaller remoteEntry.json and allows chunks to be skipped if the dependency is not used in the final import map.

Dense Externals

The denseExternals feature flag reshapes the shared array in remoteEntry.json so that all entrypoints of a shared external (its primary import plus every secondary and shared mapping) are grouped under a single object:

module.exports = withNativeFederation({
  shared: {
    ...shareAll({
      singleton: true,
      strictVersion: true,
      requiredVersion: 'auto',
    }),
  },
  features: {
    denseExternals: true,
  },
});

When enabled, instead of one flat entry per entrypoint, each package becomes one object whose entries map keys the full import name to its output file (e.g. { "@angular/common": "...", "@angular/common/http": "..." }). Entrypoints whose sharing metadata (singleton, strictVersion, requiredVersion, version, shareScope) diverges are split into separate groups. Bundler chunks stay flat, and importmap.json is unaffected. The flag is opt-in and fully backward compatible: the runtime auto-detects each entry by shape, so old and new remoteEntry.json both load.

Configuring Remotes

When configuring a remote, you can expose files that can be loaded into the shell at runtime:

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

export default withNativeFederation({
  name: 'mfe1',

  exposes: {
    './component': './mfe1/component',
  },

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

Loading Remotes at Runtime

This core library covers the build side of Native Federation. The generated remoteEntry.json files are consumed at runtime by a separate, framework-agnostic package: @softarc/native-federation-orchestrator. It loads micro frontends built with Native Federation into any web page — SPAs as well as server-rendered hosts (PHP, Java, Rails, …) that reload on navigation.

Quickstart (drop-in script)

For a zero-build integration, declare your remotes in a manifest and include the quickstart bundle:

<!-- Optional: enable shim mode for older browsers -->
<script type="esms-options">
  { "shimMode": true }
</script>

<!-- Define your micro frontends -->
<script type="application/json" id="mfe-manifest">
  {
    "team/mfe1": "http://localhost:3000/remoteEntry.json",
    "team/mfe2": "http://localhost:4000/remoteEntry.json"
  }
</script>

<!-- Load modules once the orchestrator is ready -->
<script>
  window.addEventListener(
    'mfe-loader-available',
    e => {
      e.detail.loadRemoteModule('team/mfe1', './Button');
      e.detail.loadRemoteModule('team/mfe2', './Header');
    },
    { once: true }
  );
</script>

<!-- Include the orchestrator -->
<script src="https://unpkg.com/@softarc/[email protected]/quickstart.mjs"></script>

The mfe-loader-available event signals that the orchestrator has fetched the remote metadata, resolved dependencies and set up the import map, so loadRemoteModule is ready to use.

Programmatic API

For full control, install the package and initialize federation yourself:

npm i @softarc/native-federation-orchestrator
import { initFederation } from '@softarc/native-federation-orchestrator';
import { consoleLogger, localStorageEntry } from '@softarc/native-federation-orchestrator/options';

const manifest = {
  'team/mfe1': 'http://localhost:3000/remoteEntry.json',
  'team/mfe2': 'http://localhost:4000/remoteEntry.json',
};

const { loadRemoteModule, load } = await initFederation(manifest, {
  logLevel: 'error',
  logger: consoleLogger,
  storage: localStorageEntry,
});

const ButtonComponent = await load('team/mfe1', './Button');
const HeaderComponent = await loadRemoteModule('team/mfe2', './Header');

The manifest maps logical remote names to their remoteEntry.json URLs (the files generated by the build steps above). Entries can also be objects carrying an integrity hash for Subresource Integrity. Manifests let you adjust your application to different environments without recompilation.

Import maps & polyfills

The orchestrator uses native browser import maps by default, so no polyfill is required for modern browsers. To support older browsers that lack import-map support, add the es-module-shims polyfill and opt into shim mode:

import 'es-module-shims';
import { initFederation } from '@softarc/native-federation-orchestrator';
import { useShimImportMap } from '@softarc/native-federation-orchestrator/options';

const { loadRemoteModule } = await initFederation(manifest, {
  ...useShimImportMap({ shimMode: true }),
});

For server-side rendering, the event registry, version-conflict resolution and security / Trusted Types, see the orchestrator documentation.

React and Other CommonJS Libs

Native Federation uses Web Standards like EcmaScript Modules. Most libs and frameworks support them meanwhile. Unfortunately, React still uses CommonJS (and UMD). We do our best to convert these libs to EcmaScript Modules. In the case of React there are some challenges due to the dynamic way the React bundles use the exports object.

As the community is moving to EcmaScript Modules, we expect that these issues will vanish over time. In between, we provide some solutions for dealing with CommonJS-based libraries using exports in a dynamic way.

One of them is fileReplacements:

import { reactReplacements } from '@softarc/native-federation-esbuild/src/lib/react-replacements';
import { createEsBuildAdapter } from '@softarc/native-federation-esbuild';

[...]

createEsBuildAdapter({
  plugins: [],
  fileReplacements: reactReplacements.prod
})

Please note that the adapter comes with fileReplacements settings for React for both, dev mode and prod mode. For similar libraries you can add your own replacements. Also, using the compensateExports property, you can activate some additional logic for such libraries to make sure the exports are not lost

createEsBuildAdapter({
  plugins: [],
  fileReplacements: reactReplacements.prod,
  compensateExports: [new RegExp('/my-lib/')],
});

The default value for compensateExports is [new RegExp('/react/')].

More: Blog Articles

Find out more about our work including Micro Frontends and Module Federation but also about alternatives to these approaches in our blog.

More: Angular Architecture Workshop (100% online, interactive)

In our Angular Architecture Workshop, we cover all these topics and far more. We provide different options and alternatives and show up their consequences.

Details: Angular Architecture Workshop