由于在 Dart 3.3 中达到了令人兴奋的 JavaScript 互操作里程碑,Wasm 的支持刚刚登陆当前的 Flutter 测试版。为了庆祝这一里程碑,我们回顾了 Dart 和 JavaScript 互操作性长达十年的历程。
从 Dart 诞生之初,互操作性就是一个核心重点。2011 年 Dart 首次发布时,它被设计为可嵌入和多平台的。它可以运行在独立的虚拟机上,嵌入到浏览器中,并编译为 JavaScript。2015 年 Flutter 出现时,我们也准备将其嵌入其中。现在,我们也很高兴能将 WasmGC 运行时作为目标。
起初,我们很快就暴露了嵌入 Dart 的各个平台的功能。这就是我们的 SDK 平台特定库出现的方式:dart:io 暴露了虚拟机上的文件系统,dart:html 暴露了 Web 上的浏览器 API,等等。这些库在外观和感觉上都与普通的 Dart 库无异,但其背后却隐藏着一些复杂的底层本地原语,以使它们能正常工作。这是我们发明的第一种互操作形式。它具有很强的表现力,但仅限于 SDK 库。
在 Web 上,开发人员需要访问的不仅仅是浏览器 API。因此,我们开始研究如何开放互操作性,以覆盖更多目标。作为起点,我们在 2013 年推出了 dart:js,以实现对 JavaScript 库的访问。
// 用于说明 Dart/JS 互操作的简短 JavaScript 代码示例window.myTopLevel = { field1: 0, method2() { return this.field1; }}
// 通过“dart:js”访问(2013)import 'dart:js' as js;void main() { // 这一行有一个错字!哎呀 :( var object = js.context['myTopLevl']; object['field1'] = 1; // 此调用因 noSuchMethod 失败,因为 method2 返回一个 int,哎呀 object.callMethod('method2', []).substr(1);}
我们当时就知道,dart:js 并不是我们想要的编程模型。你必须使用字符串来访问 JavaScript 中的名称--别提在编译时发现问题了,也别提代码自动补全了!实现成本也很高。大多数操作都严重依赖盒和深度拷贝。因此,我们在 2014 年和 2015 年继续起草各种想法,直到 package:js 的 v0.6 版本发布。
// 通过 `package:js` 访问 (2015)import 'package:js/js.dart';// @JS 注解允许我们声明 API 签名:@JS()class MyObject { external int get field1; external void set field1(int value); external String method2();}@JS()external MyObject get myTopLevel;void main() { // 访问代码不容易出错:分析器可以检查 // 这些符号与声明相匹配,而且我们还能获得代码补全! var object = myTopLevel; object.field1 = 1; // 但是没有检查类型,这就在一个 int 上调用了子串,这是不正确的。 object.method2().substring(1);}
有了 package:js,我们终于有了高效、用户友好的开放式 API。你可以在抽象类上添加一些注解,然后就可以访问 JavaScript API 了。这一切就像魔法一样神奇,直到它失效。使用 package:js 有很多无法实现的功能:直接访问浏览器 API、重命名成员、转换、附加 Dart 逻辑等等。为了弥补这些不足,我们还提供了 dart:js_util--一个类似于 dart:js 的轻量级、高效的底层 API 作为备用。package:js 中的所有限制确实困扰着我们,但我们束手无策。我们需要更多的 Dart 语言来做得更好。
大约在那个时候,我们已经在致力于对语言进行有史以来最大的改变——我们让 Dart 听起来更有趣。讽刺的是,当我们在 2018 年发布带有 Dart 2.0 的新类型系统时,互操作性变得更糟!除了这些早期的限制之外,使 package:js 变得特别的魔法也有一个黑暗的一面——它无法检查类型的有效性。这意味着我们的互操作性是我们原本健全的语言中不健全的根源。
之后,我们的工作重心发生了变化,转而集中精力改进 Dart 和 JS-interop。我们遵循明确的原则(习惯化、表现力强、组合性强、精确、平易近人、务实、非神化和完整),转向以类型和静态分派为基础的设计,并对 Dart 语言提出了挑战。接下来的发展是并行的。
那一年,我们为 WasmGC 引入了编译功能,并利用 JS 互操作在其上运行 Flutter web 等丰富的框架。这引发了 JS Types 的工作,以在编程模型中明确定义 Dart 和 JS 的边界,并找到在 Wasm 和 JS 编译目标中使用 JS 的一致方法。我们还开始了扩展类型语言实验--这是 Dart 3.3 中推出的一项功能,它在 Dart 语言和 JS 互操作之间架起了一座桥梁。多年来,JS 互操作的行为(如类型擦除)与 Dart 中的任何其他行为都不匹配。有了扩展类型,JS 互操作终于可以习以为常,并在 Dart 开发工具中获得应有的支持。
尽管一路走来经历了许多转变和转折,但有一件事在整个十年中始终如一:我们的 Dart 社区的积极参与。社区成员采取了早期步骤测试并为 dart:js 做出贡献,然后影响 package:js 的设计。他们编写了工具来解决功能差距 (package:js_wrapping[1]),并尝试通过自动生成 Dart API 来提高生产力的方法 (package:js_facade_gen[2] 、 package:js_bindings[3] 、 package:typings[4])
最后,我们已经到了 2024 年了。我们在 Dart 3.3 中发布了 dart:js_interop 以及 package:web ,这是 Dart 中 JS 互操作的最新解决方案,使将 Flutter 编译为 Wasm 成为可能。
// 通过 `dart:js_interop` 访问 (2024)import 'dart:js_interop';// 声明使用扩展类型,这与 package:js// 声明非常相似。主要区别在于:它们是静态调度的。extension type MyObject._(JSObject _) implements JSObject { external int get field1; external void set field1(int value); external String method2();}@JS()external MyObject get myTopLevel;void main() { var object = myTopLevel; object.field1 = 1; // At last, access is sound - this line fails with a type error // when returning from method2. object.method2().substring(1);}
今天,我们很高兴庆祝 Dart/JS 互操作的新形式及其所带来的未来。了解我们的过去,我们确信这不是旅程的终点,而是我们历史上令人兴奋的时刻。
我们迫不及待地想看看您将用它构建什么!
原文:https://medium.com/dartlang/history-of-js-interop-in-dart-98b06991158f
[1]package:js_wrapping: https://github.com/a14n/dart-js-wrapping
[2]package:js_facade_gen: https://github.com/dart-archive/js_facade_gen
[3]package:js_bindings: https://pub.dev/packages/js_bindings
[4]package:typings: https://pub.dev/packages/typings
本文链接:http://www.28at.com/showinfo-26-82365-0.htmlDart 中 JS 互操作的历史,你知道吗?
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 教你如何玩转Next Image