#interop #js #pattern #macro #js-object #helper

js-helpers

WebAssembly中js对象的易用互操作工具

8个版本

0.1.7 2024年8月21日
0.1.6 2024年8月8日
0.1.5 2024年7月17日

#108WebAssembly

Download history 422/week @ 2024-07-13 54/week @ 2024-07-20 10/week @ 2024-07-27 108/week @ 2024-08-03 18/week @ 2024-08-10

每月214次下载

MIT/Apache

22KB
223

概述

在用Rust编写wasm代码时,我们经常需要深入到javascript世界处理某些方面。使用像wasm-bindgenweb-sysjs-sys这样的流行crate,可以在Rust中完成所有这些。然而,执行这些操作所需的代码很快就会变得异常冗长。例如,以下是从Rust设置javascript全局变量的当前推荐方法

Reflect::set(
    &JsValue::from(web_sys::window().unwrap()),
    &JsValue::from("foo"),
    &JsValue::from("bar")
).unwrap()

这个crate的目标是将所有这些大而烦人的模式封装在简短的宏语法中。例如,使用这个crate的以下代码是等效的

js!(window.foo = "bar").unwrap();

这个crate提供的js!宏旨在提供尽可能接近javascript完整语法的语法,并使用wasm-bindgenweb-sysjs-sys将其展开为正常的Rust代码。重要的是,js!宏不仅仅是eval其内容,而是生成在编译时自动进行语法和类型检查的正常Rust代码。

以下是支持的语法功能的分解;请注意,所有这些功能可以任意组合,顺序不限。

值创建

您可以使用正常的javascript语法创建任何标准javascript值类型。这包括数字、字符串、布尔值、数组和对象

let my_num = js!(45).unwrap();
let my_str = js!("hello world").unwrap();
let my_arr = js!([ 1, true, null, undefined, "test" ]).unwrap();
let my_obj = js!({ name: "john", "with space": true, my_arr }).unwrap();

如上图所示,您还可以通过名称引用作用域内的Rust变量,这些变量会自动转换为JS值。如果您想包含更复杂的Rust表达式,可以将表达式包裹在一个Rust风格的代码块中:{ <expr> }。然而,由于基本的Rust标识符已经支持无需代码块语法,所以特殊的情况{ <ident> },例如{ x },是保留用于创建JS对象,作为{ x: x }的简写。

变量访问

您可以使用正常的JavaScript语法获取或分配JavaScript变量/字段等。赋值表达式返回分配的值,就像在正常的JavaScript中一样。要设置全局变量,您可以从window对象(见上文)访问它们。

let my_obj = js!({ name: "john", "with space": true, my_arr: [1, 2, 3] }).unwrap();
js!(my_obj.name = "kevin").unwrap();
js!(my_obj["with space"] = 17).unwrap();
js!(my_obj.my_arr[1] = { hello: true, world: false }).unwrap();

如上图所示,js!支持点(.)和括号([])语法选项来访问对象。此外,还支持与JavaScript相同的语义的可空点符号(?.)。

let val = js!(my_obj?.foo?.bar?.baz).unwrap();

函数和方法调用

js!宏还支持调用函数和方法的能力。

let my_obj = ...; // pretend we have an object with functions
js!(my_obj.foo(1, 2, 3)).unwrap();
js!(window.open("http://google.com", "_blank")).unwrap();

语法<obj>.<func>(...)<obj>[<func>](...)表示在上下文对象<obj>上的方法调用,而任何其他函数调用都被视为非方法调用(即没有上下文this对象)。

函数

js!宏支持通过正常的JavaScript箭头(=>)或显式函数(function)语法创建匿名函数。

let f = js!((x, y) => x + y).unwrap();
let g = js!(somethingAsync().then(res => res.body)).unwrap();
let h = js!(function (x, y, z) { return x + y * z; }).unwrap();

然而,由于当前wasm动态函数界面的限制,这些创建的箭头函数是非捕获的,并且重要的是不能在作用域内引用Rust JsValue对象。这是因为它们的主体实际上是eval字符串,因此不包含额外的Rust功能或编译时语法检查。

尽管如此,如前所述,您仍然可以使用Rust对象作为参数调用函数。这提供了一种通过高阶函数模拟捕获闭包的方法。

let my_obj = js!({ hello: true, world: 56 }).unwrap();
let f = js!((my_obj => (x, y) => x + y + my_obj.world)(my_obj)).unwrap();

特殊标记

如前所述,您可以在js!宏调用中通过名称访问Rust JsValue对象。然而,以下标识符被宏语法保留,具有特殊含义

  • null - 获取javascript null 值的实例
  • undefined - 获取javascript undefined 值的实例
  • window - 获取全局window对象的引用

任意组合

如前所述,所有这些特性可以任意组合,顺序不限。以下是一个示例

js!(window.username = window.sessions[window.sessionId].getState().username).unwrap();
js!(window.console.log("status", {"active": "on", "inactive": "off"}[window.status])).unwrap();

开发

在开发此crate时,使用以下命令运行所有测试

wasm-pack test --chrome

依赖

~9.5MB
~178K SLoC