Embed JavaScript in Rust

• 5 minutes to read

WasmEdge brings together Rust’s performance and JavaScript’s ease of use

In my previous article, I discussed how to run JavaScript programs in a WebAssembly sandbox. The WasmEdge runtime provides a lightweight, high-performance, and OCI compliant “container” for cloud-native JavaScript applications.

However, JavaScript is a “slow” language. The reason to use JavaScript is mostly due to its ease of use and developer productivity, especially for beginner developers. WebAssembly, on the other hand, is capable of running high-performance applications written in languages such as Rust. Is there a solution that combines JavaScript and Rust? WasmEdge offers the best of both worlds.

With WasmEdge and QuickJS, developers can now mix and match those two powerful languages to create applications. It works like this.

  • The application itself is written in Rust and compiled into WebAssembly. It can be compiled and deployed as a single wasm bytecode file. It can be managed by OCI-compliant container tools like Docker Hub, CRI-O and k8s.
  • JavaScript programs are embedded inside the Rust application. The JavaScript source code could be included at compile-time, or at runtime through a file, STDIN, a network request, or even a function call. That allows the JavaScript program to be written by a different developer than the Rust program.
  • The Rust program could handle computationally intensive tasks in the application, while the JavaScript program could handle the “business logic”. For example, the Rust program could prepare data for the JavaScript program.

Next, let’s look into several examples. All examples are in the embed_in_rust branch of the wasmedge-quickjs Github repo.

$ git clone [https://github.com/second-state/wasmedge-quickjs](https://github.com/second-state/wasmedge-quickjs/tree/embed_in_rust)
$ cd wasmedge-quickjs
$ git checkout embed_in_rust

You must have Rust and WasmEdge installed to build and run the examples in this article.

Hello WasmEdge

The following Rust program in main.rs embeds a JavaScript program at compile-time.

pub mod quickjs_sys;

... ...

fn main() {
    use quickjs_sys as q;
    let mut ctx = q::Context::new();
    // include js code
    let code = include_str!("../example_js/demo.js");
    // get args and set into quickjs
    let mut res_args = args_parse();
    res_args.insert(0, "<embedded_no_filename>".to_string());
    ctx.put_args(res_args);
    // run js code
    ctx.eval_str(code, "");
}

The example_js/demo.js code is as follows.

import * as std from 'std';

print('hello')
print('args:',...args)

//write fs
let wf = std.open('demo.txt','w')
wf.puts('hello quickjs')
wf.close()

//read fs
let rf = std.open('demo.txt','r')
let r = rf.getline()
print(r)
rf.close()

You can build the Rust + JavaScript application into a single WebAssembly bytecode program.

$ cargo build --target wasm32-wasi --release

Run it and see the results printed to the console.

$ cd example_js
$ wasmedge --dir .:. ../target/wasm32-wasi/release/quickjs-rs-wasi.wasm

That’s it. The Rust code could pass data into the JavaScript code by passing arguments or even modifying the included JavaScript source code directly. The JavaScript code could also return values by writing to a temp file.

Use embedded JavaScript to classify an image

You can modify the Rust program in main.rs to embed a different JavaScript program. In the example below, let’s change to

pub mod quickjs_sys;

... ...

fn main() {
    ... ...
    let code = include_str!("../example_js/tensorflow_lite_demo/main.js");
    ... ...
    ctx.eval_str(code, "");
}

The main.js code uses the WasmEdge Tensorflow extension’s JavaScript API to read and classify an image using the ImageNet model.

import {TensorflowLiteSession} from 'tensorflow_lite'
import {Image} from 'image'

let img = new Image('./example_js/tensorflow_lite_demo/food.jpg')
let img_rgb = img.to_rgb().resize(192,192)
let rgb_pix = img_rgb.pixels()

let session = new TensorflowLiteSession('./example_js/tensorflow_lite_demo/lite-model_aiy_vision_classifier_food_V1_1.tflite')
session.add_input('input',rgb_pix)
session.run()
let output = session.get_output('MobilenetV1/Predictions/Softmax');
let output_view = new Uint8Array(output)
let max = 0;
let max_idx = 0;
for (var i in output_view){
    let v = output_view[i]
    if(v>max){
        max = v;
        max_idx = i;
    }
}
print('label index:',max_idx,'\nconfidence:',max/255)

You can build the Rust + JavaScript application into a single WebAssembly bytecode program. Notice the --features=tensorflow flag, which asks the Rust compiler to use the WasmEdge TensorFlow extension API.

$ cargo build --target wasm32-wasi --release --features=tensorflow

Run it and see the results printed to the console.

$ cd example_js/tensorflow_lite_demo
$ wasmedge --dir .:. ../../target/wasm32-wasi/release/quickjs-rs-wasi.wasm

Make it run faster

The above Tensorflow inference example takes 1–2 seconds to run. It is acceptable in web application scenarios but could be improved. Recall that WasmEdge is the fastest WebAssembly runtime today due to its AOT (Ahead-of-time compiler) optimization. WasmEdge provides a wasmedgec utility to compile the wasm file to a native so shared library. You can use wasmedge to run the so file instead of wasm file to get much faster performance.

The following example uses the extended versions to wasmedge and wasmedgec to support the WasmEdge Tensorflow extension.

cd example_js/tensorflow_lite_demo
$ wasmedgec-tensorflow ../../target/wasm32-wasi/release/quickjs-rs-wasi.wasm quickjs-rs-wasi.so
$ wasmedge-tensorflow-lite --dir .:. quickjs-rs-wasi.so

You can see that the image classification task can be completed within 0.1s. It is at least 10x improvement!

The so shared library is not portable across machines and OSes. You should run wasmedgec and wasmedgec-tensorflow on the machine you deploy and run the application.

What’s next

Embedding JavaScript in Rust is a powerful way to create high-performance cloud-native applications. In the next article, we will look into another approach: embed native libraries in JavaScript.

JavaScript in cloud-native WebAssembly is still an emerging area in the next generation of cloud and edge computing infrastructure. We are just getting started! If you are interested, join us in the WasmEdge project (or tell us what you want by raising feature request issues).

ProductWasmEdgeJavaScriptRust
A high-performance, extensible, and hardware optimized WebAssembly Virtual Machine for automotive, cloud, AI, and blockchain applications