AlabJS implements microfrontends using native browser ESM + import maps β no webpack/rspack runtime, no __webpack_require__, no extra dependencies. Each remote app exposes standard ES modules; the host app resolves them at runtime through a <script type="importmap"> injected by the framework.
This approach works natively in every modern browser and Node.js 18+. Because it's built on web standards, it is fully compatible with Vite 8 + Rolldown.
Concepts β
| Term | Meaning |
|---|---|
| Host | The app that consumes remote components |
| Remote | The app that exposes components |
federation.name | Namespace for this app's exposed modules |
federation.exposes | Components/modules this app publishes |
federation.remotes | Remote apps this app consumes |
Configuration β
Create alabjs.config.ts in your project root and export a config with defineConfig:
// alabjs.config.ts
import { defineConfig } from "alabjs";
export default defineConfig({
federation: {
// This app's namespace (used in the /_alabjs/remotes/<name>/ URL)
name: "marketing",
// Expose these components to any remote host
exposes: {
"HeroBanner": "./app/components/HeroBanner",
"NavBar": "./app/components/NavBar",
},
// Consume components from these remote apps
remotes: {
"Dashboard": "https://dashboard.internal.example.com",
},
// Extra packages to treat as shared singletons (react/react-dom automatic)
shared: ["date-fns"],
},
});Exposing components (remote app) β
Any React component can be exposed β no special wrapper needed:
// app/components/HeroBanner.tsx
export default function HeroBanner({ headline }: { headline: string }) {
return <section><h1>{headline}</h1></section>;
}After alab build, AlabJS produces:
.alabjs/dist/client/
_alabjs/
remotes/
marketing/
HeroBanner.js β standalone ESM, react externalized
NavBar.js
vendor/
react.js β shared React singleton for the host
react-dom.js
β¦
federation-manifest.jsonThe manifest is served at /_alabjs/federation-manifest.json and lists what's available:
{
"name": "marketing",
"exposes": {
"HeroBanner": "/_alabjs/remotes/marketing/HeroBanner.js",
"NavBar": "/_alabjs/remotes/marketing/NavBar.js"
}
}Consuming remote components (host app) β
Use useFederatedComponent from alabjs/client. The specifier format is "<RemoteName>/<ExposedName>":
// app/page.tsx
import { useFederatedComponent } from "alabjs/client";
import { Suspense } from "react";
// Resolved at runtime via the host's import map β zero bundler overhead.
const RemoteHero = useFederatedComponent("Dashboard/HeroBanner");
export default function HomePage() {
return (
<main>
<Suspense fallback={<div>Loadingβ¦</div>}>
<RemoteHero headline="Welcome" />
</Suspense>
</main>
);
}AlabJS automatically injects this into the page's <head> when federation.remotes is configured:
<script type="importmap">
{
"imports": {
"Dashboard/": "https://dashboard.internal.example.com/_alabjs/remotes/Dashboard/",
"react": "/_alabjs/vendor/react.js",
"react/jsx-runtime": "/_alabjs/vendor/react-jsx-runtime.js",
"react-dom": "/_alabjs/vendor/react-dom.js",
"react-dom/client": "/_alabjs/vendor/react-dom-client.js"
}
}
</script>The trailing-slash scope means any "Dashboard/*" specifier resolves to the correct remote file β no manifest fetch or runtime routing required.
React as a shared singleton β
Remote modules externalize react and react-dom. The host's import map points both to /_alabjs/vendor/react.js (a build artifact from the host), so host and remote share the exact same React instance. This is required for hooks and context to work across boundaries.
In dev mode (alab dev), React is provided by Vite's module graph and the import map only includes remote scope entries β no /_alabjs/vendor/ files are needed in development.
Monorepo setup β
In a pnpm workspace you can run multiple apps locally:
# terminal 1 β remote app
alab dev --cwd apps/dashboard --port 3001
# terminal 2 β host app (consumes the remote)
alab dev --cwd apps/marketing --port 3000Host config:
// apps/marketing/alabjs.config.ts
export default defineConfig({
federation: {
name: "marketing",
remotes: {
"Dashboard": "http://localhost:3001",
},
},
});How it works β
Browser loads host page
β
ββ <script type="importmap"> { "Dashboard/": "http://β¦" }
β
ββ <script type="module" src="/@alabjs/client">
β ββ React hydrates the page
β
ββ useFederatedComponent("Dashboard/Widget")
ββ React.lazy(() => import("Dashboard/Widget"))
ββ Browser resolves via import map
β GET http://dashboard.example.com/_alabjs/remotes/Dashboard/Widget.js
ββ ESM module with react externalized
ββ `import "react"` resolved via import map
β /_alabjs/vendor/react.js β host's copyLimitations in v0.x β
- SSR of remote components is not yet supported. Remote components render client-side only (wrapped in
<Suspense>). Full SSR federation is planned for v1.0. - Hot reloading across app boundaries in dev mode requires restarting the consuming app after changes to the remote.
- CORS: Remote apps must serve
/_alabjs/remotes/withaccess-control-allow-origin: *(AlabJS does this automatically via its static file middleware).