开启左侧

小程序多平台同构方案分析-kbone 与 remax

[复制链接]
在线会员 只要你感受就 发表于 2023-2-6 09:54:47 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
目前海内女伶 href="https://www.taojin168.com/cloud/" target="_blank">小法式仄台浩瀚,微疑小法式、付出宝小法式、头条小法式、和未来借会呈现的新小法式仄台,以是为了处置一套代码能够正在多个小法式仄台上运行,呈现了多种计划去处置,京东的 Taro、蚂蚁的 Remax、微疑的 Kbone,各有特性,主要回为二品种型,编译时取运行时适配二种。
此文介绍海内支流小法式的架构,和颠末运行时适配可到达一套小法式代码运行正在多个小法式仄台上的计划,主要介绍 kbone 取 remax 二套计划,他们道理根本不合,统统小法式代码皆正在 worker 线程上运行,终极正在 worker 线程天生一棵 dom tree,再把 dom tree 共步到 render 线程上颠末 w/axml中止 衬着。
小法式架构

小法式素质上是运行正在 webview 上的一个 H5 使用,代码颠末挨包后别离运行正在 render 线程取 worker 线程,这样干最年夜的启事是包管仄台宁静性,不克不及闪开收者掌握 render 线程,掌握 render 线程将会构成小法式仄台圆管控艰难,好比颠末 js dom api 操纵 dom 元艳,颠末 location.href 随便跳转,这全部小法式便完整不成控,能够沉意绕太小法式考核,上线时是个一般小法式,开辟者能够随便掌握界里上展示的实质或者随便跳转到打赌或者黄色页里。小法式仄台便把 view 取逻辑别离,view 搁正在 render 线程,供给了一种特别的语言(微疑嚷 wxml 、付出宝嚷 axml)去写 view,而且不克不及写进 js 代码,逻辑便搁正在 worker 线程,因为 worker 其实不能操纵 dom,以是便处置了上面管控艰难的成就,架构以下:


小法式多仄台共构计划阐发-kbone 取 remax-1.jpg

每一个小法式界里有 axml 取 js 文献,js 文献是页里逻辑,逻辑主要干二件工作:

  • 照应 render 线程的工作,并施行小法式营业逻辑。
  • 准备佳数据,颠末 setData 传到 page 中,由 page中止 衬着。
以上是海内微疑、付出宝、头条小法式的架构,可是今朝开辟者假设要把一个小法式撑持三个仄台战 web 仄台,便需要开辟屡次,今朝呈现了多种共构仄台。有编译时取运行时静态变换二种。
编译时 Taro 干的很胜利,Taro 可让开辟者用 React 写小法式,终极颠末编译变换到差别仄台的小法式。
来日诰日道的是另一种计划,没有靠编译时去完毕,而是正在运行时干适配,别离是微疑供给的 kbone 取付出宝供给的 remax 二个计划。
二个计划比照:

  • 差异面

    • 皆是正在 worker 线程保护一棵 vdom tree,而后共步到 render 线程颠末 w|axml 去截至衬着。

  • 差别面

    • kbone 是适配了 js dom api ,基层能够用所有框架,如 react、vue、本死 js 去写小法式。remax 是自已经写了一套 react 的 renderer,基层只撑持 react。
    • remax 正在 dom tree发作 变革时,没有是把整棵 vdom tree 传到 render 线程,而是计较差别,把差别传到 render 线程,那面能够放慢了二个线程之间的数据传输速率。

kbone

kbone 正在 worker 线程适配了一套 js dom api,基层不论是哪一种前端框架(react、vue)或者本死 js 终极皆需要挪用 js dom api 操纵 dom,适配的 js dom api 则接收了统统的 dom 操纵,并正在内乱存中保护了一棵 dom tree,统统基层终极挪用的 dom 操纵城市革新到那棵 dom tree 中,屡屡操纵(有撙节)后会把 dom tree 共步到 render 线程中,颠末 wxml 自界说组件截至 render。
过程以下:

小法式多仄台共构计划阐发-kbone 取 remax-2.jpg
因而统统小法式的代码皆是搁正在 worker 上跑,开辟者能够颠末差别的前端框架(react、vue、angular) 或者本死 js 去建立小法式了。
worker 线程

worker 线程会运行统统的小法式代码,并适配了 js dom api 战界说一套数据构造去描绘一棵 dom tree。
模仿 js dom api 即是把 api 函数从头完毕一次,那些函数用去操纵自己正在内乱存中保护的 dom tree,比方以下 api办法 :

  • document.createElement
  • document.createTextNode

正在 worker 线程中自己是不 document 工具的,只要供把自己模仿的 document 工具寄存到全部变质中,这基层的前端框架或者本死 js 代码就可以挪用到了。颠末 document创立 的每一个节面有四个主要的属性:

  • type:以后 节面范例
  • parentNode:女节面工具
  • childNodes: 儿童节面工具数组
当 worker 线程创立佳了 dom tree 后,正在内乱存中的大要少上面如许:
  1. {
  2.     "innerChildNodes": [],
  3.     "childNodes": [{
  4.         "nodeId": "b-1573463704434",
  5.         "pageId": "p-1573463704431-/pages/index/index",
  6.         "type": "element",
  7.         "tagName": "div",
  8.         "id": "app",
  9.         "class": "h5-div node-b-1573463704434 ",
  10.         "childNodes": [{
  11.             "nodeId": "b-1573463704435",
  12.             "pageId": "p-1573463704431-/pages/index/index",
  13.             "type": "element",
  14.             "tagName": "div",
  15.             "id": "",
  16.             "class": "h5-div node-b-1573463704435 ",
  17.             "childNodes": [{
  18.                 "nodeId": "b-1573463704436",
  19.                 "pageId": "p-1573463704431-/pages/index/index",
  20.                 "type": "element",
  21.                 "tagName": "button",
  22.                 "id": "",
  23.                 "class": "h5-button node-b-1573463704436 ",
  24.             }, {
  25.                 "nodeId": "b-1573463704438",
  26.                 "pageId": "p-1573463704431-/pages/index/index",
  27.                 "type": "element",
  28.                 "tagName": "span",
  29.                 "id": "",
  30.                 "class": "h5-span node-b-1573463704438 ",
  31.             } ]
  32.         }]
  33.     }]
  34. }
复造代码
那是一棵多叉树,每一个节面界说了目前节面的属性战儿童节面。交下来即是把那棵树传到 render 线程,并由 render 线程把他显现进去。那里传到 render 线程接纳的是小法式供给的办法 setData,把那棵 dom tree 当做数据传到 render 界里。

render 线程
  1. <view>
  2.   <picker></picker>
  3.   <button>面尔</button>
  4.   <Element>
  5.     <button></button>
  6.     <button></button>
  7.   </Element>
  8. </view>
复造代码
上面代码是 wxml 语法写的一个小法式界里,worker 线程中的内乱存 dom tree 能够战 wxml 里的节面一一对于应,只要供把 dom tree 颠末递回迭代映照到 wxml 的节面。
kbone 界说了一个 [Element 自界说组件],用于衬着 dom tree 上的每一个节面战他的儿童节面。
Element 节面干的工作比力简朴,起首是把自己衬着进去,而后再把子节面衬着进去,共时子节面的子节面又颠末 Element 去衬着,如许便颠末自界说组件完毕了递回功用,那是 wxml 自界说组件供给的自引用特征,每一个节面颠末 dom 节面的 type 去辨别,进而把一棵内乱存 dom tree 颠末 wxml 衬着进去了。
Element 代码以下(简单):
  1. <!--目前节面-->
  2. <cover-view wx:elif="{{wxCompName === 'cover-view'}}" id="{{id}}" class="{{class}}" style="{{style}}" hidden="{{hidden}}" scroll-top="{{scrollTop}}">
  3.   <template is="subtree-cover" data="{{childNodes: innerChildNodes}}" />
  4. </cover-view><scroll-view wx:elif="{{wxCompName === 'scroll-view'}}" id="{{id}}" class="{{class}}" style="{{style}}" hidden="{{hidden}}" scroll-x="{{scrollX}}" scroll-y="{{scrollY}}" upper-threshold="{{upperThreshold}}" lower-threshold="{{lowerThreshold}}" scroll-top="{{scrollTop}}" scroll-left="{{scrollLeft}}" scroll-into-view="{{scrollIntoView}}" scroll-with-animation="{{scrollWithAnimation}}" enable-back-to-top="{{enableBackToTop}}" enable-flex="{{enableFlex}}" bindscrolltoupper="onScrollViewScrolltoupper" bindscrolltolower="onScrollViewScrolltolower" bindscroll="onScrollViewScroll">
  5.   <template is="subtree" data="{{childNodes: innerChildNodes, inCover}}" />
  6. </scroll-view>
  7. <live-player wx:elif="{{wxCompName === 'live-player'}}" id="{{id}}" class="{{class}}" style="{{style}}" hidden="{{hidden}}" src="{{src}}" mode="{{mode}}" autoplay="{{autoplay}}" muted="{{muted}}" orientation="{{orientation}}" object-fit="{{objectFit}}" background-mute="{{backgroundMute}}" min-cache="{{minCache}}" max-cache="{{maxCache}}" sound-mode="{{soundMode}}" auto-pause-if-navigate="{{autoPauseIfNavigate}}" auto-pause-if-open-native="{{autoPauseIfOpenNative}}" bindstatechange="onLivePlayerStateChange" bindfullscreenchange="onLivePlayerFullScreenChange" bindnetstatus="onLivePlayerNetStatus">
  8.   <!--递回-->
  9.   <template is="subtree-cover" data="{{childNodes: innerChildNodes}}" />
  10. </live-player>
  11. <!--子节面-->
  12. <block wx:for="{{childNodes}}" wx:key="nodeId" wx:for-item="item1">
  13.   <block wx:if="{{item1.type === 'text'}}">{{item1.content}}</block>
  14.   <image wx:elif="{{item1.isImage}}" data-private-node-id="{{item1.nodeId}}" data-private-page-id="{{item1.pageId}}" id="{{item1.id}}" class="{{item1.class || ''}}" style="{{item1.style || ''}}" src="{{item1.src}}" rendering-mode="{{item1.mode ? 'backgroundImage' : 'img'}}" mode="{{item1.mode}}" lazy-load="{{item1.lazyLoad}}" show-menu-by-longpress="{{item1.showMenuByLongpress}}" bindtouchstart="onTouchStart" bindtouchmove="onTouchMove" bindtouchend="onTouchEnd" bindtouchcancel="onTouchCancel" bindtap="onTap" bindload="onImgLoad" binderror="onImgError"></image>
  15.   <view wx:elif="{{item1.isLeaf || item1.isSimple}}" data-private-node-id="{{item1.nodeId}}" data-private-page-id="{{item1.pageId}}" id="{{item1.id}}" class="{{item1.class || ''}}" style="{{item1.style || ''}}" bindtouchstart="onTouchStart" bindtouchmove="onTouchMove" bindtouchend="onTouchEnd" bindtouchcancel="onTouchCancel" bindtap="onTap">
  16.     {{item1.content}}
  17.     <block wx:for="{{item1.childNodes}}" wx:key="nodeId" wx:for-item="item2">
  18.       <block wx:if="{{item2.type === 'text'}}">{{item2.content}}</block>
  19.       <image wx:elif="{{item2.isImage}}" data-private-node-id="{{item2.nodeId}}" data-private-page-id="{{item2.pageId}}" id="{{item2.id}}" class="{{item2.class || ''}}" style="{{item2.style || ''}}" src="{{item2.src}}" rendering-mode="{{item2.mode ? 'backgroundImage' : 'img'}}" mode="{{item2.mode}}" lazy-load="{{item2.lazyLoad}}" show-menu-by-longpress="{{item2.showMenuByLongpress}}" bindtouchstart="onTouchStart" bindtouchmove="onTouchMove" bindtouchend="onTouchEnd" bindtouchcancel="onTouchCancel" bindtap="onTap" bindload="onImgLoad" binderror="onImgError"></image>
  20.       <view wx:elif="{{item2.isLeaf || item2.isSimple}}" data-private-node-id="{{item2.nodeId}}" data-private-page-id="{{item2.pageId}}" id="{{item2.id}}" class="{{item2.class || ''}}" style="{{item2.style || ''}}" bindtouchstart="onTouchStart" bindtouchmove="onTouchMove" bindtouchend="onTouchEnd" bindtouchcancel="onTouchCancel" bindtap="onTap">
  21.           {{item2.content}}
  22.         </view>
  23.       <!--递回-->
  24.       <element wx:elif="{{item2.type === 'element'}}" in-cover="{{inCover}}" data-private-node-id="{{item2.nodeId}}" data-private-page-id="{{item2.pageId}}" id="{{item2.id}}" class="{{item2.class || ''}}" style="{{item2.style || ''}}" bindtouchstart="onTouchStart" bindtouchmove="onTouchMove" bindtouchend="onTouchEnd" bindtouchcancel="onTouchCancel" bindtap="onTap" generic:custom-component="custom-component"></element>
  25.     </block>
  26.   </view>
  27.   <element wx:elif="{{item1.type === 'element'}}" in-cover="{{inCover}}" data-private-node-id="{{item1.nodeId}}" data-private-page-id="{{item1.pageId}}" id="{{item1.id}}" class="{{item1.class || ''}}" style="{{item1.style || ''}}" bindtouchstart="onTouchStart" bindtouchmove="onTouchMove" bindtouchend="onTouchEnd" bindtouchcancel="onTouchCancel" bindtap="onTap" generic:custom-component="custom-component"></element>
  28. </block>
复造代码
remax

remax 是颠末 react 去写小法式,全部小法式是运行正在 worker 线程,remax 完毕了一套自界说的 renderer,道理是正在 worker 线程保护了一套 vdom tree,那个 vdom tree 会颠末小法式供给的 setData办法 传到 render 线程,render 线程则把 vdom tree 递回的遍历进去。
以是部分完毕战 kbone类似 ,皆是正在 worker 线程保护一棵 dom tree,再把那棵 dom tree 传到 render 线程截至衬着,唯一的区分是 remax dom tree发作 变革时,管帐算差别,而没有需要把整棵树皆传到 render 线程,此功用是 react 供给的,即是正在 diff 完后找出差别,则把差别传到 render 线程,比方:

小法式多仄台共构计划阐发-kbone 取 remax-3.jpg

差别里面记载佳了是哪一个节面要截至简略或者增加,此中 path 变质标记是树上的哪一个节面,如 root.children.0.children.1,他代表的意义即是顶节面下第 0 个儿童节面下的第 1 个儿童节面。
render 线程会记载一棵 vdom tree 正在内乱存中,屡屡 worker 线程传过去的 patch 会标记要操纵树上的哪些节面,把那些节面 patch 到 render 线程的 vdom tree 上后,再革新到界里上。
归纳

小法式共构计划呈现过许多,把 vue 或者 react交流 失落现有的女伶 href="https://www.taojin168.com" target="_blank">小法式开辟方法实是很没有错,开辟者能够拿自己熟谙的开辟框架去开辟小法式,共时 vue 取 react 的社区死态这样老练,如组件库、形状办理框架等均可以间接拿去使用,放慢了小法式的开辟速率。
kbone 取 remax 二套计划,觉得 kbone开展 远景没有错,他可让您颠末 vue 取 react 等统统框架去开辟小法式。可是里面必然另有许多坑要处置,一个老练的框架借需要相干配套皆老练,今朝 kbone 取 remax 那二块干的借不敷,期望前期他们能够放慢开辟速率,完美相干配套。

精彩评论10

avatar
在线会员 ECCf2f 发表于 2023-2-6 09:55:04 | 显示全部楼层
还有一个alita 也是运行时方案 https://github.com/areslabs/alita
回复

使用道具 举报

avatar
在线会员 M9D 发表于 2023-2-6 09:55:13 | 显示全部楼层
小程序把分为渲染和worker线程是为了安全?这个我一直不太理解,防止审核后显示色情站点,现在的小程序一样能做到吧?
回复

使用道具 举报

avatar
在线会员 VYil2A6wf4N 发表于 2023-2-6 09:55:45 | 显示全部楼层
楼主应该关注uni-app,阿里小程序工具官方内嵌的跨端工具。编译和运行是要协作搭配的,才能更好的平衡运行性能和开发体验
回复

使用道具 举报

avatar
在线会员 5DlYsGu 发表于 2023-2-6 09:55:56 | 显示全部楼层
我只是举其中一个例子,重点是不能让用户拿到 js dom api ,你说的内容安全这块现在还是能做到,但是可控。
回复

使用道具 举报

avatar
在线会员 tkgvdkBWvT 发表于 2023-2-6 09:56:27 | 显示全部楼层
是的,这里只是列出一些核心技术点。
[哈哈]
回复

使用道具 举报

avatar
在线会员 kM2ugqWV 发表于 2023-2-6 09:56:40 | 显示全部楼层
不太懂,仿造的dom api是把什么转换成don tree,这个dom tree与虚拟dom树和浏览器dom有啥区别?是你上面截图的js对象?不是一个element 对象吗?
回复

使用道具 举报

avatar
在线会员 mwVHv 发表于 2023-2-6 09:57:06 | 显示全部楼层
为什么不让用户拿到dom?
回复

使用道具 举报

avatar
在线会员 lSV4itR 发表于 2023-2-6 09:57:30 | 显示全部楼层
用底层是webview的小程序造了一个运行h5的虚拟机,这样真的不慢吗
回复

使用道具 举报

avatar
在线会员 8xL 发表于 2023-2-6 09:57:46 | 显示全部楼层
肯定比默认小程序写法慢,worker中多了一个上层框架的执行,并且首次渲染时通信的数据传输会大很多
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册 qq_login

本版积分规则

发布主题
阅读排行更多+
用专业创造成效
400-778-7781
周一至周五 9:00-18:00
意见反馈:server@mailiao.group
紧急联系:181-67184787
ftqrcode

扫一扫关注我们

Powered by 职贝云数A新零售门户 X3.5© 2004-2025 职贝云数 Inc.( 蜀ICP备2024104722号 )