First of all, I wish you a happy 1024 Programmers festival!

Note: This article does not cover state sharing between microapplications and main applications, only two-way communication is considered.

background

In our previous micro-front-end transformation of existing projects, there were always some projects that could not be transformed into micro-front-end projects, or involved costs that were not easy to transform. Such as: SSR, JSP/PHP project and so on. But no matter what the cost, we’re going to have to get access in one way or another. So in some scenarios, we tend to downgrade the micro front end to iframe to fit our needs.

However, the communication of the micro front end is very different from that of iframe.

Differences in communication methods

Since the implementation container of the micro front end is still in the same JavaScript execution environment as the parent project, communication is also very simple. However, the execution environment of iframe is isolated from the parent project, which makes communication difficult. In the non-cross-domain case, we can also communicate by using a similar communication approach to the microfront (calling the window.parent object directly). The introduction of MDN

However, in the case of cross-domain communication, the browser security policy does not allow iframe to communicate directly, but asynchronously in the form of postMessage, and the data transmitted can only be serialized by JSON.

The difference between the micro front-end and iframe communication leads to a big gap in its communication ideas. But in any case, we want to smooth out the differences as much as possible in the way we communicate in microapplications. So design a common communication SDK that allows any supported application type to access freely and use the same communication API.

ADAPTS to multiple application types

The SDK has to bridge the differences in usage and architecture design.

General aggregation design ideas may only be ifelse, such as ali’s design of a small program SDK aggregation framework. This method is the most simple, direct and crude, but also the most effective. But the implementation is not very elegant, and maintenance costs can be high. This can be modified using the “adapter pattern” in the design pattern.

Since many of you at the front end know more about the publish-subscribe model and the producer-consumer model, I’ll cover the adapter pattern here. For example: sometimes electrical plugs in other countries/regions are inconsistent with those in mainland China. In Hong Kong, for example, electrical plugs are generally larger than those in the mainland (see figure below). So the electrical appliances we bought back from Hong Kong cannot be used directly. You need to purchase an adapter to perform the transformation (figure below), which is how the adapter pattern is used in real life.

The role of the adapter pattern is to resolve interface incompatibilities between two software entities. With the adapter pattern, two entities that cannot work together because of incompatible interfaces can work together. The concept of the adapter pattern is not hard to understand, but how do you implement it in code?

We divide iframe and microfront end into two classes and implement them separately. IframeChannel and LocalAppChannel respectively. Each class implements its own logic. IframeChannel has logic for asynchronous callbacks as well as handling logic for message listening.

However, the apis exposed by the IframeChannel and LocalAppChannel classes are exactly the same regardless of the implementation. Finally, in the SDK entry, select different adapters to instantiate according to whether the current page is an IFrame

In this way, we leverage the adapter pattern so that we can individually encapsulate the different Cannel for the microfront end and iframe and aggregate them through a unified class. In this way, the communication code of both the micro front end and the IFrame does not pollute the implementation of the principal communication logic as much as possible.

However, neither best practices nor design patterns are one-size-fits-all. There may still be a small amount of intrusive code. We try to keep the maintenance costs as low as possible.

Processing of asynchronous messages

In daily development, it is inevitable to encounter some asynchronous communication scenarios.

For example: ask the main project to give you some data, ask the main project to open a popover, etc. The biggest problem with asynchrony, however, is that it can lead to races. For example, if you send two asynchronous messages, the return order of the asynchronous messages is uncertain. If the two messages are not sorted out, the impact on business logic can be serious.

This is especially true in iframe scenarios. Postmessage itself is asynchronous message communication. You might be faced with sending two messages, but you don’t know which one the two messages belong to. To address this problem, you can choose to carry a unique ID for each message.

When a message is sent, it is sent with a unique ID and uses the message ID as the key to store resolve and Reject into our global map.

When an asynchronous result message is sent to a child application, it is also sent back with the previous unique ID.

The child application gets the ID, finds the corresponding asynchronous callback function and fires.

At the same time, in order for all messages to master each other’s status, whether the parent application sends a message to the child application or the child application sends a message to the parent application, the other application is required to send back an ACK message to ensure that the other application receives the message and completes the message processing. This idea comes from the network protocol. At the same time use ACK to confirm the message design, also can do timeout processing logic.

Design implementation of message unique ID

We mentioned earlier that messages are sent with a unique message ID for later identification. There are also the following types of message ID:

  • Math.random() : use random number to generate unique ID, high repetition rate, discard
  • UUID: Extremely low repetition rate, but still possible. Deferred combination consideration
  • Counter: the only variable source, theoretical reliable

The implementation of a counter is usually an incremented sequence number based on the ID of the message sent. Because JavaScript is run in a single thread, there are no variable conflicts like in Java, a multithreaded language. Therefore, our message ID generation rule can be combined with UUID + counter to avoid ID conflict to the greatest extent

other

In the future, depending on the demand, we will probably do communication across sub-applications, communication across page applications.

In our internal project, this part of the function code has been implemented, this article only provides an idea. If you want to see the code, welcome to join our team. My email is [email protected]