6

Preface

Since the micro-front-end framework micro-app open sourced, many small partners are very interested and ask me how to achieve it, but this is not a few words to explain. In order to clarify the principle, I will implement a simple micro front-end framework from scratch. Its core functions include: rendering, JS sandbox, style isolation, and data communication. Because there is too much content, it will be divided into four articles to explain according to the function. This is the final article of the series: data communication.

Through these articles, you can understand the specific principles and implementation methods of the micro front-end framework, which will be of great help when you use the micro front-end or write a set of the micro front-end framework yourself. If this article is helpful to you, please like and leave a comment.

related suggestion

Start

Architecture design

Each application of the micro front end runs independently, and the communication system should not invade the application too deeply, so we adopt a publish and subscribe system. However, since the sub-application is encapsulated in the micro-app tag, as a webComponents-like component, the weak binding of the publish-subscribe system is incompatible with it.

The best way is to pass data through the micro-app element like normal attributes. However, custom elements cannot support object type attributes and can only pass strings. For example, <micro-app data={x: 1}></micro-app> will be converted to <micro-app data='[object Object]'></micro-app> . If you want to communicate data in a componentized form, you must let the element support object type attributes. For this we need to rewrite micro-app The setAttribute method on the prototype chain handles object type attributes.

flowchart

Code

Create file data.js , the function of data communication is mainly realized here.

Publish and Subscribe System

There are many ways to implement the publish and subscribe system, we simply write one to meet the basic needs.

// /src/data.js

// 发布订阅系统
class EventCenter {
  // 缓存数据和绑定函数
  eventList = new Map()
  /**
   * 绑定监听函数
   * @param name 事件名称
   * @param f 绑定函数
   */
  on (name, f) {
    let eventInfo = this.eventList.get(name)
    // 如果没有缓存,则初始化
    if (!eventInfo) {
      eventInfo = {
        data: {},
        callbacks: new Set(),
      }
      // 放入缓存
      this.eventList.set(name, eventInfo)
    }

    // 记录绑定函数
    eventInfo.callbacks.add(f)
  }

  // 解除绑定
  off (name, f) {
    const eventInfo = this.eventList.get(name)
    // eventInfo存在且f为函数则卸载指定函数
    if (eventInfo && typeof f === 'function') {
      eventInfo.callbacks.delete(f)
    }
  }

  // 发送数据
  dispatch (name, data) {
    const eventInfo = this.eventList.get(name)
    // 当数据不相等时才更新
    if (eventInfo && eventInfo.data !== data) {
      eventInfo.data = data
      // 遍历执行所有绑定函数
      for (const f of eventInfo.callbacks) {
        f(data)
      }
    }
  }
}

// 创建发布订阅对象
const eventCenter = new EventCenter()

The publish and subscribe system is very flexible, but too flexible may cause confusion in data transmission, and a clear data flow must be defined. Therefore, we need to perform data binding. The base application can only send data to the specified sub-application at a time, and the sub-application can only send data to the base application. As for the data communication between the sub-applications, the base application is controlled. The data flow becomes clear

The data binding communication is carried out by formatting the subscription name.

// /src/data.js
/**
 * 格式化事件名称,保证基座应用和子应用的绑定通信
 * @param appName 应用名称
 * @param fromBaseApp 是否从基座应用发送数据
 */
 function formatEventName (appName, fromBaseApp) {
  if (typeof appName !== 'string' || !appName) return ''
  return fromBaseApp ? `__from_base_app_${appName}__` : `__from_micro_app_${appName}__`
}

Since the data communication methods of the base application and the sub-application are different, we define them separately.

// /src/data.js

// 基座应用的数据通信方法集合
export class EventCenterForBaseApp {
  /**
   * 向指定子应用发送数据
   * @param appName 子应用名称
   * @param data 对象数据
   */
  setData (appName, data) {
    eventCenter.dispatch(formatEventName(appName, true), data)
  }

  /**
   * 清空某个应用的监听函数
   * @param appName 子应用名称
   */
  clearDataListener (appName) {
    eventCenter.off(formatEventName(appName, false))
  }
}

// 子应用的数据通信方法集合
export class EventCenterForMicroApp {
  constructor (appName) {
    this.appName = appName
  }

  /**
   * 监听基座应用发送的数据
   * @param cb 绑定函数
   */
  addDataListener (cb) {
    eventCenter.on(formatEventName(this.appName, true), cb)
  }

  /**
   * 解除监听函数
   * @param cb 绑定函数
   */
  removeDataListener (cb) {
    if (typeof cb === 'function') {
      eventCenter.off(formatEventName(this.appName, true), cb)
    }
  }

  /**
   * 向基座应用发送数据
   * @param data 对象数据
   */
  dispatch (data) {
    const app = appInstanceMap.get(this.appName)
    if (app?.container) {
      // 子应用以自定义事件的形式发送数据
      const event = new CustomEvent('datachange', {
        detail: {
          data,
        }
      })

      app.container.dispatchEvent(event)
    }
  }

  /**
   * 清空当前子应用绑定的所有监听函数
   */
  clearDataListener () {
    eventCenter.off(formatEventName(this.appName, true))
  }
}

Create the base application communication object in the entry file.

// /src/index.js

+ import { EventCenterForBaseApp } from './data'
+ const BaseAppData = new EventCenterForBaseApp()

Create communication objects of sub-applications 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) {
    // 创建数据通信对象
    this.microWindow.microApp = new EventCenterForMicroApp(appName)
    ...
  }

  stop () {
    ...
    // 清空所有绑定函数
    this.microWindow.microApp.clearDataListener()
  }
}

Up to this point, most of the functions of data communication have been completed, but one thing is still missing, that is, the support for the object type attributes of the micro-app element.

We rewrite the setAttribute method on the Element prototype chain, and perform special processing when the micro-app element sets the data attribute.

// /src/index.js

// 记录原生方法
const rawSetAttribute = Element.prototype.setAttribute

// 重写setAttribute
Element.prototype.setAttribute = function setAttribute (key, value) {
  // 目标为micro-app标签且属性名称为data时进行处理
  if (/^micro-app/i.test(this.tagName) && key === 'data') {
    if (toString.call(value) === '[object Object]') {
      // 克隆一个新的对象
      const cloneValue = {}
      Object.getOwnPropertyNames(value).forEach((propertyKey) => {
        // 过滤vue框架注入的数据
        if (!(typeof propertyKey === 'string' && propertyKey.indexOf('__') === 0)) {
          cloneValue[propertyKey] = value[propertyKey]
        }
      })
      // 发送数据
      BaseAppData.setData(this.getAttribute('name'), cloneValue)
    }
  } else {
    rawSetAttribute.call(this, key, value)
  }
}

You're done, let's verify whether it can run normally, send data to the sub-application in the vue2 project, and receive data from the sub-application.

// 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: '来自基座应用的数据'
      }
    }, 2000)
  },
  methods: {
    handleDataChange (e) {
      console.log('接受数据:', e.detail.data)
    }
  }
}
</script>

In the react17 project, listen to the data from the base application and send data to the base application.

// react17/index.js

// 数据监听
window.microApp?.addDataListener((data) => {
  console.log("接受数据:", data)
})

setTimeout(() => {
  window.microApp?.dispatch({ name: '来自子应用的数据' })
}, 3000);

View the printing information of the control lift:

The data is printed normally and the data communication function takes effect.

Concluding remarks

It can be seen from these articles that the implementation of micro front-end is not difficult. What is really difficult is the various problems encountered in the development and production environment. There is no perfect micro-front-end framework, whether it is Module Federation or qiankun. Micro-app and other micro-front-end solutions will have problems in certain scenarios. Only by understanding the principles of micro-front-ends can you quickly locate and deal with problems and make yourself invincible.


cangdu
1.1k 声望281 粉丝

hurry up