注册

要不要打造一个轻量的小程序引擎玩玩?

0e4791f9f4e38256ac0902550fc816a2.png

我们的小程序框架的底层,我把它分为四个部分,主要是

  • 多线程模型
  • runtime 框架
  • js 沙箱
  • 其他

我们一个一个来

多线程模型和线程通信

0c70670e0923590ed20e45c66f63615f.png

多线程模型

c72c920076462a1871a79d997c37785f.png

多线程模型是一个非常常见的 UI 模型,包括 RN、flutter 统统都是使用的这个模型,小程序也不例外


它们其实只是线程主体的不同,比如 RN 主要是 shadow tree 和 jscore,而 flutter 则是 skia 和 dart engine,小程序则是 webview 充当渲染层,js engine(或 worker)充当逻辑层


尽管本质一样,但因为业务场景的不同,小程序的诉求却和 RN/flutter 完全不同


在 RN 中,app 作为一个主体,我们更乐意分配更多资源,以至于 RN 一直在跑 react 这种 runtime 浪费的框架,在过去,这被认为是值得的


但是小程序与之相反,它作为 app 的附属品,我们不乐意给小程序分配更多资源,不乐意分配内存,不乐意分配更多线程,所以我们这次分享的前提是

基于最小分配的前提,探讨小程序的每个环节

请记住前提,然后我们接着往下看


线程通信

a5c33d9a265914de65349eaea99523e1.png

说到多线程,我们首先想到的就是多线程的调度和通信,我们先讲通信,通常来说,多线程的非 UI 线程都是没有 dom 环境的,无论是 js 引擎还是 worker


所以为了能跑一个前端框架,我们不得另寻出路,主要方案有三种,其中幻灯片的第二种,写一个 dom 无关的 diff 算法,这是写不出来什么好算法的,所以我们主要看剩下两种思路


7804f12fce349e96ed78a5c79f126c89.png

幻灯片中,左边的代码是 [ 使用 Proxy 劫持 dom ],右边的是 [ 模拟 dom API ]


这两种思路其实是类似的,模拟 dom API 是最为常见的,比如 react-reconciler,vue3 的 renderer,都是用的这个思路,就是它把用到的 dom API 暴露出来,你在不同的端去模拟 dom 的行为


甚至还有 taro-next,kbone 这种框架,他们模拟了一整个 dom/bom 层


这个思路好处是粗暴方便好用,坏处就是抽象程度低,比如 taro-next 中就用了千行代码做这件事,属于 case by case,没啥逼格


所以我提出了 Proxy 劫持 dom 的思路,其实这个思路在微前端中比较常用,只不过我们现在用 Proxy 不再是劫持一两个 dom 操作了,而是将所有 dom 操作通通记录下来,然后批量发送给 UI 线程


这个实现抽象程度非常高,我使用了不到 200 行代码就可以劫持所有 dom 操作


代码在这里:github.com/yisar/fard/…


除了线程通信,更重要的是线程的调度,因为很重要,我们放到最后说


前端框架

3943db046927bc63cd9771b2dfa9d1c1.png

还记得小程序架构的前提吗?没错,就是最小资源分配


因为我们不想给小程序分配过多的资源,所以像 react、vue 这种 runtime 特别重的框架,其实是不适合用作小程序的


甚至 fre 也不适合,因为大家可能对“轻量”这个词有误解,不是代码越少就越轻量,代码量是很重要的一个方面,但是更重要的是代码的内存占用,以及算法的算力和复杂度


fre 虽然代码量少,但它的算法和 vue 是一样的,算力相同,内存占用也不少

ba77a94c3e8178e19a8e178d6b7a7b71.png

所以我们不得不将目光转向 svelte 这类框架,幻灯片可以看到,svelte 通过编译,直接生成原生 dom 操作,没有多余的算法和 vdom


实际上,我们在做性能优化的时候,讲究一个“换”字,react 这种框架,通过浪费 runtime 去做算法,“换”一个最小结果,而 svelte 则是通过编译(浪费用户电脑),去换 runtime



JS 沙箱


4ab87ce68cadc182ae547843294d4ff4.png

然后我们来讲讲沙箱,也就是 js 引擎和 worker,这部分适合语言爱好者

选型

1f6dc625d3f322c1ca0c178bfede1cc7.png

通常来说,一提到 js 引擎,大家都是 v8 v8 v8

但是实际上,v8 是一个高度优化的 JIT 引擎,它跑 js 确实是足够快的,但对于 UI 来说,我们更多要的不是跑得快

a4d18a42bef44b62be47ca2991ab68c3.png

实际上,AOT 的语言或引擎更适合 UI 框架,比如 RN 的 hermes,dart 也支持 AOT,它可以编译成字节码,只需要一次构建即可,当然,AOT 也有缺点,就是 热更新 比较难做


另外除了 js 引擎,worker 也是一个非常赞的选择,方便好用,而且还提供了 bom 接口,比如 offscreen canvas,fetch,indexdb,requestAnimationFrame……

总结

b6b740995671f37e1d6e9fdd14eabeb9.png

哈哈哈总结,我们基于最小分配的前提去设计这个架构,每个环节都选择节省资源的方案


事实上写代码就是这样的,比如我写 fre,那么我追求 1kb,0 依赖,我写业务框架,我追求 0 配置,1mb 的 node_modules 总大小


我写小程序,我追求最小资源分配,不管做啥,有痛点然后有追求然后才有设计


其他

其实小程序还有很多东西可以做,比如现在的小程序都需要兼容微信小程序,也就是类似 wxml,wxss,wxs这些非标准的文件,还要得是个多 Page 的 mpa


比如 ide,我们可以使用 nobundle 的思路来加快构建速度


当然,为了服务业务,在我们公司我没有使用 nobundle


d2277d6fc022dd1b8bc5b1467b70ab03.png

比如剧透一下,我在公司中为了兼容微信小程序,开的新坑


原理是将微信的文件(wxml,wxss,wxs)先编译成可识别的文件(jsx,css,js),然后使用 babel、postCss 去转移,形成一个个 umd 的子应用


然后通过 berial(微前端框架)的路由,沙箱,生命周期,将它们跑在 h5 端,这样就可以在浏览器中模拟和调试啦

48ff9146a41591fe7d02d5a9a01432a8.png

最后我们通过三张图和一个问题,来补充和结束一下这次分享


第一张图是微信小程序的后台桌面,有没有感觉和操作系统有点像,但其实不是的,操作系统的软件是进程的关系,只能切换,不能共存,而小程序是多进程,这些小程序可以在后台留驻,随时保持唤醒


第二张图是钉钉的仪表盘,这也是小程序最常用的场景,就是和这种一堆子应用的 app


第三张图是 vscode 的插件系统,是的,想不到吧,这玩意也是小程序架构,而且也是同样的思想,我不让你操作 dom

然后最后的问题:canvas 怎么办?



这个问题实际上非常难搞,如果我们使用 worker 作为 js 沙箱还好,有 offscreen canvas 和 requestAnimationFrame


如果我们使用 js 引擎怎么办呢,走上面提到的线程通信成本就太高了,动画走这个通信,等接收到消息,动画已经卡死了


所以还有什么办法,这里不得不再次提多线程的特性,也就是多线程的内存是共享的,我们可以 js 引擎中,将 canvas 整个元素放到堆栈里,然后 UI 线程和 js 线程共享这一块内存


这样就不需要走线程通信了,适合 canvas,动画这种场景



链接:https://juejin.cn/post/6962028699872919559


0 个评论

要回复文章请先登录注册