@sprlab/microfront
v0.4.2
Published
Micro frontend library using iframes with automatic resizing, messaging, and route synchronization
Maintainers
Readme
@sprlab/microfront
A framework-agnostic library for building micro frontend architectures using iframes. It handles iframe resizing, bidirectional messaging, and route synchronization between a shell (host) application and remote (child) applications.
Supports Vue 3, Vue 2 / Nuxt 2, React, and Angular remotes.
Features
- Automatic iframe resizing based on content height (ResizeObserver + penpal)
- Full-height mode: iframe fills container, expands for tall content
- Bidirectional messaging between shell and remotes via penpal
- Route synchronization between shell and remote routers
- Connection status tracking (loading, connected, error, no-plugin detection)
- Framework-agnostic core with Vue, React, and Angular adapters
- Configurable connection timeout and allowed origins
Installation
yarn add @sprlab/microfrontImport paths
| Path | Description |
|------|-------------|
| @sprlab/microfront/core | Framework-agnostic core (types, initRemote, utilities) |
| @sprlab/microfront/vue/shell | Vue 3 shell (RemoteApp component, useRemote composable) |
| @sprlab/microfront/vue/remote | Vue 3 remote (sprRemote plugin, send, onMessage) |
| @sprlab/microfront/react/remote | React remote (initReactRemote, createReactRouterAdapter) |
| @sprlab/microfront/angular/remote | Angular remote (initAngularRemote, createAngularRouterAdapter) |
| @sprlab/microfront/mpa/remote | MPA standalone remote (initMpaRemote, penpal bundled) |
Legacy aliases (backward compatible):
| @sprlab/microfront/shell | Same as ./vue/shell |
| @sprlab/microfront/remote | Same as ./vue/remote |
Usage
Shell (Vue 3 host application)
Basic setup
<template>
<RemoteApp
src="http://localhost:4001"
title="Remote 1"
/>
</template>
<script setup lang="ts">
import { RemoteApp } from '@sprlab/microfront/vue/shell'
</script>Messaging with useRemote
<template>
<div>
<article v-if="isLoading" aria-busy="true">Connecting...</article>
<article v-else-if="isError">Connection error</article>
<template v-if="isConnected">
<button @click="sendToRemote">Send</button>
</template>
<RemoteApp src="http://localhost:4001" title="Remote 1" />
</div>
</template>
<script setup lang="ts">
import { RemoteApp, useRemote } from '@sprlab/microfront/vue/shell'
const { sendMessage, onMessage, isLoading, isConnected, isError, isNoPlugin } = useRemote()
function sendToRemote() {
sendMessage({ greeting: 'hello from shell' })
}
onMessage((payload, metadata) => {
console.log(`Message from: ${metadata.appName}`, payload)
})
</script>Route synchronization
<template>
<RemoteApp
src="http://localhost:4002"
title="Remote 2"
basePath="/remote2"
/>
</template>The shell router needs a catch-all route:
{ path: '/remote2/:path(.*)*', component: Remote2View }Full-height mode
<RemoteApp
src="http://localhost:4004"
title="FullHeight Remote"
basePath="/fullheight"
fullHeight
/>When fullHeight is enabled, the iframe takes at least 100% of its container height. If the remote content is taller, the iframe expands. On navigation, it resets and re-measures.
Remote — Vue 3
import { createApp } from 'vue'
import { sprRemote } from '@sprlab/microfront/vue/remote'
import App from './App.vue'
import router from './router'
createApp(App)
.use(sprRemote, { appName: 'my-app', router })
.use(router)
.mount('#app')Sending and receiving messages:
import { send, onMessage } from '@sprlab/microfront/vue/remote'
onMessage((payload) => console.log('From shell:', payload))
send({ greeting: 'hello from remote' })The plugin detects if the app is inside an iframe. When standalone, it does nothing.
Remote — React
import { initReactRemote } from '@sprlab/microfront/react/remote'
import { createBrowserRouter } from 'react-router-dom'
const router = createBrowserRouter([...])
// With router (route sync + messaging)
const connection = initReactRemote({ appName: 'my-react-app', router })
// Without router (messaging only)
const connection = initReactRemote({ appName: 'my-react-app' })
// Send/receive messages
connection?.send({ greeting: 'hello' })
connection?.onMessage((payload) => console.log(payload))Returns null if not inside an iframe.
Remote — Angular
// app.config.ts
import { ApplicationConfig, APP_INITIALIZER, inject } from '@angular/core';
import { provideRouter, Router } from '@angular/router';
import { initAngularRemote } from '@sprlab/microfront/angular/remote';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
{
provide: APP_INITIALIZER,
multi: true,
useFactory: () => {
const router = inject(Router);
return () => initAngularRemote({ appName: 'my-angular-app', router });
},
},
],
};Without router (messaging only):
import { initAngularRemote } from '@sprlab/microfront/angular/remote';
const connection = initAngularRemote({ appName: 'my-angular-app' });
connection?.send({ greeting: 'hello' });
connection?.onMessage((payload) => console.log(payload));Remote — MPA (Multi-Page Apps / SSR)
For server-rendered apps (PHP, ASP, static HTML, etc.) that do full page reloads. The MPA bundle includes penpal — no import map or external dependencies needed:
<script type="module">
import { initMpaRemote } from '/path/to/mpa-remote.js'
initMpaRemote({ appName: 'my-mpa-app' })
</script>Or via npm with a bundler:
import { initMpaRemote } from '@sprlab/microfront/mpa/remote'
initMpaRemote({ appName: 'my-mpa-app' })Features:
- Messaging works on each page while connected
- Height reporting works (ResizeObserver)
- Route sync works — shell URL updates after each page load
- Back and forward navigation works within the MPA remote
- Back to other shell pages works correctly
- Known limitation: forward navigation after leaving the MPA remote (e.g., back to Home then forward) only reaches the first MPA page. This is inherent to iframes in SPAs — the iframe is destroyed when the shell component unmounts and recreated without its history when remounted.
The shell automatically reconnects penpal after each iframe reload.
Remote — Vue 2 / Nuxt 2
// plugins/microfront.client.js
import { sprRemoteLegacy } from '@sprlab/microfront/dist/remote.js'
export default ({ app }) => {
sprRemoteLegacy.init({
appName: 'my-nuxt2-app',
router: app.router,
})
}// nuxt.config.js
plugins: [
{ src: '~/plugins/microfront.client.js', mode: 'client' }
],
build: {
transpile: ['@sprlab/microfront', 'penpal']
}API Reference
RemoteApp component (@sprlab/microfront/vue/shell)
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| src | string | required | URL of the remote application |
| title | string | required | Iframe title for accessibility |
| basePath | string | '' | Shell route prefix for route sync |
| timeout | number | 10000 | Connection timeout in ms |
| allowedOrigins | string[] | ['*'] | Allowed origins for postMessage |
| fullHeight | boolean | false | Iframe fills container, expands for tall content |
useRemote() composable (@sprlab/microfront/vue/shell)
| Property | Type | Description |
|----------|------|-------------|
| sendMessage | (payload) => Promise<void> | Send message to remote |
| onMessage | (handler) => void | Listen for messages from remote |
| onRouteChange | (handler) => void | Listen for route changes from remote |
| isLoading | ComputedRef<boolean> | Connecting |
| isConnected | ComputedRef<boolean> | Connected |
| isError | ComputedRef<boolean> | Server unreachable |
| isNoPlugin | ComputedRef<boolean> | Server responds but plugin missing |
initReactRemote (@sprlab/microfront/react/remote)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| appName | string | 'unknown' | Identifier for messages |
| router | Router | undefined | React Router instance (createBrowserRouter) |
| allowedOrigins | string[] | ['*'] | Allowed origins for postMessage |
Returns RemoteConnection | null (null if not in iframe).
initAngularRemote (@sprlab/microfront/angular/remote)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| appName | string | 'unknown' | Identifier for messages |
| router | Router | undefined | Angular Router instance (@angular/router) |
| allowedOrigins | string[] | ['*'] | Allowed origins for postMessage |
Returns RemoteConnection | null (null if not in iframe).
Architecture
┌─────────────────────────────────────────┐
│ Shell (Vue 3) localhost:4000 │
│ │
│ ┌─────────────────────────────────┐ │
│ │ RemoteApp (iframe) │ │
│ │ │ │
│ │ ┌───────────────────────────┐ │ │
│ │ │ Remote (any framework) │ │ │
│ │ │ Vue 3 / Nuxt 2 / React │ │ │
│ │ │ / Angular │ │ │
│ │ └───────────────────────────┘ │ │
│ │ │ │
│ │ penpal ←→ messaging │ │
│ │ ResizeObserver ←→ resize │ │
│ └─────────────────────────────────┘ │
│ │
└─────────────────────────────────────────┘Migration from 0.1.x
New import paths (recommended)
- import { RemoteApp, useRemote } from '@sprlab/microfront/shell'
+ import { RemoteApp, useRemote } from '@sprlab/microfront/vue/shell'
- import { sprRemote, send, onMessage } from '@sprlab/microfront/remote'
+ import { sprRemote, send, onMessage } from '@sprlab/microfront/vue/remote'The old paths (/shell, /remote) still work as aliases.
Removed dependency
@open-iframe-resizer/core has been removed. Height management is now handled internally via ResizeObserver + penpal communication. No action needed — this is transparent to consumers.
Dependencies
- penpal — Promise-based iframe messaging
License
MIT
