微前端之三 · 微前端开发中的要点

css 隔离

同时加载多个应用可能出现样式互相覆盖的问题,特别是引入了第三方 UI 库的时候。我们采取了两个方案:

对于面向客户的页面,我们使用 css-in-js 的模式(Emotion css)开发自研的组件库和页面,它生成的 class 名字是一个随机字符串,避免了同名覆盖问题。另外样式都是以 React 组件的形式使用,非常方便复用。

内部管理系统的页面我们使用了 Antd UI 组件库,不同的应用使用的 Antd 版本也不一致,并且使用方无法修改第三方组件内部的 class 名字,很容易造成莫名的冲突。幸好 Antd 支持传入样式前缀,为每个子应用设置一个独一无二的 class 前缀,解决了应用之间 class 重名问题。

js 隔离

js 隔离的目的主要是避免应用之间互相污染全局变量,比如同时依赖了挂载window 上的某个变量。

我们目前没有在技术层面上做 js 隔离。首先我们尽量不使用 window,其次如果需要在 window 上挂载什么内容,我们会通知到所有人,依赖良好的约定做好隔离。

Single spa 提供了一个工具 single-spa-leaked-globals,在子应用销毁的时候它会从 window 上删除其依赖的变量,重新挂载的时候再添加回去。但这种方法还是会直接操作 window 对象,如果在同一个路由页面下展示多个微应用时,依然会有环境变量污染的问题。

我更喜欢 QianKun 中的 proxySandbox 隔离方式,它为每个子应用分配一个 fakeWindow, 再利用 ES6 的 Proxy 来监听 fakeWindow 的 getter 和 setter。如果需要修改原生变量,比如改变 location.href 实现页面跳转,仅仅修改fakeWindow 是无法实现的,这时候需要将其应用到 window 中。每个子应用拥有一个独立的 fakeWindow,它们相互隔离互不干扰,只要小心处理原生变量,这个方案是比较完美的。

应用通信

应用之间难免需要进行通信,比如在修改界面语言的时候,页面上所有的文字必须要同时改变。我们的页面目前基本只有修改语言的时候需要通信,便采取了简单处理。基座应用会给每个子应用定义 customProps 作为 mount 方法的入参, 我们将语言及改变语言的回调函数传给每个子应用,当触发修改时,子应用都能感知到。

如果需要通信的变量较多,可以将上述 customProps 设计的更通用,比如QianKun 实现的 GlobalState, 就是一个典型的发布订阅模式,它在基座应用中初始化一个状态机 (GlobalState),子应用注册监听函数(观察者),当状态机发生更新的时候,通知到所有的观察者。但 QianKun 计划在 3.x 版本删除 GlobalState,目前并没有说明原因以及替代方案。

上面的方法依赖基座应用的中转,另外还有一个不错的方案是利用 js
CustomEvent 原生 API,CustomEvent 支持自定义事件名称,传参方便,非常适合子应用之间的通信。

子应用的发布与版本管理

子应用的发布完全独立,与现在流行的敏捷开发非常契合。当子应用更新后会发布一份新的静态资源到 CDN,SingSpa 通过路由匹配到子应用的时候,需要先拿到最新的版本号以及 js 文件名字中的 hash,再拼接完整的 CDN 地址以获取对应的 js 文件。

这就要求子应用在打包的时候,1. 创新一个新的版本号作为 CDN 里面待创建的文件夹名字(我们使用 semantic-release 工具通过 git commit message 关键字自动生成新的版本),并写入记录版本信息的文件。2. 生成 manifest.json 保存新生成的 js 文件名字。

我们曾经将所有的子应用版本信息放在同一个文件里面,加载任一个子应用之前都必须先下载这个文件,这样做方便管理和查看,但是一旦这个文件没拿到就会导致整个网站崩溃。后来我们将版本文件 (version.json) 分散放在各子应用的静态文件里,并且只存放当前应用的版本信息,即使一个子应用无法拿到也不至于影响到其他的应用。

posted @ 2022-10-25 18:38  WinjayYu  阅读(103)  评论(0编辑  收藏  举报