做者|严康 京东 ARES编辑|王文婧ARES 做为京东技术中台的多端融合技术团队,聚焦于跨端开发技术框架和平台搭建,包括但不限于 RN、Flutter、小程序等技术栈。目前已经普遍应用于京东商城、京东金融、京东到家、京东拼购等京东 La 系核心 APP 内,帮助业务团队低成本、快速开发本身的业务,以应对市场的瞬息万变之势。css
Google Flutter 是一个很是优秀的跨端框架,不只能够运行在 Android、 iOS 平台,并且能够支持 Web 和桌面应用。在国内小程序是很是重要的技术平台,咱们也一直思考可否把 Flutter 扩展到小程序端?咱们团队以前已经开源了 Alita 项目,Alita 能够把 React Native 的代码转换并运行在微信小程序平台。受此启发,咱们认为一样是声明式 UI 框架的 Flutter 一样能够运行在小程序平台。html
因此,咱们发起了 flutter_mp 开源项目。以微信小程序为例,不过现阶段,flutter_mp 项目还处于早期的实验阶段,不少功能还在探索规划中,欢迎你们在 Github 上随时关注咱们的最新进展,或者参与项目共同探索。git
原理简介虽然还有诸多功能未完成,咱们先来谈谈整个flutter_mp的实现原理。篇幅缘由,下面咱们将只对flutter_mp几个重要的部分进行简单说明。github
先看下 flutter_mp 的实际效果:web
Flutter 版官方 layout 样例canvas
经过 flutter_mp 转换并运行在小程序端效果小程序
声明式 UI 的处理Flutter是声明式 UI 框架,声明式 UI 只须要向框架描述 UI 长什么样子而不用关心框架具体的实现细节,具体到Flutter,上层的 UI 描述使用底层的 skia 图形引擎处理就是原生Flutter,而把底层处理换成 html/css/canvas 就是flutter_web,而flutter_mp则是探索在类小程序上对这些 UI 描述的处理。微信小程序
咱们看一个最简单例子:var x = 'Hello World'对于上面的 UI 结构,咱们只须要在小程序的 wxml 文件里,用以下的结构对应就 OK 了。
Center(
child: Text(x)
);
// wxml 部分虽然实际的结构要比上面的状况复杂的多,不过经过上面简单的例子,咱们知道起码要作两个事情:
<Center>
<Text>{{x}}</Text>
</Center>
// js 部分
Component({
data: {
x: 'Hello World'
}
})
收集 wxml 渲染须要的数据,放置到小程序组件的 data 字段。设计模式
咱们知道小程序是没法动态操做节点的,wxml 结构须要预先生成,因此Flutter运行在小程序以前,会存在一个编译打包阶段,这个阶段会遍历 Dart 代码,根据必定规则生成 wxml 文件(编译阶段还会作下文将要提到的另一个重要事情 --- 把 Dart 编译为 js)。浏览器
具体的,咱们首先会将 Dart 源码处理为可分析的 AST 结构,AST 是源代码的树型表示结构。而后咱们深度遍历这份 AST 语法树结构,生成目标 wxml,整个过程以下:
构建 wxml 结构的难点在于:Flutter 不只是声明式 UI 仍是“值 UI”,什么叫“值 UI”?简单来讲,Flutter 把 UI 当作是一个普通的值,相似于字符串,数字同样的值,既然是一个普通的值,就能够参与全部的控制流程,能够是函数的返回值也能够是函数参数等等。而小程序的 wxml 虽然也是声明式 UI,却不是“值 UI”,wxml 更加像模版,更加的静态。怎么用静态的 wxml 表达动态的“值 UI”是构建 wxml 结构的关键所在。
看个例子:Widget getX() {这里的 child: x,x 是一个动态值,它的具体值须要在运行阶段才能肯定,它多是任意的 Widget,如何在静态的 wxml 上处理这里动态的 x?受 Alita 框架的启发,这里主要是借助于小程序 template 的动态性(template 的 is 属性能够接受变量值)。有以下几步:
if (condition1) {
return Text('Hello');
} else if (condition2) {
return Container(
child: ...
);
} else if (condition3) {
return Center(
child: ...
);
}
...
}
Widget x = getX();
Center(
child: x // < --- 如何处理这里的 x??
);
<template name="template001">
<text>Hello</text>
</template>
<template name="template002">
<Container>...</Container>
</template>
<template name="template003">
<Center>...</Center>
</template>
在遇到相似 x 这种动态值的时候,固定地会生成一个 template 占位。
<template name="template004">
<Center>
<template is="{{templateName}}" data="{{...templateData}}"/>
</Center>
<template name="template003">
在运行阶段,会根据 getX 函数的运行结果来决定 x 映射的“UI 值”,若是 getX 里面 condition1 为 true,那么这里的 templateName 的值就是 template001。具体的数据计算收集工做,参考下面的 “渲染数据收集”过程。
能够看出 flutter_mp 处理“值 UI”方式,彻底参考了 Alita。
渲染数据收集wxml 结构的生成是在编译阶段就完成了,与它不一样渲染数据是运行时的信息,随时会根据 setState 而改变。那么咱们怎么收集出咱们须要的渲染数据呢?
若是咱们仍是顺着 Flutter 的架构图,很难插入咱们收集的钩子函数,另外 Flutter 的这个架构对于小程序来讲过重了,下图红框里的这些过程对于小程序的渲染来讲并没必要要。最后因为最终的代码会被转化为 js,而 Flutter 自己依赖的库里面不少是不支持转化 js 的,好比 dart:ui 等等。
因此咱们实现了一个极简极简的 Flutter 小程序版本 mini_flutter,在编译期咱们会把全部对 Flutter 库的引用替换为 mini_flutter, mini_flutter 只存在到上图的 Rendering 阶段,这个 Rendering 的实现也是为小程序定制的, 在运行时期 Rendering 不断收集 Widgets 的信息。最终生成一个 UI 描述的 JSON 结构,这个结构就包含了上文所说的 templateName , templateData,UI 描述将会被下层小程序得到,用来渲染小程序 UI,架构图以下:
Flutter的开发语言是 Dart,而小程序的运行环境是浏览器,因此咱们还须要把 Dart 编译为 JavaScript 代码。
在上文的编译打包阶段也提到这一点,这个过程主要是使用了 Dart 提供的 dart2js 工具,不过,针对小程序环境,生成的 js 代码仍须要作一些适配,另外虽然都是 JS 代码,dart2js 生成的 js 和小程序原生 js 的运行环境倒是隔离的,也就是说它们是不能共享变量,方法等等,它们各自在自己的"域"里执行。
这带来两个问题:Widget 初始化 或者 setState 更新,生成的 UI 描述 JSON,如何传递给小程序"域"呢?
相关渲染回调,事件的都发生在小程序"域",这些信息如何传递给 Dart?
总结一下:Dart(最终会编译为 JS)与小程序原生 JS 如何互操做?
解决这个问题主要是借助 dart:js, package:js 这两个库:
Dart 操做 JS:import 'package:js/js.dart';
@JS("JSON.stringify")
external stringify(String str);
这样当 Dart 代码调用 stringify 方法的时候,实际上会执行window.JSON.stringify
方法。
// dart 注册
void main() {
context['dartHi'] = () {
print('dart hi!');
};
}
// js 调用
window.dartHi()
这里只是简单说明 Dart 与 JS 的互操做,另外因为小程序的运行环境是阉割之后的浏览器环境,flutter_mp的实现还稍有不一样。
总之,Dart 与 JS 是能够互操做的,这样就打通了上层Flutter环境和下层小程序环境。
布局系统Flutter的布局系统不一样与 css,可是和 css 颇类似。
在上文提到的 Rendering 阶段,会根据 Widget 的布局属性、类别、约束条件生成一个等效的 css 样式。注意,这里边界约束是上下文相关的。好比一个没有宽高的 Container 实际大小,不只和子元素相关,还和父元素传递过来的边界约束条件相关,这个实际上是比较麻烦的,能不能把 Flutter 的 Widget 属性,边界约束彻底用 css 表达,咱们还在寻求有效的方案。
总结和flutter_web同样,彻底把Flutter全部特性渲染到小程序上是不可能的,通常咱们以为应该是部分页面,部分功能须要运行在小程序上,这样使用flutter_mp才是有意义的。
正如前文所说,flutter_mp还在很早期的阶段,社区的支持和反馈对咱们来讲特别宝贵。同时欢迎广大开发者一块儿来维护flutter_mp。
flutter_mp: https://github.com/areslabs/flutter_mp
若是你须要在生产环境实现小程序跨端开发,推荐使用咱们成熟的 RN 转小程序项目 Alita。
Alita:https://github.com/areslabs/alita
课程推荐Java 工程师除了 Java 语法,还须要哪些知识?点亮 Java 开发技能树,全面掌握:设计模式、流行框架、组件和工具、分布式架构、微服务、性能优化。清除能力短板,突破进阶关口。点击「阅读原文」或扫描「下图二维码」了解 Java 工程师的学习路径。