滴滴开源小程序框架 Mpx 新特性:局部运行时能力增强

Mpx 是滴滴开源的一款增强型跨端小程序框架,自 2018 年立项开源以来如今已经进入第六个年头,在这六年间,Mpx 根植于业务,与业务共同成长,针对小程序业务开发中遇到的各类痛点问题提出了解决方案,并在滴滴内部建设了完善的小程序跨端开发生态。目前,Mpx 已经覆盖支持了滴滴内部全量小程序业务开发,成为了滴滴小程序开发的统一技术标准。

本文主要探讨MPX局部运行时能力增强的方案设计。如需深入了解滴滴开源项目MPX,请参阅相关文章:

滴滴开源小程序框架Mpx,致力于提高小程序开发体验

滴滴出行小程序体积优化实践

滴滴小程序框架Mpx发布2.0:可直接转换已有微信小程序

小程序框架Mpx的下一代脚手架升级之路|滴滴开源

滴滴小程序开发标准 Mpx 推出新版本,聚焦性能与包体积优化

目前在小程序社区当中存在两种小程序上层框架技术方向:

  1. 以 Mpx 为代表的重编译框架,源码采用小程序平台的dsl,编译后直接产出满足小程序规范的代码

  2. 以 Taro@3.x 为代表的重运行时框架,源码可以使用 Vue、React 作为上层dsl,在运行时当中注入框架代码,同时 polyfill 掉小程序在逻辑线程当中并不能访问的 Dom、Bom 等相关 api

由于技术选型和设计思路的差异,小程序框架的发展及开发者实际开发过程中面临的问题侧重点也有所不同。

对于小程序框架自身的迭代:

  • 重运行时框架已具备现代Web框架开发的效率和体验,主要解决Web标准与上层框架和小程序平台规范的差异性和兼容性问题

  • 重编译框架遵循小程序平台的开发规范,核心在于增强小程序能力,提高开发效率和体验,尽量与现代Web框架能力对齐

Mpx 正是在小程序能力增强的设计思路下,努力与现代 Web 框架的开发体验和能力拉齐,以全面提升用户的开发效率、体验及小程序性能。例如,近两年发布了两个 minor 版本,分别支持了 webpack5 持久化缓存来大幅提高编译构建速度以及 composition-api 特性,让用户能够使用当下最热门且备受好评的开发范式。

在实际业务开发过程中,如果想要使用 props 透传、默认插槽、事件透传等现代 Web 框架提供的功能,但由于小程序自身框架设计未支持这些特性,开发者需要编写大量冗余代码才能达到相同的效果。因此,为了尽可能与现代 Web 框架能力拉齐,我们需要在运行时层面进行能力增强,以提高用户的开发效率和体验。这就是接下来要介绍的 mpx 局部运行时方案设计。

方案设计

在整体的技术方案设计上还是按照小程序能力增强的思路进行,以最小的组件粒度来按需开启运行时渲染。

bee6f53b6272f5a3a20e6a254508ed7d.png

在具体实现上分为运行时和编译两部分内容。

在编译阶段主要完成:

  1. Render Function 增强

  2. 基础模板按需生成

  3. 胶水代码注入

在运行时阶段主要完成:

  1. propertiesToComputed

  2. vdom tree 生成&视图渲染

  3. 组件实例上下维护

  4. 事件代理&分发

aaff474fb8a97ac3b265f048bee7e769.png

核心模块设计

render 函数增强

Mpx 在视图层处理上严格遵守小程序开发规范:一个Mpx SFC文件经过编译后,其处理完成的template将被输出为符合小程序语法规范的wxml静态文件。

在性能优化方面,Mpx 引入了 render 函数。目前,render 函数的主要功能是收集响应式数据。在组件初次渲染阶段,调用 render 函数完成响应式数据的收集。当响应式数据发生变化时,重新触发 render 函数的执行,完成变更后数据的收集,然后进行数据 diff,最后通过 setData 完成页面UI视图的更新。render 函数的形式如下:

global.currentInject = {moduleId: "m3f28ff60",render: function () {this._c("message", this.message);({ tap: [["onViewTapBubble", 'b', "__mpx_event__"]] });this._i(this._c("navigatorList", this.navigatorList), function (item, index) {({ tap: [["jumpTo", item]] });item.name;});this._r();}
}

在运行时增强方案中,Mpx SFC 文件经过编译后,对于 template 处理最大的不同在于经过编译后是处理为一个执行后会生成 vdom tree 的 render 函数。

在组件初次渲染过程中,执行这个 render 函数,不仅完成了响应式数据的收集,还生成了 vdom tree 。这个 vdom tree 描述了 template 的内容,并将它注入到运行时的 render 函数中。因此,在页面的初次渲染阶段,setData 设置的数据是整个 vdom tree ,它描述了整个模板视图的内容。然后通过自定义容器组件 mpx-custom-element 的递归渲染来完成整个组件的渲染。

global.currentInject = {moduleId: 'm3f28ff60',render: function () {var vnode = this.__c('block', [this.__c('view',{class: this.__sc('root', undefined),mpxbindevents: {...},eventconfigs: {...}},[this._i(this._c('navigatorList', this.navigatorList),function (item, index) {return this.__c('view',{...},[...])})])])this._r(vnode)}
}

在组件初次渲染时,通过 setData 传递到渲染层的数据是全量的 vdom tree。当组件响应用户操作或接口请求导致组件实例上的响应式数据发生变化时,会触发 Render Function 的重执行,以生成一个新的 vdom tree。考虑到逻辑层与渲染层通信的成本和性能,这涉及到前后两次 vdom tree 的 diff。最终,生成具体的 vdom 路径数据更新内容,并通过 setData 传递到渲染层,完成视图的局部更新。

mpx-custom-element 容器组件

mpx-custom-element.mpx 是 mpx 内置的一个自定义组件,主要用于运行时渲染。其主要功能有两个:

  1. 作为基础容器组件承载模板渲染:mpx-custom-element.mpx 主要作为一个渲染容器,接收组件传递的需动态渲染的 composed tree 数据,并在其上下文中完成真实组件的渲染。需要注意的是,mpx-custom-element.mpx 本身也是一个基于模板动态渲染的组件,其最终编译输出的内容包含 wxml(视图层)/wxss(样式)/js(逻辑层)/json(配置)。

  2. 跳脱微信小程序模板递归渲染的限制:由于微信小程序的 template 能力不支持模板的自引用递归渲染,因此需要借助一层自定义组件容器来绕开这一限制,从而提供新的渲染上下文。

基础模板

所有模板动态化组件的视图都是基于 mpx-custom-element.mpx 提供的基础模板 mpx-custom-element.wxml 进行动态渲染的。在组件的编译阶段,会按需收集使用到的组件和属性,并将其输出到基础模板中,以确保基础模板的包体积最小化。

组件的 Render Function 执行后生成的 vdom tree 描述了组件的视图层结构和样式。最终,通过组件实例的 setData 方法将 vdom tree 传递到视图层,并利用这些基础模板进行动态渲染。

<template is="tmpl_0_container" wx:if="{{r && r.nodeType}}" data="{{ i: r }}"></template><template name="mpx_tmpl"><element r="{{r}}" wx:if="{{r}}"></element>
</template><template name="tmpl_0_block"><block wx:for="{{i.children}}" wx:key="index"><template is="tmpl_1_container" data="{{i:item}}" /></block>
</template><template name="tmpl_0_view"><view class="{{i.data.class}}" bind:tap="__invoke" data-eventconfigs="{{i.data.dataEventconfigs}}" style="{{i.data.style}}"  data-mpxuid="{{i.data.uid}}"><block wx:for="{{i.children}}" wx:key="index"><template is="tmpl_1_container" data="{{i:item}}" /></block></view>
</template>...

事件系统

小程序采用双线程架构(视图层+逻辑层),在其事件系统设计中:

  • 事件是视图层到逻辑层的通讯方式

  • 事件可以将用户的行为反馈到逻辑层进行处理

  • 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数

同时,小程序的事件模型(事件冒泡、事件捕获)在视图层进行声明,而非在逻辑代码层通过 API 调用的形式实现,这也是小程序的事件系统和 Web 事件系统的最大差异点。

由于小程序没有真实的 DOM/BOM,其他运行时方案中的事件系统需要基于 vdom tree(vdom tree 上绑定了不同节点的事件处理类型&函数)按照浏览器的事件机制实现相关的事件流程(事件冒泡、事件捕获)。在整个事件系统处理的方案中,完全由运行时接管事件触发的生命周期。简单总结流程如下:

  • 由小程序的视图层接收事件的触发

  • 视图层与逻辑层通信,将用户行为反馈到逻辑层处理

  • 运行时代码模拟整个事件系统生命周期

<viewbindtap="__invoke"bindtouchstart="__invoke"bindtouchmove="__invoke"bindtouchend="__invoke"bindtouchcancel="__invoke"bindlongpress="__invoke"></view>

可以看到在基础模板上,我们只对普通的事件如 tap、touchstart 等进行了绑定(在模板上没有绑定其他特殊的事件类型),同时每个事件使用__invoke 代理方法统一响应微信小程序触发的事件。在其他运行时方案(以 Kbone 为例)中,运行时代码实现了一整套事件系统,在__invoke 代理方法内部根据实际的事件类型触发相应的事件,并根据 vdom tree 递归寻找事件触发的上层节点,从而模拟出事件冒泡的流程,事件捕获的流程同理。

然而,在 mpx 运行时能力增强方案中,整个方案设计的出发点是按组件粒度使用。如果事件系统也采用这种运行时模拟事件生命周期的方式,将无法很好地与微信小程序的事件机制贴合在一起:主要体现在事件的生命周期无法统一(一个是视图层的声明式,另一个是逻辑代码层的 API 形式)。因此,在 mpx 运行时能力增强方案中,针对事件系统的设计仍然遵循微信小程序的事件系统设计:视图层的声明式。我们不单独实现一套基于 vdom tree 的事件系统。

为了实现这一目标,在运行时组件的模板编译过程中,我们会收集那些使用了 catch、capture-bind、capture-catch 特殊事件生命周期的节点。这些节点最终会被注入到 mpx-custom-element.wxml 基础模板中,同时会根据节点的属性生成对应的 template 模板内容。这样,最终运行时组件的事件生命周期也与微信小程序保持一致。虽然使用了特殊事件生命周期的节点会被注入到基础模板中,导致 mpx-custom-element.wxml 基础模板的最终体积有所增加,但在我们实际使用的场景中,例如乘客主小程序里使用特殊场景事件的情况少之又少,所以这部分的基础模板体积增加是完全可接受的。

性能相关

对于运行时方案来说,小程序的渲染性能是需要特别关注的地方。相较于编译型框架,随着项目复杂度的提升,运行时方案的整体渲染性能表现可能会较差。

有几个比较典型的场景:

  • 页面初次渲染:对于 mpx 项目,最终页面的模板存在于视图层,渲染只需将模板中使用到的数据传递到视图层。而在运行时方案中,逻辑层需要将全量的 vdom tree 数据传递到渲染层驱动基础模板的渲染,因此整体的数据量会大得多。

  • 页面二次更新渲染:对于 mpx 项目,基于 Render Function 的数据 diff 最终只会收集发生变化的 key 以及 value。然而,对于运行时方案,例如使用 Vue 的 Taro 项目,如果响应式数据发生变化,重新触发 Render Function 执行及后续的 diff 操作,需要准确找到数据发生变更的 vdom 节点信息,因此传输的数据量相较于编译型框架而言也会更多。

在 mpx 运行时渲染中,同样会遇到这些性能问题,但在具体的实现过程中,我们会尽量将一些对性能有影响的点降到最低:

  • 首先,在运行时方案的整体策略上,mpx 运行时的能力是按需的,做到局部开启组件的运行时特性。因此,对于一个页面而言,可以部分走原有的编译方案,部分走运行时的方案。

  • 其次,相较于纯运行时方案,它们的基础模板基本要做到全量的输出,而在 mpx 局部方案中,我们是按需完成基础模板的收集和输出,同时对于模板节点的属性、事件绑定也能做到按需。

小程序基于运行时的能力拓展

对于运行时方案来说,它提供了一种新的逻辑层控制视图层渲染的方式。从某种程度上说,小程序的运行时渲染可以看作是对原生小程序能力的增强,这为小程序的能力拓展带来了更多的可能性。

例如,React 提出的 Server Components,通过调试官方提供的 demo,我们可以大致了解其工作流程。简单来说,前端通过接口(props数据)驱动后端 Server Components 的执行,生成描述前端页面结构的 ast json,然后这部分数据再驱动前端完成最终的视图渲染。

使用 Server Components 会带来以下好处:

  • Zero-Bundle-Size Components(在 server 端运行的组件及其纯 js 依赖最终并不会被下载到浏览器当中运行)

  • loading performance(Server Components 可以直接访问后端的服务or database,对于一些场景可以减少网络请求数量,同时把一些纯计算的工作可以放到 server)

那么,在小程序场景下,借助运行时的能力,我们是否可以尝试实现类似 Server Components 的功能呢?暂时抛开工程方面的问题,针对目前小程序的技术架构,可能会遇到以下的问题。

视图渲染

在小程序场景下,视图层渲染有两种方案:一种是原生小程序的渲染流程,另一种是运行时渲染流程。以 mpx 运行时能力增强为例,编译环节将 template 转换为可执行的 Render Function,运行后生成描述视图的 vdom tree,然后驱动视图层的基础模板进行渲染。在这个过程中,Render Function 的执行实际上是由 props/data 的变化驱动的,与平台和环境(例如不依赖小程序提供的API)无关。因此,Render Function 也可以在 server 端执行,生成描述视图的 vdom tree。

既然在 server 端可以实现与原本在小程序环境下相同的效果,那么小程序通过接口获取到这部分数据后,后续的流程就可以交给运行时方案接管,完成最终的渲染。

4830fb9ce9ed0d6420f4eb7d31ce9477.png

样式规则

对于 wxss 样式规则而言,需要注意的是:在小程序的技术架构中,开发者工具在编译构建阶段会将 wxss 和 wxml 提前处理为可执行的函数,这些代码最终在小程序运行时被注入到视图层执行。

然而,在 Server Components 中,样式规则不再是静态数据,而是由业务后端接口下发。这些数据最初存在于逻辑线程中。为了使逻辑线程中的样式规则能够在视图层生效,有且仅有一个方案就是在 Render Function 执行完生成 vdom tree 后将样式规则通过 inline 的方式合并到最终需要被渲染的 composed tree 当中。       

aa1a146c85a06b3ea65eec8e64e586c3.png

组件逻辑

对于一个组件来说,通常包括由外部传入的 props、自身的状态、生命周期、事件处理(用户交互)等方面。首先,我们可以明确的是,小程序组件的生命周期脱离了小程序平台是无法正常运行的,同时事件也需要在用户发生交互行为后才能触发。因此,对于组件本身而言,这部分内容无法像视图层渲染那样将js代码存放在远端并通过接口/cdn 的形式下发,而只能随着小程序的上线发布存在于逻辑线程中。这样,组件的生命周期和事件交互等方法可以与原有的执行策略保持一致,确保其正常工作。对于事件的处理,与运行时方案一致:通过事件代理的形式来分发真实的用户操作。

实际上,这在 server components demo 中也可以清晰地看到不同类型的组件的职责划分非常明确:

  • 对于 x.server 组件而言:由 props 驱动(props 来源可以是 client 的请求,也可能是服务端访问其他服务或 database),不涉及组件自身状态的维护、生命周期、用户事件交互,只做纯渲染工作;

  • 对于 x.client 组件而言:在浏览器中执行的代码与普通组件无异,可以进行任何操作。

server components 和 client components 混合渲染

在 RFC 中也提到了 server components 和 client components 相互引用的场景。其中,server components 最终在服务器端运行并生成 vdom tree,而 client components 则仅通过一个组件标识下发,其实例的创建在浏览器端执行。

在小程序运行时方案中,也存在类似的场景:部分组件采用运行时渲染,这些组件可能会引用原生的小程序组件,这意味着组件内部存在两种不同的组件渲染方式。对此,我们有相应的解决方案:将原生的小程序组件作为依赖注册到基础模板容器组件中,基础模板最终也需要生成原生组件。

结语

Mpx 作为一款兼具优秀开发体验和深度性能优化的增强型跨端小程序框架,我们一直致力于在此领域持续前进,同时也诚邀大家加入 Mpx 用户群,共同参与和交流。

40e6bada16a0aec9bd85a6f3dc4b898d.jpeg

1504e2dfb0e2c0bd9ebb941e49d311e9.gif

点阅读原文,跳转项目官网

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/248205.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android中下载 HAXM 报错 Intel® HAXM installation failed,如何解决?

最近在搭建 Flutter 环境&#xff0c;但是在 Android Studio 中安装 Virtual Device 时&#xff0c;出现了一个 问题 Intel HAXM installation failed. To install Intel HAXM follow the instructions found at: https://github.com/intel/haxm/wiki/Installation-Instructio…

基于ldap实现登录认证

最近开发的应用需要外协人员实现登录认证&#xff0c;外协人员的密码等信息已经录入到ldap, 需要连接ldap进行登录认证。下面先介绍一下登录的网络旅程图。 一.nginx实现AES加密 nginx请求处理入口&#xff08;前端请求为json格式&#xff09; location /aes {default_type te…

adb测试冷启动和热启动 Permission Denial解决

先清理日志 adb shell logcat -c 打开手机模拟器中的去哪儿网&#xff0c;然后日志找到包名和MainActivity adb shell logcat |grep Main com.Qunar/com.mqunar.atom.alexhome.ui.activity.MainActivity 把手机模拟器的去哪儿的进程给杀掉 执行 命令 adb shell am start -W…

方法、数组

方法 是语句的集合&#xff0c;在一起执行一个功能 它是解决一类问题的步骤的有序集合 包含于类或对象中 在程序中创建&#xff0c;在其他地方被引用 设计方法的原则&#xff1a;方法的本意是功能块&#xff0c;就是实现某一个功能的语句块的集合。设计时&#xff0c;最好保持…

Vue3+Vite使用Puppeteer进行SEO优化(SSR+Meta)

1. 背景 【笑小枫】https://www.xiaoxiaofeng.com上线啦 资源持续整合中&#xff0c;程序员必备网站&#xff0c;快点前往围观吧~ 我的个人博客【笑小枫】又一次版本大升级&#xff0c;虽然知道没有多少访问量&#xff0c;但我还是整天没事瞎折腾。因为一些功能在Halo上不太好实…

Unity中URP下额外灯角度衰减

文章目录 前言一、额外灯中聚光灯的角度衰减二、AngleAttenuation函数的传入参数1、参数&#xff1a;spotDirection.xyz2、_AdditionalLightsSpotDir3、参数&#xff1a;lightDirection4、参数&#xff1a;distanceAndSpotAttenuation.zw5、_AdditionalLightsAttenuation 三、A…

哪吒汽车与经纬恒润合作升级,中央域控+区域域控将于2024年落地

近日&#xff0c;在2024哪吒汽车价值链大会上&#xff0c;哪吒汽车与经纬恒润联合宣布合作升级&#xff0c;就中央域控制器和区域域控制器展开合作&#xff0c;合作成果将在山海平台新一代车型上发布。 哪吒汽车首席技术官戴大力、经纬恒润副总裁李伟 经纬恒润在智能驾驶领域拥…

Springboot自定义线程池实现多线程任务

1. 在启动类添加EnableAsync注解 2.自定义线程池 package com.bt.springboot.config;import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.concurrent.ThreadPoolTask…

MySQL原理(二)存储引擎(1)概述

一、存储引擎介绍 1、概念&#xff1a; &#xff08;1&#xff09;MySQL中的数据用各种不下同的技术存储在文件中&#xff0c;每一种技术都使用不同的存储机制、索引技巧、锁定水平并最终提供不同的功能和能力&#xff0c;这些不同的技术以及配套的功能在MySQL中称为存储引擎…

@Async结合CompletableFuture实现主线程阻塞,CompletableFuture并发执行任务

Async结合CompletableFuture实现主线程阻塞&#xff0c;CompletableFuture并发执行任务 项目开发中经常会遇到业务需要多任务处理的场景&#xff0c;比如目前我除了的业务就是如此。 我要提供给客户端一个批量查询第三方数据的接口&#xff0c;由于是调用第三方的接口&#xf…

正则表达式 文本三剑客

一 正则表达式&#xff1a; 由一类特殊字符及文本字符所编写的模式&#xff0c;其中有些字符&#xff08;元字符&#xff09;不表示字符字面意义&#xff0c;而表示控制或通配的功能&#xff0c;类似于增强版的通配符功能&#xff0c;但与通配符不同&#xff0c;通配符功能是用…

分享4款不能错过的修改照片尺寸的软件!

在当今这个数字化时代&#xff0c;照片已经成为我们分享生活、表达观点的重要方式。但是&#xff0c;你是否曾遇到过这样的问题&#xff1a;一张精美的照片因为尺寸不合适而无法在朋友圈中展现出最佳效果&#xff1f;不用担心&#xff0c;今天我们就来聊聊那些可以帮助你轻松修…

漏洞原理远程命令执行

漏洞原理远程命令/代码执行 远程命令执行函数&#xff08;Remote Command Execution Function&#xff09;是指在一个网络环境中&#xff0c;通过远程执行命令来控制另一个计算机系统或设备的功能。 远程命令执行函数可以通过网络协议&#xff08;如SSH、Telnet、RPC等&#x…

wpf 数据转换(Bytes 转 KB MB GB)

效果 后端 using ProCleanTool.Model; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Data;namespace P…

SkyWalking+es部署与使用

第一步下载skywalking :http://skywalking.apache.org/downloads/ 第二步下载es:https://www.elastic.co/cn/downloads/elasticsearch 注&#xff1a;skywalking 和es要版本对应&#xff0c;可从下面连接查看版本对应关系&#xff0c;8.5.0为skywalking 版本号 Index of /di…

keepalived+nginx双主热备(有问题私信)

keepalivednginx双主热备 前言keepalivednginx双主热备keepalivednginx双主热备部署安装nginx安装keepalived修改master节点的keepalived配置文件 修改backup节点的keeepalived配置文件配置keepalived主备配置keepalived双主热备 前言 有关keepalived和nginx的一些工作原理&am…

【蓝桥杯日记】复盘篇二:分支结构

前言 本篇笔记主要进行复盘的内容是分支结构&#xff0c;通过学习分支结构从而更好巩固之前所学的内容。 目录 前言 目录 &#x1f34a;1.数的性质 分析&#xff1a; 知识点&#xff1a; &#x1f345;2.闰年判断 说明/提示 分析&#xff1a; 知识点&#xff1a; &am…

RHCE练习3

1.基于域名www.openlab.com可以访问网站内容为 welcome to openlab 2.给该公司创建三个子界面分别显示学生信息&#xff0c;教学资料和缴费网站&#xff0c;基于www.openlab.com/student 网站访问学生信息&#xff0c;www.openlab.com/data网站访问教学资料www.openlab.com/mo…

python之组合数据类型-列表

列表操作 列表增删改查列表增加元素的方法列表删除元素的方法列表修改元素的方法列表查找元素的方法 列表其他常用方法列表的切片用法列表修改排序的方法列表的常用符号、常用函数 列表是什么&#xff1f; 列表是有序集合&#xff0c;列表可以一次性存储几个或几万个元素&#…

海外云手机运营Instagram攻略

Instagram是世界著名的社交媒体平台&#xff0c;有着10亿实时用户&#xff0c;是跨境电子商务的优质流量来源。平台以女性用户为主&#xff0c;购物倾向高&#xff0c;转化率好。它被公认为外贸行业的优质社交媒体流量池。那么&#xff0c;如何使用海外云手机吸引Instagram上的…