活动回顾 / T SALON

编辑内容

崔明辉谈 MPFlutter:把 Flutter 布局带到微信小程序

崔明辉从当时 Flutter Web 的包体、滚动和文本体验问题出发,介绍 MPFlutter 如何序列化布局、映射宿主组件并复用节点信息,也坦陈四端共用代码与原生体验之间的取舍。

崔明辉在深圳“前端的挑战与机遇”分享中介绍了自己的开源项目 MPFlutter:沿用 Flutter 的开发方式,尝试把同一套应用带到微信小程序及 Web。出发点不是证明所有平台都应该采用 Flutter,而是已有 Flutter 代码怎样跨过小程序环境的限制。

这段分享来自历史录播第 2 段,所属稿件发布于 2022 年 5 月 9 日。技术表现、平台支持和项目案例均按分享时的版本与经验叙述,不代表相关框架今天的能力边界。

问题从哪来:跨平台仍有宿主差异

崔明辉先介绍自己的 iOS 与 Web 开发背景,以及 SVGA 动画库的经历,再把问题限定到微信小程序。Flutter 已能面向多种平台开发,但小程序有自己的运行环境,不能简单把现有 Web 输出当成小程序直接交付。

他当时对 Flutter Web 的主要顾虑包括基础包体、移动端滚动体验,以及 JavaScript 工作与界面更新相互影响。在这些约束下,他希望找到更适合目标环境的渲染路径。片中提到的包体大小和特定设备卡顿,是当时版本及实践观察,不是当前性能基准。

他先把“Flutter 已经跨端”与“能在目标端交付”分开:iOS、Android 和桌面平台各有对应实现,微信小程序却不能直接接收 Flutter 的浏览器产物。即便先从 Flutter Web 入手,他当时观察到简单应用也会有数兆级的基础包体;在某些 Android 设备上,滚动卡顿尤其明显。这些不是说同一套框架无法复用,而是复用之前必须明确小程序的包体和交互约束。

分开看业务逻辑、布局与宿主呈现

分享用 Flutter 的架构帮助解释方案:开发者用 Dart 描述界面和逻辑,框架承担布局与渲染流程,底层再与平台衔接。迁移到浏览器时,最终仍要选择怎样把这些信息呈现在宿主中。

崔明辉比较了当时的 HTML 与基于 Canvas/WebGL 的呈现路径。前者可能产生频繁样式更新与 JavaScript、DOM 交互;后者又需要处理字体、文本选择、输入和无障碍等浏览器能力的衔接。他把这些列为方案需要面对的问题,并非在声称其中任何一条路径永远不可用。

他的滚动例子把开销说明得很直观:若一次滑动要不断改写多层元素的样式,目标是每秒接近 60 帧,JavaScript 与 DOM 的交互便会反复落在关键路径上;如果主线程被其他 JavaScript 任务占住,界面也会受影响。改走 WebGL 不能让问题自动消失。英文字形较小,中文字体集合要大得多;把文字先画到二维 Canvas,再作为纹理交给 WebGL,是一种适配思路,但文字选择、输入框与无障碍仍需另外处理。

关键思路:把已有布局结果交给另一端

MPFlutter 的思路是利用框架已经完成的工作。既然布局和应用逻辑已经在 Flutter 一侧计算,能否把布局信息序列化,再由另一端恢复对应的显示结构?

他拿当时常见的虚拟 DOM 框架作对照:仅把样式和节点映射到宿主,布局仍要在另一端计算;Flutter 框架内部已经算出布局,便有机会把这部分结果一并传过去。这是“用空间换时间”的出发点。应用的逻辑与布局留在 Dart/Flutter 一侧,接收端负责把结果呈现出来,但它并不因此免去平台适配。

他提出,不必为 Flutter 丰富的每一种 Widget 都重新实现完整逻辑;宿主呈现真正需要的是节点的位置、宽高,以及文本、图片等内容。把这些信息传到渲染端,就可以建立相应的组件或元素。

这改变了跨端适配的重点:不是在每个宿主重新写一遍业务,而是维护从框架输出到宿主表示的映射。代价也随之转移到序列化、通信、更新和组件适配上。

序列化与增量更新怎样配合

在具体讲解中,他围绕可复用的 Element 节点组织数据,将相关布局信息序列化为 JSON,再发给渲染端。只要接收端能重建相应位置、尺寸和内容,就可以呈现目标界面。

他指出接收端可以是浏览器、小程序,甚至其他能承接输出的环境;演示中选择 JSON 作为传输形式,不是说格式只能如此。具体映射也不用逐个复制 Flutter 的全部 Widget 类型:普通节点首先需要位置与尺寸,文本和图片等特殊内容再传相应数据。这样听众能从“传一棵树”的抽象说法,看到对端真正要恢复的是什么。

他特别提到节点复用带来的更新机会:利用节点标识比较变化,做差异更新,减少每次刷新都重复处理相同内容。这里的节点、布局及标识策略属于所介绍实现的设计线索,不宜简化成“Flutter 的任意一棵树直接转成 JSON 就能跨端运行”。

分享具体指向可复用的 Element 树:节点有稳定标识,下一次更新时可以据此比较旧数据和新数据,只发送或处理改变的部分。若每一帧都把完整布局重建一遍,序列化和通信本身也会成为成本;增量更新正是为了避免把跨端复用换成另一处重复劳动。

微信小程序没有浏览器 DOM,怎样接上

小程序宿主并不提供普通浏览器的完整 DOM 环境。崔明辉介绍,通过微信相关的 DOM 适配能力衔接已有操作,再将输出映射到小程序的显示体系。因而方案仍有明确的适配层,并不是把浏览器代码原样放进去。

他在视频中提到通过微信环境中的 DOM 模拟层承接这类操作:Flutter 一侧输出布局信息,适配层把节点操作转到小程序可接受的组件和更新方式。这个环节恰好说明“同一套业务代码”与“每个端完全没有专门工作”不是一回事。

现场提到的实践案例是“一兜糖”:按他的介绍,团队用 Flutter 开发 iOS、Android、Web 和小程序,尝试由一套代码支持多个端。他也明确承认,相比原生小程序,体验仍有差距;收益在于代码复用和团队投入的统筹。是否值得采用,需要把这种收益与目标端的性能、兼容和交互要求一起衡量。

构建链路仍以 Dart 与 Flutter 工具为主

最后,他说明项目没有把 webpack 或 TypeScript 当作核心构建起点,而是沿用 Dart 编译到 JavaScript、打包静态资源的路径,并针对输出体积作了精简。代码复用的另一面是工具链维护:适配不仅发生在运行时,也包括编译和资源处理。

讲构建时,他从 Dart 项目生成 JavaScript,再由 Flutter 的资源打包环节处理图片等静态文件。项目要服务小程序的包体约束,还需对原有输出做精简。因而评估 MPFlutter 不能只看写页面时是否仍像写 Flutter;还要检查生成物、平台桥接、升级 Flutter 版本后的适配工作,以及目标端真实的滚动和文字体验。

他以项目开源、实现与工具链可供查看作结。原片到这里结束,进一步的现场问答未收录在本分段中。

查看原视频与分段信息:大前端时代的挑战与机遇:深圳场 →

主题
Flutter小程序前端跨平台