11 releases (4 breaking)

0.5.0 Jul 10, 2021
0.4.2 Apr 29, 2021
0.3.0 Apr 9, 2021
0.2.3 Mar 31, 2021
0.1.1 Feb 18, 2021

#160 in Development tools

Download history 31/week @ 2021-04-05 58/week @ 2021-04-12 19/week @ 2021-04-19 17/week @ 2021-04-26 17/week @ 2021-05-03 4/week @ 2021-05-10 13/week @ 2021-05-17 1/week @ 2021-05-24 23/week @ 2021-05-31 14/week @ 2021-06-07 3/week @ 2021-06-14 9/week @ 2021-06-21 2/week @ 2021-06-28 15/week @ 2021-07-05 9/week @ 2021-07-12 18/week @ 2021-07-19

61 downloads per month

MIT license

440KB
10K SLoC

quickjs_runtime

quickjs_runtime is a library for quickly getting started with embedding a javascript engine in your rust project.

DISCLAIMER: This project is far from what I would call "Battle Tested", use at your own risk.

quickjs_runtime focuses purely on making quickjs easy to use and does not add any additional features, that's where these projects come in:

This project is heavily inspired by the awesome quickjs wrapper at theduke/quickjs-rs and still uses its low level bindings libquickjs-sys.

The big difference to quickjs-rs is that quickjs_runtime executes all quickjs related code in a dedicated single-threaded EventLoop.

Please see the DOCS for all inner workings

This lib serves two main goals:

1. Provide simple utils for working with quickjs (these are located in the quickjs_utils mod)

  • The QuickJsRuntime struct, this is to be used from a single thread
  • E.g. objects::set_property(), functions::invoke_func()
  • Wrap JSValue to provide reference counting (+1 on init, -1 on drop) (JSValueRef)
  • Pass a module loader

2. Wrap quickjs for use as a ready to go JavaScript Runtime

  • This is the EsRuntime struct, it provides an EventQueue which has a thread_local QuickJsRuntime
  • All values are copied or abstracted in an EsValueFacade
  • So no need to worry about Garbage collection
  • evaluate script and invoke functions while waiting for results blocking or with async/await
  • Get Promise result blocking or with async/await

What works?

Script and Modules

  • console (.log/info/debug/trace/error) (docs)
  • Eval script (docs)
  • Create promises in JavaScript which execute async
  • Eval modules (docs)
  • Load modules (dynamic and static) (docs)
  • fetch api (docs)
  • setImmediate
  • setTimeout/Interval (and clear)
  • script preprocessing (impls for ifdef/macro's/typescript can be found in GreenCopperRuntime)

Rust-Script interoperability

  • Return Promises from rust functions and resolve them from rust (docs)
  • Add functions from rust (docs)
  • Invoke JS functions from rust (docs)
  • Pass primitives, objects and arrays from and to rust (docs)
  • Create Classes from rust (docs)
  • async/await support on eval/call_function/promise resolution (docs)
  • import native Modules (e.g. dynamic loading of rust functions or Proxy classes) (docs)

Future / Todo

  • Worker support
  • WebAssembly support

goals

Same goals as https://github.com/HiRoFa/es_runtime but with using quickjs

so

  • slower js

but

  • smaller footprint
  • much faster compilation

For some of my projects those are a big plus!

examples

Here are some quickstarts:

Cargo.toml

[dependencies]
quickjs_runtime = "0.4"
log = "0.4.11"
simple-logging = "2.0.2"

main.rs

use quickjs_runtime::esruntimebuilder::EsRuntimeBuilder;
use hirofa_utils::js_utils::Script
use log::LevelFilter;
use futures::executor::block_on;
use std::sync::Arc;
use quickjs_runtime::esruntime::EsRuntime;

async fn test(rt: Arc<EsRuntime>){
    let res = rt.eval(Script::new(
        "basics.es",
        "7 * 12;",
    )).await.ok().unwrap();

    assert_eq!(res.get_i32(), 84);
}

fn main() {
    simple_logging::log_to_stderr(LevelFilter::Info);

    let rt = EsRuntimeBuilder::new()
        .build();

    block_on(test(rt));

}

invoke a js method from rust

    let res = rt.call_function(vec!["myAppUtils"], "some_func", es_args![8, 7]).await;
    match res {
        Ok(val) => log::info!("8*7 in JavaScript = {}", val.get_i32()),
        Err(e) => log::error!("script failed: {}", e),
    }

add a function from rust and invoke it

    rt.set_function(vec!["nl", "my", "utils"], "methodA", |args| {
        if args.len() != 2 || !args.get(0).unwrap().is_i32() || !args.get(1).unwrap().is_i32() {
            Err(JsError::new_str(
                "i'd really like 2 args of the int32 kind please",
            ))
        } else {
            let a = args[0].get_i32();
            let b = args[1].get_i32();
            log::info!("rust is multiplying {} and {}", a, b);
            Ok((a * b).to_es_value_facade())
        }
    })
    .ok()
    .expect("set_function failed");

    let method_a_res = rt.eval(Script::new(
        "test_func.es",
        "(nl.my.utils.methodA(13, 56));",
    )).await;

    match method_a_res {
        Ok(val) => {
            assert_eq!(val.get_i32(), 13 * 56);
        }
        Err(e) => {
            panic!("test_func.es failed: {}", e);
        }
    }

eval a module

    rt.eval_module(Script::new(
        "my_app.mes",
        "\
    import {foo} from 'example.mes';\
    console.log('static foo is ' + foo);\
    ",
    )).await
    .ok()
    .expect("module failed");

eval a module with a dynamic import

    
    rt.eval_module(Script::new(
        "my_app2.es",
        "\
    import('example.mes')\
    .then((example_module) => {\
        console.log('dynamic foo is ' + example_module.foo);\
    });\
    ",
    )).await
    .ok()
    .expect("script failed");

get a function from js and invoke it in rust

    rt.set_function(vec!["nl", "my", "utils"], "methodB", |mut args| {
        if args.len() != 1 || !args[0].is_function() {
            Err(JsError::new_str(
                "i'd really like 1 arg of the function kind please",
            ))
        } else {
            let consumer_func = args.remove(0);

            // invoke the func async, just because we can
            std::thread::spawn(move || {
                consumer_func
                    .invoke_function_sync(es_args![19, 17])
                    .ok()
                    .expect("func failed");
            });

            Ok(quickjs_es_runtime::esvalue::EsNullValue {}.to_es_value_facade())
        }
    })
    .ok()
    .expect("set_function failed");

    rt.eval(Script::new(
        "test_func2.es",
        "(nl.my.utils.methodB(function(a, b){console.log('consumer was called with ' +a + ', ' + b);}));",
    )).await.ok().expect("test_func2.es failed");

    // wait a sec for the async onvoker to run
    std::thread::sleep(Duration::from_secs(1));

Dependencies

~12MB
~251K SLoC