halo 大家好,俺是 132,今天给大家带来一篇有关于小程序底层架构的文章
小程序总体架构
如图,小程序是双线程异步渲染架构,但是虽然名字都是双线程,不同的作者做出来的架构还是有所不同
上面的图片是 fre-miniapp 的架构,它与微信最大的不同在于,fre-miniapp 是重逻辑层,而 webview 是纯受控渲染,没有逻辑的
我们一一道来
compiler
小程序 compiler 主要做的事情是将小程序文件 wxml/wxss/js 编译为 fre 的 jsx
fre-miniapp 的编译器是自研的,其中 js 部分使用了 esbuild,wxml 则是 rust 写的,所以总体性能超级快,大约比微信快 30 倍,比 taro 的反向编译快 100 倍
当然除了 esbuild 和 rust 的加成,fre-miniapp 的编译器,结构和算法的复杂度都是经过精心设计过的,而且可以批量进行 io 操作
所以各种因素综合下来,fre-miniapp 的编译性能是很快的
双线程
现在我们通过编译器,已经可以拿到一个 fre jsx 组件了,接下来就是跑这个组件,然后生成一串指令,发送到 webview 就可以了
像微信,它的自定义组件逻辑是在 webview 端的,jscore 端只是发送 data 给 webview
而 fre-miniapp 则是把所有的逻辑放在逻辑层,然后发送渲染指令给 jscore
想要做到这一点有两个办法,一个是模拟 dom,一个是 Proxy 劫持 dom 操作
fre-miniapp 中,普通页面和组件的渲染走的是模拟 dom,而 canvas 等则是走 Proxy 劫持(因为模拟一个 canvas 对象成本太高了)
new MutationObserver(mutations => {
send({ type: 'MutationRecord', mutations })
})
const proxy = id => {
const fn = function Canvas(){}
fn.id = id
return new Proxy(fn, {})
}
const canvas = proxy(0)
其中,canvas api 的实现利用了同步 xhr 请求,在逻辑层使用 canvas api,然后 Proxy 劫持,发同步请求给 service worker,然后 service worker await 到 webview 的内容
这样,我们的小程序引擎就可以完成普通的 dom 渲染和 canvas 渲染了
然后就是最开始提到的问题,我们为什么要做重逻辑层轻渲染层的架构设计?
微信的双向通信,就是因为逻辑层和渲染层都很重,所以双向需要不断进行通信,尤其是生命周期,这个通信是需要阻塞的,所以会造成卡顿
其实并不是 setData 的问题,而是通信的问题
我们保证所有逻辑都在逻辑层的话,那么通信就可以变成单向异步流,webview 只需要乖乖听话,完全受控就行了
这样就可以解决通信问题
远程调试
如图,因为 fre 本身的 dom 指令非常纯粹,所以远程调试也很容易做
我们只需要通过 socket 把渲染指令往真机上发送即可,断点可以在本地逻辑上直接打
值得一提的是,远程调试除了远程渲染,海报错 wx api 的调用,比如 wx.request
,异步 api 很简单,那么同步 api 呢?
是的你没看错,还记得上面我们同步 canvas api 的实现吗?
一样的道理,Proxy 劫持,发同步请求,即可
模拟器/容器
fre-miniapp 使用 flutter 做了一个模拟器,但是这不是标准答案
正确的做法是根据业务来,你的 app 想要嵌入小程序引擎的话,只需要做下面几件事情:
- 内置一个 js 引擎,比如 IOS 直接用内置 jscore,RN 直接用 hermes
- 做 webview 和 jscore 的预加载
- 做离线包增量更新机制,小程序走 IO 而不是 http
以上,一个小而美的小程序引擎就成型了
fre
fre 是一个迷你 react 框架,它是 fre-miniapp 的底层渲染框架
因为 fre 只有 500 行,而且用的算法叶比较极致,所以用来做跨端就特别轻松
fre-miniapp 的代码我放到了 github 上,有兴趣可以看看
最后说一下俺的近况,俺已经离开携程了,然后入职了一家新的公司,未来还会根据公司的业务做一些合适的框架
然后业余也会研究其他感兴趣的领域
总之,就是要复活啦,哎嘿