阿里妹攻略:Flutter在设计之初并没有考虑web生态。原因很简单:两种技术的设计理念不同,强行融合很可能会让对方失去优势。但是业内很多团队都在做这样的尝试,可见需求是存在的。今天阿里无线开发达人门流就来教大家如何实现Flutter与Web生态的对接?先说结论:不要连接!“三体警告”根本没用。我自己也在研究如何连接它。可能吃完之后会觉得“真香”。为什么对接?先讨论为什么Flutter要接入Web生态。Flutter是现在很火的跨平台技术,可以在Android、iOS、PC、IoT和浏览器上运行一套代码,被认为是下一代跨平台技术。与Weex和ReactNative相比,可以很好的解决多平台一致性问题。本机渲染性能相似。上层没有JS那种厚重的封装层次,整体性能会稍微好一些。但大多数兴奋学习Flutter的人的第一个问题是:为什么Flutter使用Dart?一门全新的语言意味着新的学习成本。JS不好吃吗?难道JS不香还有TypeScript吗!事实上,Flutter不仅抛弃了JS语言,还抛弃了HTML和CSS,设计了一个更好解耦的Widget系统。Flutter抛弃了整个Web,致力于打造一个新的生态,但是这个生态是不可能复用Web生态的代码和解决方案的。尤其是之前的跨平台方案Hybrid、ReactNative、Weex都接入了Web生态,这让Flutter显得有些格格不入,让大部分前端开发者望而却步。下面是我整理的,前端开发者使用Flutter的各种成本:由于Flutter的开发模式类似于前端框架(可以说是抄袭React),所以Flutter的学习成本framework不高,稍微高一点的是学习Dart语言的成本,以及如何用Widgets组装UI。很多布局Widget虽然设计的很像CSS,但是灵活性还是差了很多。如果要在实际项目中使用,需要改造整个工具链,从“NativeFirst”的角度进行开发。开发Flutter和开发原生应用的环节很相似,和开发前端页面有很大区别。生态成本最高的是前端生态的积累,无论是代码还是技术方案都难以复用。这是最痛的一点,生态也是Flutter最薄弱的环节。不管Flutter为什么放弃web生态,是出于先进的技术理念还是出于商业私心,真正的问题是最大的UI开发者群体是前端,而生态最丰富的是web生态。我认为web技术也是开发UI最高效的方式。如果能在上层使用web技术栈进行开发,在底层使用Flutter实现跨平台渲染,岂不是在开发效率、性能和跨平台一致性之间取得了很好的平衡?也可以复用web技术栈的大量技术积累。也许这些理由还不够。我们暂且按照这个假设继续分析,最后再讨论到底要不要对接。Flutter与Web生态的连接涉及两个方面:从Web到Flutter。就是利用web技术栈进行开发,然后接入Flutter实现跨平台渲染。对于Web,它解决了性能和跨平台一致性的问题,对于Flutter,它解决了生态复用的问题。从Flutter到网络。是官方已经实现的对Flutter的Web支持。它将使用Dart开发的应用程序编译成HTML/JS/CSS,然后在浏览器上运行。可用于降级和外部投射场景。如何实现“从Web到Flutter”?首先分析一下Flutter的架构图,看看从何入手。Flutter可以分为两个部分:Framework和Engine。引擎部分在底部相对稳定。最好不要更改它。需要改变的是Dart实现的Framework。想要对接Web生态,就必须要引入JS引擎。至于是否保留DartVM,还有待商榷。图片最上面的两个UI库Material和Cupertino的前端不需要,前端自带。关键是把Widget部分换成HTML/CSS来写UI,或者保留Widget但是把语言改成JS,不同的方案给出的方案是不一样的。实现对接的方案有很多,业界也有很多尝试。我总结了以下三种方法:TS魔改:用JS引擎替换DartVM,用JS/TS重新实现FlutterFramework(或者直接用dart2js编译)。JS对接:在保留DartVM的同时引入JS引擎,使用前端框架对接FlutterFramework。C++魔改:用JS引擎替换DartVM,用C++重新实现FlutterFramework。TSmagicmodificationTSmagicmodification是完全抛弃DartVM,使用TypeScript重新实现用Dart编写的FlutterFramework。为什么是TS而不是JS?不就是因为TS大火,而且向下兼容JS,现在几乎所有流行的框架都要用TS重写。这个解决方案的出发点是“如果能把Flutter的Dart换成JS就好了”,最容易想到的办法就是把Dart翻译成TS,或者直接用dart2js把代码编译成js,但是编译后的代码包含很多dart:ui等库的封装,生成的包很大,很难自定义需要导出的接口。不如简单的用TS重写,工具链比较熟悉,可以加一些自定义。理论上来说,经过翻译,Flutter的大部分功能还是支持的,各种npm包可以复用,也可以是动态的。但是,如果没有AOT能力,JS语言的执行性能应该不如Dart。而且所有节点的布局操作都发生在JS中,底层只需要提供基本的图形能力即可。就像是基于CanvasAPI写了一套UI框架,性能可能没有现有的前端框架高。另外,最大的问题是如何和官方的Flutter保持一致。如果现在是从v1.13版本翻译过来的,是不是应该在正式升级到v1.15之后同步更新?这个过程没有技术含量,需要持续投入。比较恶心。另外,还要考虑上层是用widget的方式写UI,还是前端用大家熟悉的HTML+CSS。如果你还用Widget,那么大部分前端组件还是不能用,UI还是要重写。反正你要重写,成本没降低,那就用Dart重写吧。。。直接用Flutter官方原版,免得每次更新都要翻译Dart代码。所以既然选择了对接前端生态,就必须对接CSS,否则就没有足够的价值。但是CSS和Widget的对接也是一个非常繁琐的过程,并且存在完整性问题。JS对接翻译代码的方式不够优雅,为什么不保留Dart,将JS/CSS接入Widget呢?当然,这种方式只是使用了Flutter作为底层渲染引擎,上层维护了前端框架的写法,只将渲染部分接入Flutter。现有的很多前端框架将底层的渲染能力抽象出来,可以对接不同的渲染引擎。比如Vue/Rax同时支持浏览器和Weex。同样的,它可以支持另一个Flutter。这种方式比较兼容前端框架,但是链接太长了。业务代码调用前端框架接口进行渲染。短暂的操作后,发出渲染指令。这个渲染指令必须通过通信传递给FlutterFramework。这涉及到一个从JS到C++再到Dart的跨语言转换,然后在接收到渲染命令后转换成对应的Widget树。从CSS到Widget的转换还是很麻烦的。而且,Widget本身可以有一个状态,并且它本身以响应的方式更新。更新时,小部件将重新生成并进行差异化。如果在前端更新UI,前端框架会在js中diff一次vdom,传给Flutter后再diff一次。小部件。如果想绕过Widget,直接连接图中的Rendering层,可以绕过widgetdiff,但是要改FlutterFramework的渲染链接。既然要改FlutterFramework,为什么不干脆用TS魔改代替JS与Dart通信,回到第一种方案。综上所述,该方案的优点是:实现简单,能够最大限度的保留前端开发经验。缺点是:渲染链接长,通信成本高,响应式逻辑冲突,CSS到Widgets的转换不完全。如果要干掉DartVM,需要用其他语言重新实现用Dart开发的Framework。可以用JS/TS,当然也可以用C++。最难的是用C++重新实现Flutter的Framework,然后接入JS引擎,通过绑定将C++接口暴露给JS环境,而上层应用还是用JS开发。将Framework层下沉到C++后,不仅性能会更好,而且支持的语言也会更多。本来,FlutterFramework是基于DartVM的,它必须依赖DartVM才能运行,因此对Dart的依赖性很强;在用C++重新实现后,JS引擎是基于C++版本的Framework,框架本身不依赖于JS引擎。它还可以连接到其他各种语言。比如Java和Kotlin在接入JVM后可以支持,Dart接入DartVM后可以继续支持。该方案可以提升性能并保持与Flutter的一致性,但改造和维护成本相当高。C++的开发效率肯定不如Dart。Flutter快速迭代后如何跟进,是个大问题。如果跟进不及时或执行不一致,很可能会被分化。CSS到Widget的转换也是不得不面对的问题。几种方案的对比上面的方案在同一张图中是这样画的:图中实线表示跨语言通信,过于频繁会影响性能,虚线表示其他连接可能。从下到上,FlutterEngine不需要移动。这一层是跨平台的关键。Framework有JS/TS、Dart、C++三种语言版本。C++版本性能最好,Dart版本成本最低。然后需要向上处理HTML/CSS和Widget的问题,可以直接对接一个前端框架,也可以直接在C++层实现(否则需要暴露的绑定接口太多,通讯方式过于频繁)。如何实现“从Flutter到Web”?该功能已正式实现。使用Dart开发的应用程序可以编译成Web应用程序并在浏览器上运行。官方文档主要介绍用法和API。这里我简单分析一下内部细节实现方案。根据实现原理结合Flutter的架构图,需要改造的是上层的Framework实现从Web到Flutter的改造,底层的Engine是实现从Flutter的改造需要改造的到网络。Framework对Engine的核心依赖是dart:ui,在Engine中实现,抽象UI层绘制接口,连接底层到skia的实现,向上揭示Dart语言的接口.从这点来看,对接的方式比较简单:使用dart2js将Framework编译成JS代码。dart:ui的基于浏览器的API重新实现,即dart:web_ui。Dart编译成JS没有问题,性能可能会受到一点影响,功能可以完整保留,关键是dart:web_ui的实现。在原生Engine中,dart:ui依赖skia暴露的SkCanvas实现绘图。这是一个非常底层的图形接口,只定义了画线、画多边形、纹理等底层能力。它使用浏览器界面来实现这一点。套接字仍然具有挑战性。从上图可以看出,Web版Engine是基于DOM和Canvas实现的。底层定义了两个图形界面,DomCanvas和BitmapCanvas,它们会将传入的层树渲染到浏览器的Element树中,但是节点只包含position,transform和opacity等Styles只使用CSS的一小部分,还有一些更复杂绘图直接用2D
