包详细信息

@amadeus-it-group/microfrontends

AmadeusITGroup864MIT0.0.7

Amadeus Micro Frontend Toolkit

micro frontends, mfe, postmessage, message channel

自述文件

Amadeus Toolkit for Micro Frontends

Messaging

The Amadeus Toolkit for Micro Frontends provides a messaging system that allows micro frontends to communicate with each other. The messaging system is based on the Channel Messaging API and works across iFrames. It can also be used in the same document for talk to MFE packaged as a Web Component for example.

Features

schema.svg

  • typed and versioned message exchange between MessagePeers
  • broadcasting messages across all connected micro frontends (ex. MF1 to everybody)
  • sending messages between two specific micro frontends (ex. MF4 to MF3)
  • lifecycle messages (ex. MF5 disconnected, MF3 connected)
  • message validation before sending and upon reception

Common use-cases

Creating a Message Peer

You can create several message peers and connect them to each other in any way avoiding loops. You need to provide some options when creating a peer. Only id is technically required.

import { Message, MessagePeer } from '@amadeus-it-group/microfrontends';

// Message types are optional
const peer = new MessagePeer({
  // unique identifier for this peer in the network
  id: 'one',

  // list of messages this peer accepts
  knownMessages: [
    { type: 'hello', version: '1.0' },
    { type: 'hello', version: '2.0' },
  ],
});

Connecting to another peer

A peer can either wait for incoming connections from another peer via .listen() or initiate a connection itself via .connect().

import { MessagePeer } from '@amadeus-it-group/microfrontends';

// Create two peers
// First waits for incoming connections from 'two'
const one = new MessagePeer({ id: 'one' });
one.listen('two');

// Second connects to 'one'
const two = new MessagePeer({ id: 'two' });
two.connect('one');

// if connection crosses iFrames, you might need to provide
// expected window and origin for both `connect` and `listen` methods
one.listen('two', {
  window: iframe.contentWindow,
  origin: 'https://example.com',
});

// Disconnecting
one.disconnect('two'); // disconnect from a specific peer
one.disconnect(); // disconnect from all peers

Declaring Message types

This is optional, but allows for type checking during development when sending and receiving messages.

import { Message } from '@amadeus-it-group/microfrontends';

interface HelloMessage_1_0 extends Message {
  type: 'hello';
  version: '1.0';
  greeting: string;
}

interface HelloMessage_2_0 extends Message {
  type: 'hello';
  version: '2.0';
  greetings: string[];
}

export type MyMessage = HelloMessage_1_0 | HelloMessage_2_0;

Sending and receiving messages

import { MessagePeer } from '@amadeus-it-group/microfrontends';

// Receiving messages
const one = new MessagePeer<MyMessage>({ id: 'one' });

// An observable-like interface consuming messages
one.messages.subscribe(({ payload }: MyMessage) => {
  if (payload.type === 'hello') {
    switch (payload.version) {
      case '1.0':
        console.log(payload.greeting); // string
        break;
      case '2.0':
        console.log(payload.greetings); // string[]
        break;
    }
  }
});

// Broadcast a message. Message will be type checked.
two.send({
  type: 'hello',
  version: '1.0',
  greeting: 'Hello, world!',
});

// Send a message to a specific peer. Other peers will not receive it.
two.send(
  {
    type: 'hello',
    version: '2.0',
    greetings: ['Hello', 'world!'],
  },
  {
    to: 'one',
  },
);

Service messages

There are some lifecycle messages (ServiceMessage) that are automatically sent by the library. You can listen to them using the .serviceMessages stream to avoid polluting your own messages in .message stream.

import { MessagePeer, ServiceMessage } from '@amadeus-it-group/microfrontends';

const peer = new MessagePeer({ id: 'one' });

peer.serviceMessages.subscribe(({ payload }: ServiceMessage) => {
  switch (payload.type) {
    case 'connect':
      // instance of `ConnectMessage`
      break;
    case 'disconnect':
      // instance of `DisconnectMessage`
      break;
    case 'error':
      // instance of `ErrorMessage`
      break;
  }
});

Logging

Simple logging can be enabled via enableLogging() method. It will log all messages sent and received for debugging purposes.

import { enableLogging } from '@amadeus-it-group/microfrontends';

enableLogging(true);

Information about the network

// List all known peers and their accepted messages
one.knownPeers; // a map of known peers and messages they accept
one.knownPeers.get('two'); // a list of message types peer 'two' accepts

更新日志

0.0.7 (2025-04-11)

Features

  • Export VersionedMessage type (ac60200)

0.0.6 (2025-04-10)

Relaxes Message type to make version optional. This allows to send messages without versioning by default.

Introduces the ability to configure how messages are validated with messageCheckStragegy option:

const peer = new MessagePeer({
  id: 'one',
  messsageCheckStrategy: 'version',
});

Check strategy can be one of default | type | version. By default, only the message structure is checked, with type it is checked that the message type is known to the peer upn reception, with version it is checked that the message type is known to the peer and the version is compatible with the peer.

With default check strategy, the knownMessages becomes optional.

Features

  • Make version optional in the Message interface (d7d671b)
  • Allow changing how messages are checked upon reception (1175331)

Fixes

  • Handle calling .connect() twice correctly (2fca710)
  • Handle calling .listen() twice correctly (c76367f)
  • Don't queue service messages like disconnect at peer level (3ba12a8)

BREAKING CHANGES

  • You might want to use Required<Message> instead of Message if you want to ensure version is mandatory for your message handling.
  • By default, message version and type checks are optional. Previous behaviour can be restored by setting messageCheckStrategy to version:
const peer = new MessagePeer({
  id: 'one',
  messageCheckStrategy: 'version',
  knownMessages: [],
});

0.0.5 (2025-04-03)

New .messages, .serviceMessages and .errors API that make it easier to dissociate peer creation with and places where messages might be consumed. It uses Obsevable and rxjs compatible implementation for streams of messages.

Features

  • Introduce new APIs for message consumption (1216d49)

Fixes

  • Throw an error if provided origin is invalid in .connect() or .listen() calls (db062bf)

BREAKING CHANGES

  • No longer possible to get messages via onMessage, onServiceMessage and onErrorat construction time, one should use.messages, .serviceMessages and .errors subscribable streams:
// BEFORE
const peer = new MessagePeer({
  id: 'one',
  onMessage: (m) => {},
  onServiceMessage: (m) => {},
  onError: (e) => {},
});

// AFTER
const peer = new MessagePeer({ id: 'one' });

peer.messages.subscribe((m) => {});
peer.serviceMessages.subscribe((m) => {});
peer.errors.subscribe((e) => {});

0.0.4 (2025-03-14)

Features

  • Exception during message validation upon reception now reported back to original sender via ErrorMessage (db468df)

Fixes

  • Messages .send() before connection is established are now cached and sent after connection is established (8e6b7cf)
  • Fixed throwing errors when receiving a postMessage from 3rd party libraries (bb41141 )
  • ConnectMessage now is sent before any queued user messages, not after (d02ef3d )

0.0.3 (2025-03-03)

Features

  • Publish CommonJS, ESM and minified bundle (c3a84f9)
  • Add enableLogging() method to configure debugging logging that is off by default(b81ad27)

BREAKING CHANGES

  • Service messages like ConnectMessage, DisconnectMessage have their own callback now:
// Before
let peer = new MessagePeer<M>({
  onMessage: (m: M) => {}, // both user and service messages
});

// After
let peer = new MessagePeer<M>({
  onMessage: (m: M) => {}, // only user messages
  onServiceMessage: (m: ServiceMessage) => {}, // only service messages
});