Berial,一个硬了的微前端框架

5,478 阅读4分钟

halo,大家好,俺是 132,好久不贱了呀 ::>_<:: 今天给大家带来一个新框架,名叫 berial

berial 是一个微前端框架,它用简洁的代码,复现了 single-spa 和 qiankun 的核心,然后无第三方依赖

use

<one-app></one-app>
<two-app></two-app>
import { register, start } from 'berial'

register(
  'one-app',
  'http://localhost:3000/one.html',
  (location) => location.hash === '#/app1'
)
register(
  'two-app',
  'http://localhost:3000/two.html',
  (location) => location.hash === '#/app2'
)
start()

micro-front-end?

我之前发过一篇关于微前端的文章,里面说了为什么和本质

微前端产生的原因,说白了是个业务问题,我们的业务项目,分久必合合久必分,一个项目合在一起太久了,等到不好维护的时候,就需要拆分,反之同理,拆的太碎的时候,合在一起更容易维护

微前端的本质是前端路由,不需要和运维童鞋联调,而且自己还能控制生命周期,进行沙箱隔离,甚至可以状态通信

微前端框架大致套路差不多,大概包括下面几个 feature,请容我一一道来

  1. lifeycycles
  1. shadow dom
  2. scoped css
  3. Proxy sandbox
  4. html-loader
  5. global store

lifecycles

生命周期的控制最初是 single-spa 进行的,berial 内部也复现了一组类似的循环队列

load 阶段(microtask)
importHtml -> parser -> shadowdom -> iframe -> sandbox -> bootstrap()
mount 阶段(macrotask)
mount() -> umount() -> mount() -> unmount() -> ...

其中,load 阶段, 负责做的事情有点多,大概就是 import html 然后解析出 dom、css、script,然后将 dom 和 css 放到 shadow 中,然后给 script 套一个 sandbox,然后将 lifecycles 收集起来,这所有流程的每一个步骤,都是 promise,所以这是个 promise 队列

load 阶段只执行一次

然后之后的路由切换,就不断调用 mount 和 unmout 钩子,交替调用……

mount 中做渲染工作,比如 React.render(), unmout 同理

最终,berial 实现了一套类似 event loop 的生命周期队列

shadow dom & scoped css

shadow dom 又名沙雕 dom,名副其实的沙雕,一个死脑筋,钢铁直男,一点也不灵活

市面上几乎所有微前端框架都选择弃用沙雕,berial 的话却选择一起沙雕

这会导致很多问题,有时候需要手动改业务逻辑,但也能接受,我们做微前端框架,真的不是为了 0 改动 迁移,如果追求 0 改动,请移步 iframe

所以我认为,消耗一点点迁移成本,换来更好的架构设计,是容许的

用了沙雕之后,scoped css 浑然天成,而且非常硬,根本无法穿透(还是有 hack 办法的)

sandox

沙箱机制,是微前端框架另一个机制,大概有两种方案

其中一种是快照沙箱,原理是对 window 进行多次遍历,达到激活和失活的效果,但这不适用于 berial

因为 berial 使用了沙雕,意味着它是一个多 app 并存的机制,无论如何都不能共享同一个 window,那,有没有办法呢?

答案是有的,我们用了一个奇技淫巧,就是创建一个 iframe,然后劫持 iframe.contentWindow,等于是拷贝了一份 window 出来

然后配合 Proxy 进行劫持,一个接近完美的沙箱就应运而生了

但也有几点需要注意,比如劫持问题,如果用户访问 document,需要劫持到 shadowRoot,如果访问 document.appendChild 这种方法,需要劫持到 iframe,甚至需要访问 document.title,那就只能劫持到最外层 window

这一整个沙箱就是一层冒泡捕获的机制,层层冒泡劫持(也就只有三层)

最新更新,我们发现不使用 iframe.contentWindow 的新思路

新思路是读写拷贝,这个思路来自 immer,就是我们全量 copy 一份 window 开销很大

于是我们写对象的时候,进行按需拷贝

类似的机制也适用于 berial,我们是需要给每个沙箱分配一个 window 的副本,除了使用 iframe.contentWindwo ,还是可以进行读写 copy 的

这样做有很多好处,比如去除 iframe 的限制(跨域,open等),而且可以减少一层沙箱

而且还可以顺带增加一次大的便利,扩大优化空间

html-loader

html-loader 也是 berial 的一个核心,灵感来自 qiankun,其实是一个 parser,然后找到 script 和 style,暴露出来

这部分目前是纯正则,因为这份匹配实际不需要走整套编译流程,相对 case 其实不是很多

有兴趣的童鞋可以尝试读一下,我们未来也会通过这个机制去探讨正则的更优写法和更高性能

global store

用于状态通信的最简机制,也是通过 Proxy 实现的,可以简单方便地在不同 APP 之间通信

然后我们还会自带一个批处理,让用户多次修改状态,mount 阶段都交替一次

different from qiankun?

和乾坤的不同在于,我们原生使用了沙雕,sandbox 也更硬,而且不依赖 single-spa,所有代码都能自己控制,各个机制之间的优化空间更大

berial 虽然是个业务框架,但代码还是会延续我的风格,就是简单直白风,在追求业务的同时,我们还是会花时间研究底层架构的设计改进

最后放一下地址:

github.com/berialjs/be…

欢迎大家加入组织,未来 berial 也会社区维护下去,大家有啥新玩法可以一起讨论呀