mport
@johnhenry/mport routes JavaScript imports across any number of CDNs.
You keep writing ordinary imports. mport decides which CDN, registry or origin serves each one. Choosing an exact version is deterministic, but which mirror delivers it can change. The result compiles down to a standard import map, so the browser never needs to know mport exists.
import { createRouter, esmSh, jsDelivr, unpkg, jsr } from "@johnhenry/mport";
const router = createRouter({ "*": [esmSh(), jsDelivr(), unpkg()], // ordered fallback "@std/*": jsr(), // JSR packages "@internal/*": "https://modules.example.com/", // your own origin});
const { importMap, lock } = await router.build(["react@^19", "lit/", "@std/path@^1"]);{ "imports": { }}mport 1.x’s one-liner still works. It is now the simplest router: a runtime race between jsDelivr, JSPM and unpkg.
import mport from "@johnhenry/mport";Provenance: previously published as the unscoped
mport, last version 1.0.0.@johnhenry/mportrestarts at0.0.0because it is a new address, not because the code is new:0.0.0is the first release of the router API (developed as mport 2.0, which never reached npm under the old name), and it keeps the 1.x API working.@johnhenry/[email protected]was published on 2026-10-03; see Getting started.
Try it live: the Import Router planet runs mport in your browser: routing scenarios (outages, races, a circuit breaker, a tampered mirror) against a simulated or the real network, a no-bundler Preact app under a strict CSP that allows its import map only by mport’s hash, and mport’s examples as a pass/fail list.
The one rule
Section titled “The one rule”Resolution is deterministic and transport is adaptive.
react@^19becomes19.2.0once. The lockfile then pins that version together with its build, and a CDN outage can’t change it.- Providers that transform packages (esm.sh, jspm) serve a different artifact from
providers that serve raw npm files (jsDelivr, unpkg). Only providers with the same
buildcount as mirrors of each other, so failover for a locked package never silently switches to a differently built file.
How it works walks through the pipeline, and what a native import map can and cannot do on its own.
What’s here
Section titled “What’s here”Guides
- Getting started: install, load from a CDN, and resolve at build time or in the browser.
- How it works: the resolution pipeline, and native import maps vs mport.
- Routes and specifiers: object and array route tables, pattern ranking, and every specifier form.
- Providers: the built-in CDNs, builds, and why raw CDNs skip CommonJS packages.
- Strategies:
fallback,race,adaptive,prefer,verified,cache, and probing. - Import maps, lockfiles and the CLI.
- In the browser:
startup()vscreateImporter(). - Debugging: traces and events.
- The v1 API and migrating from 1.x.
- Examples: twenty self-verifying Node examples and three browser pages.
- Limitations and traps: read this before relying on failover.
- Adding a new provider.
API reference: every export of every entry point, with signatures, options, defaults, return shapes, errors and trace events: specifiers, the router, providers, strategies, probing and health, trace events and errors, lockfiles and import maps, registry and semver, browser runtime helpers, the CLI and the v1 API.
Source: github.com/johnhenry/mport. MIT licensed.
Family
Section titled “Family”mport is one of the @johnhenry family’s browser-side libraries. None depends on
another.
- html-modules: declarative HTML modules.
<html-import src="./ui.html" as="ui">turns an HTML file’s<html-export>s into custom elements. It composes with mport through the import map: html-modules resolves a baresrcwithimport.meta.resolve, which applies the page’s own<script type="importmap">, and does no package or CDN routing of its own (it was split out of the project whose routing half became this router). mport’srouter.build()andstartup()produce that import map, so a prefix mapping such as"ui-kit/"fromrouter.build(["ui-kit@1/"])makes<html-import src="ui-kit/card.html">load from whichever CDN mport chose. Route such a package to a raw-file provider (jsDelivr(),unpkg()), since the HTML must be served as published. See html-modules’ Resolution, caching and errors. - window-algebra: a functional window manager for the browser
(pure state updates, a layout algebra, CSS as the layout solver). No dependency in
either direction; they meet at the import map. A no-build page using window-algebra needs
an import-map entry per entry point (and for anything loaded alongside, such as React for
its
/reactbinding), and mport can generate that map with fallback across mirrors; see window-algebra’s Getting started. - safe-fragment: Web Components that render untrusted HTML through
versioned security profiles. Its DOMPurify fallback is loaded with a dynamic
import("dompurify"), so a no-bundler page needs adompurifyentry in the import map. On raw-file providers (jsDelivr(),unpkg(),local())build(["@johnhenry/safe-fragment@0"], { dependencies: true })(CLI:--dependencies) adds it from the package’s owndependencies; listingdompurify@<pinned version>explicitly works too. esm.sh rewrites the import itself, so nothing extra is needed there. No dependency in either direction.