npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sprlab/microfront

v0.4.2

Published

Micro frontend library using iframes with automatic resizing, messaging, and route synchronization

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/microfront

Import 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