js_ffi 0.6.1

A FFI library for calling javascript
Documentation

js_ffi

A foreign function interface(FFI) library for invoking Javascript functions from Web Assembly with Rust

  • no code generation or special cargo components
  • support for callbacks (e.g. setTimeout)
  • futures based on callbacks
  • memory as a parameter
  • works with web assembly languages other than Rust
  • a js! macro for inline javascript
  • typed arrays
  • usable with nodejs

This project has similaries to Javascript's <function>.call(<object>,a0,a1,...) but with the limitations of Web Assembly's function call restrictions.

Hello World!

[dependencies]
js_ffi = "0.6"
use js_ffi::*;#[no_mangle]
pub fn main() -> () {
    js!(console.log).invoke_1("Hello World");
}
<script src="https://cdn.jsdelivr.net/gh/richardanaya/js_ffi/js_ffi.js"></script>
<script>js_ffi.run("example.wasm");</script>

How it works

  1. Get a handle to some Javascript function using the js! macro. Re-use this handle as often as possible.
  2. If you are invoking this function as a regular function, use the appropriate invoke_* function based on the number of arguments you are passing (invoke_1,invoke_7,etc.).
  3. If you are invoking this function as a method of an object represented by a JSValue, use the appropriate call_* function based on the number of arguments you are passing (call_1,invoke_7,etc.) and make sure your object is the first paramter.

Event Listener

use js_ffi::*;

#[no_mangle]
fn main() {
    let btn = js!(document.querySelector).call_1(DOCUMENT, "#button");
    js!(Node.prototype.addEventListener).call_2(
        btn,
        "click",
        create_callback_0(|| {
            js!(window.alert).invoke_1("I was clicked");
        }),
    );
}

Async Example

Using an executor library we can easily turn callbacks into futures and run behavior asynchronously.

use js_ffi::*;

#[no_mangle]
pub fn main() -> () {
    executor::spawn(async {
        let console_log = js!(console.log);
        console_log.invoke_1("Hello");
        sleep(1000).await;
        console_log.invoke_1("world!");
    });
}

fn sleep(millis: u32) -> impl core::future::Future {
    let set_timeout = js!(window.setTimeout);
    let (future, cb) = create_callback_future_0();
    set_timeout.invoke_2(cb, millis);
    future
}

Third Party

Wrap third party libraries. Anything function in global space should be able to be wrapped and invoked.

use js_ffi::*;

#[no_mangle]
fn main() {
    let jquery_handle = js!($);
    let jquery_on_handle = js!(jQuery.prototype.on);
    let alert = js!((msg)=>window.alert(msg));

    let body = jquery_handle.invoke_1("body");
    jquery_on_handle.call_2(
        body,
        "click",
        create_callback_1(move |_event| {
            alert.invoke_1("I was clicked!");
        }),
    );
}
<script src="https://code.jquery.com/jquery-3.4.1.js"></script>
<script src="https://cdn.jsdelivr.net/gh/richardanaya/js_ffi/js_ffi.js"></script>
<script>js_ffi.run("example.wasm");</script>

Don't like Rust?

The script js_ffi.js has nothing Rust specific.

  • Operations execute through an interface specified in this js_ffi.h
  • js_ffi expects an entry point main()
  • If you plan on having your module receive data it must implement jsffimalloc(i32) -> i32
  • If you plan on having your module receive callbacks it must implement jsfficallback(i32,f32,f32,f32,f32,f32,f32,f32,f32,f32,f32)
  • strings are simply c-strings in memory that end in a 0 character.

License

This project is licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in js_ffi by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.