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
每月177次下载
在 12 个crate(10个直接使用) 中使用
21KB
282 行
js-wasm
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对象引用的函数

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());
});
}
请参阅此处文档 这里
许可证
本项目根据您的选择,受以下许可证之一约束:
- Apache License,版本 2.0,(LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT 许可证 (LICENSE-MIT 或 http://opensource.org/licenses/MIT)
任选其一。
贡献
除非您明确声明,否则您提交给 js-wasm
的任何贡献,根据 Apache-2.0 许可证的定义,都将按照上述方式双许可,不附加任何额外条款或条件。
依赖关系
~170KB