32个版本

0.5.7 2023年1月2日
0.5.4 2022年12月28日
0.4.3 2022年7月17日
0.3.0 2021年5月13日
0.0.2 2014年11月21日

#31 in WebAssembly

Download history 41/week @ 2024-03-11 48/week @ 2024-03-18 75/week @ 2024-03-25 75/week @ 2024-04-01 49/week @ 2024-04-08 67/week @ 2024-04-15 47/week @ 2024-04-22 44/week @ 2024-04-29 42/week @ 2024-05-06 52/week @ 2024-05-13 33/week @ 2024-05-20 58/week @ 2024-05-27 49/week @ 2024-06-03 41/week @ 2024-06-10 37/week @ 2024-06-17 48/week @ 2024-06-24

每月177次下载
12 个crate(10个直接使用) 中使用

MIT/Apache

21KB
282

js-wasm

docs.rs docs

JavaScript和WebAssembly应该一起使用起来很愉快。

本项目旨在提供一种简单、易于学习、技术无关的跨Rust和JavaScript的桥接方式,通过极简的设置和开箱即用的Cargo编译工具。我希望几乎任何熟悉JavaScript的Rust开发者都能在悠闲的下午学会如何使用它。

Hello World

让我们看看如何在控制台放置一些基本示例

cargo new helloworld --lib
cd helloworld
cargo add js
vim src/lib.rs
use js::*;

#[no_mangle]
pub fn main() {
    js!("function(str){
        console.log(str)
    }")
    .invoke(&["Hello, World!".into()]);
}

注意基本语法是构建一个函数,然后用参数数组调用它。在底层,这是一个名为 InvokeParameter 的枚举数组,我为各种类型制作了小的转换器(见下文),以帮助数据跨越障碍。大部分情况下,你可以使用 .into() 将数据转换为 InvokeParameter

vim index.html
<html>
    <head>
        <meta charset="utf-8">
        <script src="https://unpkg.com/js-wasm/js-wasm.js"></script>
        <script type="application/wasm" src="helloworld.wasm"></script>
    </head>
    <body>
        Open my console.
    </body>
</html>

此库提供了一个相对简单的机制,在页面加载期间执行您的WebAssembly。

vim Cargo.toml
# add these lines for WebAssembly to end of Cargo.toml

[lib]
crate-type =["cdylib"]

[profile.release]
lto = true
cargo build --target wasm32-unknown-unknown --release
cp target/wasm32-unknown-unknown/release/helloworld.wasm .
python3 -m http.server

# open https://127.0.0.1:8000 in browser
# right click, inspect, look at message in console

完整示例在此:https://github.com/richardanaya/js-wasm/tree/master/examples/helloworld.

它是如何工作的?

js crate使得实例化javascript函数并传递参数变得非常容易。目前这个crate支持以下类型的参数:

  • 未定义的
  • Float64
  • BigInt
  • String
  • JavaScript对象引用
  • Float32Array
  • Float64Array
  • Boolean

以下是几个示例,展示了人们可能想要执行的常见操作。

与DOM对象交互

以下是一个更复杂的示例,它调用了返回DOM对象引用的函数

Screen Shot 2022-12-18 at 9 21 54 PM
use js::*;

fn query_selector(selector: &str) -> ExternRef {
    let query_selector = js!(r#"
        function(selector){
            return document.querySelector(selector);
        }"#);
    query_selector.invoke_and_return_object(&[selector.into()])
}

fn canvas_get_context(canvas: &ExternRef) -> ExternRef {
    let get_context = js!(r#"
        function(canvas){
            return canvas.getContext("2d");
        }"#);
    get_context.invoke_and_return_object(&[canvas.into()])
}

fn canvas_set_fill_style(ctx: &ExternRef, color: &str) {
    let set_fill_style = js!(r#"
        function(ctx, color){
            ctx.fillStyle = color;
        }"#);
    set_fill_style.invoke(&[ctx.into(), color.into()]);
}

fn canvas_fill_rect(ctx: &ExternRef, x: f64, y: f64, width: f64, height: f64) {
    let fill_rect = js!(r#"
        function(ctx, x, y, width, height){
            ctx.fillRect(x, y, width, height);
        }"#);
    fill_rect.invoke(&[ctx.into(), x.into(), y.into(), width.into(), height.into()]);
}

#[no_mangle]
pub fn main() {
    let screen = query_selector("#screen");
    let ctx = canvas_get_context(&screen);
    canvas_set_fill_style(&ctx, "red");
    canvas_fill_rect(&ctx, 10.0, 10.0, 100.0, 100.0);
    canvas_set_fill_style(&ctx, "green");
    canvas_fill_rect(&ctx, 20.0, 20.0, 100.0, 100.0);
    canvas_set_fill_style(&ctx, "blue");
    canvas_fill_rect(&ctx, 30.0, 30.0, 100.0, 100.0);
}

调用 invoke_and_return_object 返回一个名为 ExternRef 的结构,它是对从JavaScript接收到的某个东西的间接引用。您可以将此引用传递给其他JavaScript调用,这些调用将接收到这个选项。当结构根据Rust生命周期释放时,其句柄将从JavaScript端释放。

回调和定时器

此库对如何在Rust中回调没有意见。有几种方法可以使用。以下是一个简单的示例。

use js::*;

fn console_log(s: &str) {
    let console_log = js!(r#"
        function(s){
            console.log(s);
        }"#);
    console_log.invoke(&[s.into()]);
}

fn random() -> f64 {
    let random = js!(r#"
        function(){
            return Math.random();
        }"#);
    random.invoke(&[])
}

#[no_mangle]
pub fn main() {
    let start_loop = js!(r#"
        function(){
            window.setInterval(()=>{
                this.module.instance.exports.run_loop();
            }, 1000)
        }"#);
    start_loop.invoke(&[]);
}

#[no_mangle]
pub fn run_loop(){
    console_log(&format!("{}", random()));
}

注意在 start_loop 函数中,this 实际上引用了一个可以用于执行有用函数(见下文)的上下文对象,并且对于本次演示的重要性,获取 WebAssembly 模块以便我们可以在其上调用回调函数。

将数据放回 WebAssembly 中

让我们专注于最后一个示例。一个按钮,当你点击它时,从公共宝可梦 API 获取数据并将其显示在屏幕上。

use js::*;

fn query_selector(selector: &str) -> ExternRef {
    let query_selector = js!(r#"
        function(selector){
            return document.querySelector(selector);
        }"#);
    query_selector.invoke_and_return_object(&[selector.into()])
}

fn add_click_listener(element: &ExternRef, callback: &str) {
    let add_click_listener = js!(r#"
        function(element, callback){
            element.addEventListener("click", ()=>{
                this.module.instance.exports[callback]();
            });
        }"#);
    add_click_listener.invoke(&[element.into(), callback.into()]);
}

fn element_set_inner_html(element: &ExternRef, html: &str) {
    let set_inner_html = js!(r#"
        function(element, html){
            element.innerHTML = html;
        }"#);
    set_inner_html.invoke(&[element.into(), html.into()]);
}

fn fetch(url: &str, callback: &str) {
    let fetch = js!(r#"
        function(url, callback){
            fetch(url).then((response)=>{
                return response.text();
            }).then((text)=>{
                const allocationId = this.writeUtf8ToMemory(text);
                this.module.instance.exports[callback](text);
            });
        }"#);
    fetch.invoke(&[url.into(), callback.into()]);
}

#[no_mangle]
pub fn main() {
    let button = query_selector("#fetch_button");
    add_click_listener(&button, "button_clicked");
}

#[no_mangle]
pub fn button_clicked() {
    // get pokemon data
    let url = "https://pokeapi.co/api/v2/pokemon/1/";
    fetch(url, "fetch_callback");
}

#[no_mangle]
pub fn fetch_callback(text_allocation_id: usize) {
    let text = extract_string_from_memory(text_allocation_id);
    let result = query_selector("#data_output");
    element_set_inner_html(&result, &text);
}

在处理 fetch 函数时,我们有一个专门用于帮助将字符串放入 WebAssembly 的函数 writeUtf8ToMemory。它返回一个 ID,可以用于在 WebAssembly 端重建字符串 extract_string_from_memory

web

如果你不想重新发明轮子,web 包中有一个不断增长的常用函数集合。

use web::*;

#[no_mangle]
fn main() {
    console_log("Hello world!");
    let body = query_selector("body");
    element_add_click_listener(&body, |e| {
        console_log(format!("Clicked at {}, {}", e.offset_x, e.offset_y).as_str());
    });
    element_add_mouse_move_listener(&body, |e| {
        console_log(format!("Mouse moved to {}, {}", e.offset_x, e.offset_y).as_str());
    });
    element_add_mouse_down_listener(&body, |e| {
        console_log(format!("Mouse down at {}, {}", e.offset_x, e.offset_y).as_str());
    });
    element_add_mouse_up_listener(&body, |e| {
        console_log(format!("Mouse up at {}, {}", e.offset_x, e.offset_y).as_str());
    });
    element_add_key_down_listener(&body, |e| {
        console_log(format!("Key down: {}", e.key_code).as_str());
    });
    element_add_key_up_listener(&body, |e| {
        console_log(format!("Key up: {}", e.key_code).as_str());
    });
}

请参阅此处文档 这里

许可证

本项目根据您的选择,受以下许可证之一约束:

任选其一。

贡献

除非您明确声明,否则您提交给 js-wasm 的任何贡献,根据 Apache-2.0 许可证的定义,都将按照上述方式双许可,不附加任何额外条款或条件。

依赖关系

~170KB