js_ffi 0.2.1

A FFI library for calling javascript
Documentation

js_ffi

A simple FFI library for invoking javascript functions from web assembly with Rust

  • no code generation or special cargo components
  • callbacks
  • futures
  • typed arrays
  • direct memory access
  • usable with nodejs
  • works with web assembly languages other than Rust
  • fancy js! macro for clean inline javascript

Think of this project like a Rust version of Javascript's <function>.call(<object>,a0,a1,...) but limited by web assembly's function call restrictions.

Hello World!

[dependencies]
js_ffi = "0.2.0"
use js_ffi::*;#[no_mangle]
pub fn main() -> () {
    js!(console.log).invoke_1(TYPE_STRING, to_js_string("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
  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 objected represented by a JSValue, use the appropriate invoke function based on the number of arguments you are passing (call_1,invoke_7,etc.) and make sure your object is the first paramter.
  4. For each argument you are passing specify the type of the argument (TYPE_STRING,TYPE_NUMBER, etc.) and then the argument as a JSValue.
  5. Reuse function handles by putting them in global state if necessary.

Event Listener

let btn = js!(document.querySelector).invoke_1(TYPE_STRING, to_js_string("#button"));
let cb = create_callback_0(||{
    js!(window.alert).invoke_1(TYPE_STRING, to_js_string("I was clicked"));
});
js!(Node.prototype.addEventListener).call_2(btn,TYPE_STRING, to_js_string("click"),TYPE_FUNCTION,cb)

Async Example

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

use js_ffi::*;

async fn run(){
    let console_log = js!(console.log);
    let set_timeout = js!(window.setTimeout);

    console_log.invoke_1(TYPE_STRING,to_js_string("Hello"));

    let (future, id) = CallbackFuture::new();
    set_timeout.invoke_2(TYPE_FUNCTION,id,TYPE_NUM,1000 as JSValue);
    future.await;

    console_log.invoke_1(TYPE_STRING,to_js_string("world!"));
}

#[no_mangle]
pub fn main() -> () {
    executor::spawn(run());
}

Third Party

Wrap third party libraries. Anything function in global space should be able to be wrapped and invoked. You can also specify ad hoc functions.

use js_ffi::*;

#[no_mangle]
fn main() {
    // register functions of things in global scope
    let my_fn = js!((x) => { 
        console.log("say something here too");
        say_loud(x);
    });
    my_fn.invoke_0(TYPE_STRING,"hey");
}
<script src="https://cdn.jsdelivr.net/gh/richardanaya/js_ffi/js_ffi.js"></script>
<script>
    function say_loud(msg){
        window.alert(msg);
    }
</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.