Getting Started with the Runtime

Applies to

This page walks through the minimum setup needed to load remotes in the browser with the classic runtime — install the package, add es-module-shims to your HTML, split your bootstrap, and call initFederation and loadRemoteModule.

If you are on Angular, the Angular adapter already wires this up for you — schematics generate the bootstrap split and the correct initFederation call. See Angular Adapter → Runtime. This page is for hosts built with a framework-agnostic or custom stack.

1. Install the runtime

npm i @softarc/native-federation-runtime

The runtime has one peer: the build output from @softarc/native-federation — it consumes the remoteEntry.json your build emits. For Angular projects, install @angular-architects/native-federation instead; it re-exports the same runtime.

2. Add es-module-shims to the HTML

The runtime injects the import map as a <script type="importmap-shim">. Browsers ignore that type — es-module-shims picks it up and makes importShim() available for the runtime to use when loading modules.

<!-- index.html -->
<script
  type="module"
  src="https://ga.jspm.io/npm:es-module-shims@1.10.0/dist/es-module-shims.js"
></script>

<script type="module" src="./main.js"></script>

Use type="importmap-shim" (not importmap). The runtime uses the shimmed flavour so that it keeps working after Angular, React or any other framework has already started evaluating modules. If you remove es-module-shims, loadRemoteModule will fall back to native import() — but the injected map will not be honoured, and remote imports will fail.

3. Split your bootstrap

Federation must install the import map before any module that depends on a shared external is evaluated. The idiomatic way to guarantee that is to split main.ts into a tiny entry file that calls initFederation and then dynamically imports the real bootstrap:

// src/main.ts
import { initFederation } from '@softarc/native-federation-runtime';

initFederation({
  mfe1: 'http://localhost:3001/remoteEntry.json',
})
  .catch(err => console.error(err))
  .then(() => import('./bootstrap'))
  .catch(err => console.error(err));
// src/bootstrap.ts — what main.ts used to contain
import './app/app';   // or your framework's bootstrap call

The dynamic import('./bootstrap') is the important bit: it forces your bundler to emit the real app as a separate chunk that only loads after initFederation resolves. Without the split, the app chunk would evaluate before the import map exists, and any shared dependency would be pinned to the host-bundled copy.

4. Call initFederation

initFederation accepts either an inline remotes map or a URL to a manifest JSON. Inline is fine for a fixed topology; a manifest lets you ship the same build to different environments.

// Inline — remotes known at build time
initFederation({
  mfe1: 'http://localhost:3001/remoteEntry.json',
  mfe2: 'http://localhost:3002/remoteEntry.json',
});

// Manifest — remotes resolved at runtime
initFederation('/assets/federation.manifest.json');

The manifest itself is just { "<remoteName>": "<remoteEntry.json URL>" }. See initFederation for the full signature, options, and error-handling behaviour.

5. Load a remote module

Once initFederation resolves, any registered remote's exposed modules can be loaded by name. The short form takes a remote name and an exposed key:

import { loadRemoteModule } from '@softarc/native-federation-runtime';

const { AppComponent } = await loadRemoteModule('mfe1', './Component');

The exposed key is exactly the string under exposes in the remote's federation.config.js. The longer object form adds lazy registration and a fallback value — see loadRemoteModule.

6. On the remote side

A remote also calls initFederation during its own bootstrap — but passing an empty (or self-referential) remotes map. The purpose is not to load other remotes, but to register the remote's own shared dependencies into the import map so its code resolves them to the bundled versions:

// remote src/main.ts
import { initFederation } from '@softarc/native-federation-runtime';

initFederation({})
  .catch(err => console.error(err))
  .then(() => import('./bootstrap'))
  .catch(err => console.error(err));

A remote is still a deployable web app in its own right — it just happens to also expose modules. The same bootstrap split applies.

Next