@lookworld4/nano-window-pubsub
v1.0.0
Published
Zero-dependency window-scoped event bus for micro-frontends — separate React, Vue, or vanilla apps communicate via one browser EventTarget using CustomEvent.
Maintainers
Readme
@lookworld4/nano-window-pubsub
Zero-runtime-dependency pub/sub on a single browser EventTarget (typically window). Micro-frontends shipped as independent React / Vue / plain bundles coordinate by publishing topics listeners subscribe to — no Redux, no shared NPM state tree, just CustomEvent.
Why use this?
- Decoupling: cart shell listens for
checkout:openedemitted from a sidebar micro-app in another repo. - Tiny surface: ~50 lines transpiled —
emit,on,once,qualify, optional singleton helpers. - Safe names: Topics are prefixed (default
nano-pubsub:) so'click'or'resize'cannot collide with real DOM events unless you deliberately drop the prefix. - Same API can target
windowin production ornew EventTarget()in Node for tests (CustomEventis available globally in Node 20+).
Installation
npm install @lookworld4/nano-window-pubsubQuick start — vanilla
import { createNanoWindowPubSub } from '@lookworld4/nano-window-pubsub';
const bus = createNanoWindowPubSub();
bus.on('catalog/product-added', (product) => {
console.count('heard from another micro-app');
});
bus.emit('catalog/product-added', { id: 'sku-42' });Micro-frontend contract
Ensure every app uses the same topicPrefix and same underlying target (often window):
createNanoWindowPubSub({ topicPrefix: 'shop:', target: window });One bundle calls emit('inventory/restocked') and another uses on('inventory/restocked', …). With topicPrefix: 'shop:', the underlying CustomEvent type is shop:inventory/restocked.
React
import { createNanoWindowPubSub } from '@lookworld4/nano-window-pubsub';
import { useEffect } from 'react';
const cartBus = createNanoWindowPubSub({ topicPrefix: 'shop:' });
export function CartBadge() {
useEffect(() => {
const off = cartBus.on<{ lineItems: number }>('cart/changed', (p) =>
console.log(p.lineItems)
);
return off;
}, []);
return null;
}Vue 3 (<script setup>)
import { createNanoWindowPubSub } from '@lookworld4/nano-window-pubsub';
import { onUnmounted } from 'vue';
const bus = createNanoWindowPubSub();
const off = bus.on('billing:paid', () => {});
onUnmounted(off);Shared singleton
For apps that refuse to carry a DI container:
import { getSharedNanoWindowPubSub, initSharedNanoWindowPubSub } from '@lookworld4/nano-window-pubsub';
// bootstrap once (tests: pass `target: new EventTarget()` before `window` exists)
initSharedNanoWindowPubSub({ topicPrefix: 'hq:' });
// anywhere
getSharedNanoWindowPubSub().emit('user/signed-out');API
| Export | Role |
|--------|------|
| createNanoWindowPubSub(options?) | New bus: optional topicPrefix, target (default globalThis.window). |
| { emit, on, once, qualify } | Standard pub/sub; qualify shows the prefixed string used internally. |
| initSharedNanoWindowPubSub | Configure and replace the singleton. |
| getSharedNanoWindowPubSub | Lazy singleton backed by defaults (needs window unless you initialized with target). |
Scripts
npm run build # dual CJS/ESM + types
npm test # Node 20+ (EventTarget + CustomEvent globals)License
MIT — see LICENSE.
