> ## Documentation Index
> Fetch the complete documentation index at: https://knitkit.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Loading Federated Modules in the Browser

> Set up a knitkit host page: inject the import map, register remotes, and dynamically load exposed modules using native ES modules.

This guide walks you through setting up a knitkit host page in the browser. You will inject an import map before any module code runs, register one or more remote manifests so knitkit can negotiate shared dependencies, and then dynamically load any module a remote exposes — all without a bundler plugin.

## The bootstrap constraint

The browser applies an import map exactly once, before it evaluates the first `<script type="module">`. This means you must inject the map in a `<script type="importmap">` block that appears **before** your application's entry-point script. knitkit relies on this ordering: `registerRemotes` calls `injectImportMap` internally, which merges the negotiated entries into any existing import map on the page. You must call `registerRemotes` before any federated import runs so that the entries are present when the browser resolves them.

The recommended pattern is a tiny bootstrap script that registers remotes, then dynamically imports the rest of your app:

```html theme={null}
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <!--
    Seed the import map with the bare minimum the runtime itself needs.
    Every other entry (shared deps, remote exposes) is injected at runtime
    by registerRemotes().
  -->
  <script type="importmap">
    {
      "imports": {
        "@knitkit/runtime": "/runtime/index.js",
        "react": "https://esm.sh/react@18.3.1"
      }
    }
  </script>

  <!--
    Bootstrap: register remotes, THEN import the app.
    Nothing that touches the negotiated map may be imported before this point.
  -->
  <script type="module">
    import { registerRemotes, loadRemote } from "@knitkit/runtime";

    await registerRemotes(
      [{ name: "checkout", manifest: "https://cdn.example.com/checkout/knit.manifest.json" }],
      {
        hostShared: {
          react: {
            version: "18.3.1",
            requiredVersion: "^18.0.0",
            singleton: true,
            url: "https://esm.sh/react@18.3.1",
          },
        },
      },
    );

    // The import map is now in place — safe to import the rest of your app.
    await import("/src/main.js");
  </script>
</head>
<body>
  <div id="root"></div>
</body>
</html>
```

<Note>
  Import maps are supported natively in Chrome 89+, Firefox 108+, and Safari 16.4+. The `integrity` field inside an import map (module-level SRI) requires Chrome 127+, Firefox 138+, or Safari 18.4+. For older browsers, `es-module-shims` is a documented opt-in fallback — it is never the default. Re-verify these version numbers against MDN at ship time.
</Note>

## Registering remotes

`registerRemotes` accepts an array of remote descriptors and an options object. It fetches each manifest, negotiates shared dependency versions across all participants, injects the resulting import map into the page, and stores the registrations for later `loadRemote` calls.

```ts theme={null}
import { registerRemotes } from "@knitkit/runtime";

const result = await registerRemotes(
  [
    {
      name: "checkout",
      manifest: "https://cdn.example.com/checkout/knit.manifest.json",
    },
  ],
  {
    hostShared: {
      react: {
        version: "18.3.1",        // the exact version the host provides
        requiredVersion: "^18.0.0", // the semver range the host accepts
        singleton: true,           // refuse to load a second copy if versions conflict
        url: "https://esm.sh/react@18.3.1", // the URL the import map should point to
      },
    },
  },
);
```

Each key in `hostShared` is the bare package name that appears in the import map. The fields are:

| Field             | Type      | Description                                                                                                       |
| ----------------- | --------- | ----------------------------------------------------------------------------------------------------------------- |
| `version`         | `string`  | The exact version the host is providing.                                                                          |
| `requiredVersion` | `string`  | Semver range the host accepts from remotes.                                                                       |
| `singleton`       | `boolean` | When `true`, remotes whose range is incompatible with the winner get a scoped fallback rather than a second copy. |
| `url`             | `string`  | The URL knitkit writes into the import map's `"imports"` entry.                                                   |

You can also pass a pre-fetched manifest object instead of a URL string if you are managing fetch yourself:

```ts theme={null}
await registerRemotes([
  { name: "checkout", manifest: myParsedManifestObject },
]);
```

## Loading exposed modules

Once `registerRemotes` has returned, call `loadRemote` anywhere in your application. The specifier follows the pattern `"<remoteName>/<exposeKey>"`, where `exposeKey` is the key from the remote's `knit.config.json` `exposes` array (minus the leading `./`).

```ts theme={null}
import { loadRemote } from "@knitkit/runtime";

// Loads the module exposed as "./CartWidget" by the "checkout" remote.
const CartWidget = await loadRemote("checkout/CartWidget");
```

`loadRemote` resolves the expose URL from the registered manifest, imports it via the native dynamic `import()`, and returns the module's `default` export when one exists — or the full module namespace otherwise.

<Tip>
  Run `knitkit types sync` on your host to generate typed declarations for every specifier you load. After syncing, `loadRemote("checkout/CartWidget")` is fully typed — no manual type assertions needed.
</Tip>

## Multiple remotes

Pass more than one descriptor in the `remotes` array to register several remotes in one call. knitkit fetches all manifests in parallel and negotiates shared deps across all of them together.

```ts theme={null}
await registerRemotes(
  [
    { name: "checkout", manifest: "https://cdn.example.com/checkout/knit.manifest.json" },
    { name: "recommendations", manifest: "https://cdn.example.com/recommendations/knit.manifest.json" },
    { name: "reviews", manifest: "https://cdn.example.com/reviews/knit.manifest.json" },
  ],
  {
    hostShared: {
      react: {
        version: "18.3.1",
        requiredVersion: "^18.0.0",
        singleton: true,
        url: "https://esm.sh/react@18.3.1",
      },
      "react-dom": {
        version: "18.3.1",
        requiredVersion: "^18.0.0",
        singleton: true,
        url: "https://esm.sh/react-dom@18.3.1",
      },
    },
  },
);

// Each remote's modules are available immediately after the call resolves.
const CartWidget        = await loadRemote("checkout/CartWidget");
const RecommendedItems  = await loadRemote("recommendations/RecommendedItems");
const ReviewsSummary    = await loadRemote("reviews/ReviewsSummary");
```

## Inspecting the result

`registerRemotes` returns a `NegotiationResult` that includes the full import map and a resolution report. Two helper functions give you access to this state later without holding the return value yourself.

```ts theme={null}
import { getLastResult, getShareInfo } from "@knitkit/runtime";

// Full negotiation result (import map, winners, scopes, report).
const result = getLastResult();
console.log(result?.importMap);

// Human-readable per-package resolution report.
const report = getShareInfo();
console.log(report.packages);
// { react: { winner: { version: "18.3.1", url: "...", source: "host" }, fallbacks: [], conflict: false } }
```

You can also inspect the live import map in Chrome DevTools. Open the **Sources** panel, expand the **Import maps** node in the file tree, and you will see every entry knitkit injected — useful for verifying which URL won the version negotiation for each shared package.
