Rust跨平台JS解释器库dioxus-interpreter-js的使用,实现Dioxus框架与JavaScript的高效交互

Rust跨平台JS解释器库dioxus-interpreter-js的使用,实现Dioxus框架与JavaScript的高效交互

概述

dioxus-interpreter-js提供了高性能的JavaScript粘合层,用于解释Dioxus VirtualDom产生的编辑流,并将其转换为实际Web DOM的变更。

这个crate提供了针对web的绑定,并使用sledgehammer来提升性能。

架构

我们使用TypeScript编写绑定,并通过一个非常简单的build.rs配合bun将其转换为JavaScript、压缩并集成到项目中。

并非每个JS片段都会被使用,因此我们将这些片段从核心解释器中分离出来。

理论上,我们可以在浏览器中使用Rust来完成所有这些绑定所做的工作。但实际上,我们希望坚持使用JS以避免在运行LiveView和WebView渲染器时需要WASM构建步骤。我们还希望使用JS来防止在取消事件、上传文件和收集表单输入等操作时出现行为差异。这些细节在两种语言中实现时很难确保1:1的兼容性。

安装

在项目目录中运行以下Cargo命令:

cargo add dioxus-interpreter-js

或者在Cargo.toml中添加以下行:

dioxus-interpreter-js = "0.6.2"

完整示例代码

以下是一个使用dioxus-interpreter-js实现Dioxus与JavaScript交互的完整示例:

use dioxus::prelude::*;
use dioxus_interpreter_js::JsInterpreter;

fn main() {
    // 初始化Dioxus应用
    dioxus::web::launch(App);
}

fn App(cx: Scope) -> Element {
    // 创建JS解释器实例
    let interpreter = JsInterpreter::new(cx);
    
    // 定义一个从JS调用的回调
    let js_callback = move |msg: String| {
        println!("从JS接收到的消息: {}", msg);
    };
    
    // 注册回调到解释器
    interpreter.register_callback("rust_callback", js_callback);
    
    // 渲染UI
    cx.render(rsx! {
        div {
            h1 { "Dioxus与JS交互示例" }
            
            button {
                onclick: move |_| {
                    // 调用JS函数
                    interpreter.call_js("alert", "Hello from Rust!".to_string())
                        .expect("调用JS函数失败");
                },
                "调用JS alert函数"
            }
            
            button {
                onclick: move |_| {
                    // 执行JS代码
                    interpreter.eval("console.log('JS代码执行成功!')")
                        .expect("执行JS代码失败");
                },
                "执行JS代码"
            }
            
            script { "
                // 定义JS端的函数,可以被Rust调用
                function alert(msg) {
                    window.alert(msg);
                }
                
                // 定义JS端的函数来调用Rust回调
                function callRustCallback() {
                    if (window.rust_callback) {
                        window.rust_callback('Hello from JavaScript!');
                    }
                }
            " }
            
            button {
                onclick: move |_| {
                    // 调用JS函数,该函数会回调Rust
                    interpreter.call_js("callRustCallback", ())
                        .expect("调用JS回调函数失败");
                },
                "触发JS回调Rust"
            }
        }
    })
}

扩展示例

以下是一个更完整的示例,展示了更复杂的交互场景:

use dioxus::prelude::*;
use dioxus_interpreter_js::JsInterpreter;
use serde_json::json;

fn main() {
    dioxus::web::launch(AdvancedApp);
}

fn AdvancedApp(cx: Scope) -> Element {
    let interpreter = JsInterpreter::new(cx);
    let count = use_state(cx, || 0);
    
    // 注册多个回调
    interpreter.register_callback("increment", |_: ()| {
        count.with_mut(|c| *c += 1);
    });
    
    interpreter.register_callback("show_data", |data: String| {
        println!("接收到的数据: {}", data);
    });
    
    cx.render(rsx! {
        div {
            h1 { "高级交互示例 - 当前计数: {count}" }
            
            button {
                onclick: move |_| {
                    // 传递复杂JSON数据到JS
                    let data = json!({
                        "name": "Dioxus",
                        "version": "0.3",
                        "features": ["interpreter", "js-binding"]
                    });
                    
                    interpreter.call_js("processData", data.to_string())
                        .expect("调用JS函数失败");
                },
                "发送JSON数据到JS"
            }
            
            button {
                onclick: move |_| {
                    // 从JS获取数据
                    let result = interpreter.eval("getComplexData()")
                        .expect("执行JS代码失败");
                    println!("从JS获取的结果: {:?}", result);
                },
                "从JS获取数据"
            }
            
            script { "
                // 存储数据的JS对象
                const appData = {
                    users: [],
                    config: {
                        theme: 'dark'
                    }
                };
                
                // 处理来自Rust的复杂数据
                function processData(jsonStr) {
                    const data = JSON.parse(jsonStr);
                    console.log('处理数据:', data);
                    if (window.show_data) {
                        window.show_data(`处理完成: ${data.name} v${data.version}`);
                    }
                }
                
                // 返回复杂数据给Rust
                function getComplexData() {
                    return JSON.stringify({
                        timestamp: Date.now(),
                        data: appData
                    });
                }
                
                // 全局函数供外部JS调用
                window.incrementCounter = function() {
                    if (window.increment) {
                        window.increment();
                    }
                };
            " }
            
            button {
                onclick: move |_| {
                    // 调用JS全局函数
                    interpreter.eval("window.incrementCounter()")
                        .expect("执行JS代码失败");
                },
                "通过JS全局函数增加计数"
            }
        }
    })
}

贡献

  • 在我们的问题跟踪器上报告问题
  • 加入discord并提问!

许可证

本项目采用MIT许可证授权。

除非您明确声明,否则您有意为Dioxus提交的任何贡献都应按照MIT许可证授权,无需任何附加条款或条件。


1 回复

Rust跨平台JS解释器库dioxus-interpreter-js的使用指南

简介

dioxus-interpreter-js 是一个为 Dioxus 框架设计的 JavaScript 解释器库,它允许在 Rust 应用中高效地执行 JavaScript 代码并与 Dioxus 组件交互。这个库特别适合需要在 Rust 应用中嵌入 JavaScript 功能,或者在 Dioxus 应用中动态执行 JS 代码的场景。

主要特性

  • 跨平台支持(Windows、macOS、Linux)
  • 与 Dioxus 框架深度集成
  • 高效的内存管理和执行性能
  • 支持 Rust 与 JavaScript 之间的双向数据传递
  • 提供安全的执行环境

安装

在 Cargo.toml 中添加依赖:

[dependencies]
dioxus-interpreter-js = "0.3"
dioxus = { version = "0.4", features = ["desktop"] }

基本使用方法

1. 初始化解释器

use dioxus_interpreter_js::Interpreter;

fn main() {
    let mut interpreter = Interpreter::new().expect("Failed to create interpreter");
    
    // 执行简单的JS代码
    let result = interpreter.eval("2 + 2").expect("Evaluation failed");
    println!("Result: {}", result); // 输出: Result: 4
}

2. 与 Dioxus 组件交互

use dioxus::prelude::*;
use dioxus_interpreter_js::Interpreter;

fn App(cx: Scope) -> Element {
    let interpreter = use_ref(&cx, || Interpreter::new().unwrap());
    
    cx.render(rsx! {
        button {
            onclick: move |_| {
                let result = interpreter.write().eval("Math.random()").unwrap();
                println!("Random number from JS: {}", result);
            },
            "Get Random Number from JS"
        }
    })
}

3. 在 JS 中调用 Rust 函数

use dioxus::prelude::*;
use dioxus_interpreter_js::{Interpreter, JsValue};

fn App(cx: Scope) -> Element {
    let interpreter = use_ref(&cx, || {
        let mut interpreter = Interpreter::new().unwrap();
        
        // 注册Rust函数到JS环境
        interpreter.add_function("rustAdd", |args: Vec<JsValue>| {
            let a: f64 = args[0].as_number().unwrap();
            let b: f64 = args[1].as_number().unwrap();
            Ok(JsValue::Number(a + b))
        }).unwrap();
        
        interpreter
    });
    
    cx.render(rsx! {
        button {
            onclick: move |_| {
                let result = interpreter.write().eval("rustAdd(5, 3)").unwrap();
                println!("5 + 3 = {}", result); // 输出: 5 + 3 = 8
            },
            "Call Rust from JS"
        }
    })
}

高级用法

1. 传递复杂数据结构

use dioxus_interpreter_js::{Interpreter, JsValue};
use serde_json::json;

fn main() {
    let mut interpreter = Interpreter::new().unwrap();
    
    // 创建复杂JS对象
    let user_data = json!({
        "name": "Alice",
        "age": 30,
        "hobbies": ["reading", "coding"]
    });
    
    // 将JSON转换为JS值
    let js_value = JsValue::from_json(&user_data).unwrap();
    
    // 设置全局变量
    interpreter.set_global("userData", js_value).unwrap();
    
    // 在JS中使用这个变量
    let result = interpreter.eval("userData.hobbies.join(', ')").unwrap();
    println!("Hobbies: {}", result); // 输出: Hobbies: reading, coding
}

2. 错误处理

use dioxus_interpreter_js::{Interpreter, JsError};

fn main() {
    let mut interpreter = Interpreter::new().unwrap();
    
    match interpreter.eval("notDefinedFunction()") {
        Ok(result) => println!("Result: {}", result),
        Err(JsError::EvaluationError(e)) => {
            eprintln!("JS evaluation error: {}", e);
        },
        Err(e) => {
            eprintln!("Other error: {:?}", e);
        }
    }
}

3. 性能优化技巧

对于频繁执行的JS代码,可以预编译:

use dioxus_interpreter_js::{Interpreter, JsScript};

fn main() {
    let mut interpreter = Interpreter::new().unwrap();
    
    // 预编译JS代码
    let script = JsScript::compile("function add(a, b) { return a + b; }").unwrap();
    
    // 执行预编译的脚本
    interpreter.execute_script(&script).unwrap();
    
    // 多次调用更高效
    for i in 0..10 {
        let result = interpreter.eval(&format!("add({}, {})", i, i*2)).unwrap();
        println!("{} + {} = {}", i, i*2, result);
    }
}

完整示例Demo

下面是一个结合了基本使用和高级功能的完整示例:

use dioxus::prelude::*;
use dioxus_interpreter_js::{Interpreter, JsValue, JsScript};
use serde_json::json;

fn main() {
    // 初始化Dioxus应用
    dioxus::desktop::launch(App);
}

fn App(cx: Scope) -> Element {
    // 创建解释器实例
    let interpreter = use_ref(&cx, || {
        let mut interpreter = Interpreter::new().unwrap();
        
        // 注册Rust函数
        interpreter.add_function("greet", |args: Vec<JsValue>| {
            let name = args[0].as_string().unwrap();
            Ok(JsValue::String(format!("Hello, {}!", name)))
        }).unwrap();
        
        // 预编译常用函数
        let script = JsScript::compile(
            "function calculateDiscount(price, discount) { return price * (1 - discount); }"
        ).unwrap();
        interpreter.execute_script(&script).unwrap();
        
        interpreter
    });
    
    // 设置全局数据
    use_effect(&cx, (), |_| {
        let mut interpreter = interpreter.write();
        let product_data = json!({
            "name": "Rust Book",
            "price": 39.99,
            "inStock": true
        });
        let js_value = JsValue::from_json(&product_data).unwrap();
        interpreter.set_global("product", js_value).unwrap();
        async move {}
    });
    
    cx.render(rsx! {
        div {
            h1 { "Dioxus JS Interpreter Demo" }
            
            // 调用预编译的JS函数
            button {
                onclick: move |_| {
                    let result = interpreter.write().eval(
                        "calculateDiscount(product.price, 0.2).toFixed(2)"
                    ).unwrap();
                    println!("Discounted price: ${}", result);
                },
                "Calculate 20% Discount"
            }
            
            // 调用注册的Rust函数
            button {
                onclick: move |_| {
                    let result = interpreter.write().eval(
                        "greet(product.name)"
                    ).unwrap();
                    println!("{}", result);
                },
                "Greet Product"
            }
            
            // 错误处理示例
            button {
                onclick: move |_| {
                    match interpreter.write().eval("undefinedFunction()") {
                        Ok(result) => println!("Result: {}", result),
                        Err(e) => eprintln!("Error: {:?}", e),
                    }
                },
                "Trigger Error"
            }
        }
    })
}

注意事项

  1. 解释器实例不是线程安全的,需要在单线程中使用
  2. 大量JS执行可能会影响性能,建议将计算密集型操作放在Rust端
  3. 注意防范注入攻击,不要执行不可信的JS代码
  4. 内存管理需要特别注意,避免循环引用

dioxus-interpreter-js 为 Dioxus 应用提供了强大的 JavaScript 集成能力,使得在 Rust 应用中嵌入动态脚本功能变得简单高效。

回到顶部