preface
Since the micro front-end framework micro-App became open source, many friends are very interested and ask me how to achieve it, but it is not a few words can be understood. To illustrate how this works, I’m going to implement a simple micro front-end framework from scratch. Its core features include rendering, JS sandbox, style isolation, and data communication. This is the final part of the series: Data Communication.
Through these articles, you will learn how micro front end frameworks work and how they are implemented, which will be of great help if you use them later or write your own. If this post helped you, feel free to like it and leave a comment.
Related to recommend
- Micro-app Warehouse address
- Simple-micro-app Warehouse address
- Write a micro front end frame – render from scratch
- Write a micro front end frame – sandbox from scratch
- Write a micro front end frame – style isolation section from scratch
- Write a micro front end framework from scratch – data communication article
- Micro – app is introduced
start
Architecture design
The micro front applications themselves run independently, and the communication system should not penetrate the application too deeply, so we use a publish-subscribe system. But because the subapplication is encapsulated within the Micro-app tag, as a WebComponent-like component, the weak bindings of the publish-subscribe system are incompatible with it.
The best way to do this is to pass the data through a micro-app element like a normal attribute. However, custom elements cannot support attributes of object types and can only be passed strings, such as
The flow chart
Code implementation
Create file data.js, data communication function is mainly implemented here.
Publish and subscribe system
There are many ways to implement a publish-subscribe system, but let’s simply write one that meets the basic requirements.
// /src/data.js
// Publish a subscription system
class EventCenter {
// Cache data and binding functions
eventList = new Map(a)/** * bind the listening function *@param Name Event name *@param F Binding function */
on (name, f) {
let eventInfo = this.eventList.get(name)
// If there is no cache, initialize
if(! eventInfo) { eventInfo = {data: {},
callbacks: new Set(),}// Put it in the cache
this.eventList.set(name, eventInfo)
}
// Record the binding function
eventInfo.callbacks.add(f)
}
// Unbind
off (name, f) {
const eventInfo = this.eventList.get(name)
// eventInfo exists and f is a function
if (eventInfo && typeof f === 'function') {
eventInfo.callbacks.delete(f)
}
}
// Send data
dispatch (name, data) {
const eventInfo = this.eventList.get(name)
// Update only when the data is not equal
if(eventInfo && eventInfo.data ! == data) { eventInfo.data = data// Iterate over all binding functions
for (const f of eventInfo.callbacks) {
f(data)
}
}
}
}
// Create publish subscribe objects
const eventCenter = new EventCenter()
Copy the code
Publish-subscribe systems are flexible, but being too flexible can lead to a chaotic flow of data, and a clear set of data flows must be defined. So we’re going to do data binding, where the base application can only send data to the specified child application at one time, and the child application can only send data to the base application, and the data communication between the children is controlled by the base application, so that the data flow is clear
Data binding communication is done by formatting subscription names.
// /src/data.js
/** * Format the event name to ensure binding communication between the base application and the child application *@param AppName Application name *@param FromBaseApp Whether to send data from the base application */
function formatEventName (appName, fromBaseApp) {
if (typeofappName ! = ='string'| |! appName)return ' '
return fromBaseApp ? `__from_base_app_${appName}__ ` : `__from_micro_app_${appName}__ `
}
Copy the code
Because the data communication between the base application and the sub-application is different, we define it separately.
// /src/data.js
// Set of data communication methods applied by the base
export class EventCenterForBaseApp {
/** * send data to the specified subapplication *@param AppName Subapplication name *@param Data Object data */
setData (appName, data) {
eventCenter.dispatch(formatEventName(appName, true), data)
}
/** * Clear an application's listener function *@param AppName Name of the sub-application */
clearDataListener (appName) {
eventCenter.off(formatEventName(appName, false))}}// Set of data communication methods for the subapplication
export class EventCenterForMicroApp {
constructor (appName) {
this.appName = appName
}
/** * listen for data sent by the base application *@param Cb binding function */
addDataListener (cb) {
eventCenter.on(formatEventName(this.appName, true), cb)
}
/** * Unlisten function *@param Cb binding function */
removeDataListener (cb) {
if (typeof cb === 'function') {
eventCenter.off(formatEventName(this.appName, true), cb)
}
}
/** * send data to the base application *@param Data Object data */
dispatch (data) {
const app = appInstanceMap.get(this.appName)
if(app? .container) {// The child application sends data as a custom event
const event = new CustomEvent('datachange', {
detail: {
data,
}
})
app.container.dispatchEvent(event)
}
}
/** * Clear all listening functions bound to the current child application */
clearDataListener () {
eventCenter.off(formatEventName(this.appName, true))}}Copy the code
Create the base application communication object in the entry file.
// /src/index.js
+ import { EventCenterForBaseApp } from './data'
+ const BaseAppData = new EventCenterForBaseApp()
Copy the code
Create communication objects for the child application in the sandbox and clear all bound events when the sandbox is closed.
// /src/sandbox.js
import { EventCenterForMicroApp } from './data'
export default class SandBox {
constructor (appName) {
// Create a data communication object
this.microWindow.microApp = new EventCenterForMicroApp(appName)
...
}
stop () {
...
// Clear all binding functions
this.microWindow.microApp.clearDataListener()
}
}
Copy the code
At this point, most of the data communication is complete, but one thing that is missing is support for object type attributes of micro-App elements.
We override the setAttribute method on the Element prototype chain to make it special when the micro-app Element sets the data attribute.
// /src/index.js
// Record the native method
const rawSetAttribute = Element.prototype.setAttribute
/ / rewrite setAttribute
Element.prototype.setAttribute = function setAttribute (key, value) {
// When the target is a Micro-app tag and the attribute name is data
if (/^micro-app/i.test(this.tagName) && key === 'data') {
if (toString.call(value) === '[object Object]') {
// Clone a new object
const cloneValue = {}
Object.getOwnPropertyNames(value).forEach((propertyKey) = > {
// Filter the data injected by the Vue framework
if(! (typeof propertyKey === 'string' && propertyKey.indexOf('__') = = =0)) {
cloneValue[propertyKey] = value[propertyKey]
}
})
// Send data
BaseAppData.setData(this.getAttribute('name'), cloneValue)
}
} else {
rawSetAttribute.call(this, key, value)
}
}
Copy the code
With that done, let’s verify that it works properly by sending data to and receiving data from the child application in vuE2.
// vue2/pages/page1.vue
<template>
...
<micro-app
name='app'
url='http://localhost:3001/'
v-if='showapp'
id='micro-app-app1'
:data='data'
@datachange='handleDataChange'
></micro-app>
</template>
<script>
export default{... mounted () {setTimeout(() = > {
this.data = {
name: 'Data from base applications'}},2000)},methods: {
handleDataChange (e) {
console.log('Accept data:', e.detail.data)
}
}
}
</script>
Copy the code
In the REact17 project, listen for data from and send data to the dock application.
// react17/index.js
// Data listening
window.microApp? .addDataListener((data) = > {
console.log("Receiving data:", data)
})
setTimeout(() = > {
window.microApp? .dispatch({name: 'Data from sub-applications'})},3000);
Copy the code
View the printed information of the control lift:
The data is printed normally and the data communication function takes effect.
conclusion
From these articles, we can see that micro front-end implementation is not difficult, the real problem is the development and production environment encountered various problems, there is no perfect micro front-end framework, whether it is Module Federation, Qiankun. Micro-app and other micro front end solutions will encounter problems in certain scenarios. Only by understanding the principle of micro front end can we quickly locate and deal with problems and make ourselves invincible.