#quickjs #javascript #runtime #async #engine

quickjs_runtime

Wrapper API and utils for the QuickJS JavaScript engine with support for Promise, Module, Async/await

23 releases

0.8.6 Nov 7, 2022
0.8.3 Aug 18, 2022
0.8.1 Jul 21, 2022
0.8.0 Feb 28, 2022
0.2.3 Mar 31, 2021

#175 in Development tools

Download history 42/week @ 2022-08-12 93/week @ 2022-08-19 80/week @ 2022-08-26 99/week @ 2022-09-02 136/week @ 2022-09-09 72/week @ 2022-09-16 69/week @ 2022-09-23 195/week @ 2022-09-30 166/week @ 2022-10-07 26/week @ 2022-10-14 50/week @ 2022-10-21 41/week @ 2022-10-28 101/week @ 2022-11-04 74/week @ 2022-11-11 52/week @ 2022-11-18 30/week @ 2022-11-25

265 downloads per month

MIT license

515KB
11K 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 not yet what I would call "Battle Tested", use at your own risk.

An example on how to embed a script engine in rust using this lib can be found here: github.com/andrieshiemstra/ScriptExtensionLayerExample. It was published in TWIR as a walkthrough.

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 inspired by the 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 (moved to GreenCopperRuntime)
  • 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]
hirofa_utils = "0.5"
quickjs_runtime = "0.8"
log = "0.4"
simple-logging = "2.0"
use crate::builder::QuickJsRuntimeBuilder;
    use crate::facades::QuickJsRuntimeFacade;
    use crate::quickjsrealmadapter::QuickJsRealmAdapter;
    use futures::executor::block_on;
    use hirofa_utils::js_utils::adapters::proxies::JsProxy;
    use hirofa_utils::js_utils::adapters::JsRealmAdapter;
    use hirofa_utils::js_utils::facades::values::{JsValueConvertable, JsValueFacade};
    use hirofa_utils::js_utils::facades::{JsRuntimeBuilder, JsRuntimeFacade};
    use hirofa_utils::js_utils::{JsError, Script};
    use log::LevelFilter;
    use std::time::Duration;

    #[test]
    fn test_examples() {
        let rt = QuickJsRuntimeBuilder::new().js_build();
        let outcome = block_on(run_examples(&rt));
        if outcome.is_err() {
            log::error!("an error occured: {}", outcome.err().unwrap());
        }
        log::info!("done");
    }

    async fn take_long() -> i32 {
        std::thread::sleep(Duration::from_millis(500));
        537
    }

    async fn run_examples(rt: &QuickJsRuntimeFacade) -> Result<(), JsError> {
        // ensure console.log calls get outputted
        simple_logging::log_to_stderr(LevelFilter::Info);

        // do a simple eval on the main realm
        let eval_res = rt
            .js_eval(None, Script::new("simple_eval.js", "2*7;"))
            .await?;
        log::info!("simple eval:{}", eval_res.get_i32());

        // invoke a JS method from rust

        let meth_res = rt
            .js_function_invoke(None, &["Math"], "round", vec![12.321.to_js_value_facade()])
            .await?;
        log::info!("Math.round(12.321) = {}", meth_res.get_i32());

        // add a rust function to js as a callback

        let cb = JsValueFacade::new_callback(|args| {
            let a = args[0].get_i32();
            let b = args[1].get_i32();
            log::info!("rust cb was called with a:{} and b:{}", a, b);
            Ok(JsValueFacade::Null)
        });
        rt.js_function_invoke(
            None,
            &[],
            "setTimeout",
            vec![
                cb,
                10.to_js_value_facade(),
                12.to_js_value_facade(),
                13.to_js_value_facade(),
            ],
        )
        .await?;
        std::thread::sleep(Duration::from_millis(20));
        log::info!("rust cb should have been called by now");

        // create simple proxy class with an async function
        rt.js_loop_realm_sync(None, |_rt_adapter, realm_adapter| {
            let proxy = JsProxy::new(&["com", "mystuff"], "MyProxy").add_static_method(
                "doSomething",
                |_rt_adapter, realm_adapter: &QuickJsRealmAdapter, _args| {
                    realm_adapter.js_promise_create_resolving_async(
                        async { Ok(take_long().await) },
                        |realm_adapter, producer_result| {
                            realm_adapter.js_i32_create(producer_result)
                        },
                    )
                },
            );
            realm_adapter
                .js_proxy_install(proxy, true)
                .ok()
                .expect("could not install proxy");
        });

        rt.js_eval(
            None,
            Script::new(
                "testMyProxy.js",
                "async function a() {\
                            console.log('a called at %s ms', new Date().getTime());\
                            let res = await com.mystuff.MyProxy.doSomething();\
                            console.log('a got result %s at %s ms', res, new Date().getTime());\
                           }; a();",
            ),
        )
        .await?;
        std::thread::sleep(Duration::from_millis(600));
        log::info!("a should have been called by now");

        Ok(())
    }

Dependencies

~11–17MB
~334K SLoC