Dart 中 JS 互操作的历史,你知道吗?

开发 前端
从 Dart 诞生之初,互操作性就是一个核心重点。2011 年 Dart 首次发布时,它被设计为可嵌入和多平台的。它可以运行在独立的虚拟机上,嵌入到浏览器中,并编译为 JavaScript。2015 年 Flutter 出现时,我们也准备将其嵌入其中。现在,我们也很高兴能将 WasmGC 运行时作为目标。

由于在 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 语言提出了挑战。接下来的发展是并行的。

  • 2019 年,Dart 2.7 添加了静态扩展方法。您可以将自定义 Dart 逻辑附加到 JS 互操作类并转换值,例如将 JS Promise 转换为 Dart Future ,而无需使用包装器。
  • 2021 年,我们发布了 @staticInterop 和 package:js v0.6.4。最后,JS 互操作具有足够的表现力 - 您可以公开以前由 dart:html 等 SDK 库专门管理的浏览器 API。
  • 2023 年,当我们在 Dart 3.0 中放弃了不健全的空安全性时,我们终于看到了我们所取得的进步,我们的设计和 @staticInterop 的工作清楚地表明,我们已经准备好解决长期存在的健全性差距。

那一年,我们为 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_interop 是一种静态、健全、惯用、富有表现力且一致的互操作形式,基于能够公开任何 JavaScript 或浏览器 API 的扩展类型。
  • package:web 使用 dart:js_interop 完成 13 年前 dart:html 曾经做过的事情,但是 JavaScript 和 WasmGC 都支持这种方式。

今天,我们很高兴庆祝 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

责任编辑:武晓燕 来源: 独立开发者张张
相关推荐

2016-11-22 15:43:13

机房制冷发展史

2024-03-26 10:10:45

JavaScript操作符操作表达式

2022-09-07 09:01:14

JS操作符运算符

2022-11-10 09:00:41

2020-05-27 11:30:54

Chrome DevT前端命令

2021-08-05 18:21:29

Autowired代码spring

2024-06-03 14:27:08

ThisAPIThat

2019-01-07 13:01:08

Linux惊叹用法命令

2023-12-12 08:41:01

2022-11-16 08:43:30

Node.js模块

2022-09-29 15:32:58

云计算计算模式

2021-10-14 06:52:47

算法校验码结构

2024-09-18 07:00:00

消息队列中间件消息队列

2019-12-12 09:23:29

Hello World操作系统函数库

2022-03-10 08:25:27

JavaScrip变量作用域

2024-05-28 09:12:10

2024-04-07 00:00:00

ESlint命令变量

2024-09-12 08:20:39

2019-02-12 11:15:15

Spring设计模式Java

2023-12-20 08:23:53

NIO组件非阻塞
点赞
收藏

51CTO技术栈公众号