Call Javascript from Rust
In the previous article, we discussed how to call Rust functions from
Javascript. How about the other way around? The nodejs-helper
crate
enables Rust functions to call Javascript functions in Node.js.
To see the complete source code and run it in Node.js, checkout here.
In your project's Cargo.toml
, add dependency for the nodejs-helper
and wasm-bindgen
crates.
[dependencies]
wasm-bindgen = "=0.2.61"
nodejs-helper = "0.0.3"
Console and time
Recall that WebAssembly is a very simple and standalone virtual machine. It has no access the operating system's standard input / output, as well as features such as the system clock. In our Rust function, we can rely on the Javascript functions in Node.js to access those festures.
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn show_now() {
nodejs_helper::console::log("Timestamp now: ");
nodejs_helper::console::log(&nodejs_helper::date::timestamp());
}
#[wasm_bindgen]
pub fn utc_now() {
nodejs_helper::console::log("UTC time: ");
nodejs_helper::console::log(&nodejs_helper::date::utc_string());
}
#[wasm_bindgen]
pub fn my_time(tz: &str) {
nodejs_helper::console::log(tz);
nodejs_helper::console::log(&nodejs_helper::date::format_date("en-US", "long", "numeric", "long", "numeric", tz, "short"));
}
Build the wasm
bytecode application and its Javascript helper files via ssvmup
.
The generated files are in the pkg
directory.
$ ssvmup build
In a Javascript application, you can load the generated Javascript file, export the Rust function, and call it.
const { show_now, utc_now, my_time } = require('pkg/nodejs_example.js');
show_now();
utc_now();
my_time("America/Chicago");
You can now run the Javascript application from Node.js command line.
$ node date.js
Timestamp now:
1588013800826
UTC time:
Mon, 27 Apr 2020 18:56:40 GMT
America/Chicago
Monday, April 27, 2020, CDT
File system access
The Rust functions in this section read an image file from the local file system, resize it, and write back to the file system. It also uses the Javascript console tool to measure the time spent on each task.
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wasm-bindgen = "=0.2.61"
image = "0.23.0"
The Rust function takes a JSON string input that contains the image file names and resized dimensions.
use wasm_bindgen::prelude::*;
#[derive(Serialize, Deserialize)]
#[derive(Copy, Clone, Debug)]
pub struct Dimension {
pub width: u32,
pub height: u32,
}
#[derive(Serialize, Deserialize)]
pub struct Picture {
pub dim: Dimension,
pub raw: Vec<u8>,
}
#[wasm_bindgen]
pub fn resize_file(input: &str) {
// Use JSON to pass multiple call arguments
let p: (Dimension, String, String) = serde_json::from_str(input).unwrap();
nodejs_helper::console::time("Resize file");
let raw = nodejs_helper::fs::read_file_sync(&p.1);
nodejs_helper::console::time_log("Resize file", "Done reading");
let src = Picture {
dim: p.0,
raw: raw,
};
let target = resize_impl(&src);
nodejs_helper::console::time_log("Resize file", "Done resizing");
nodejs_helper::fs::write_file_sync(&p.2, &target.raw);
nodejs_helper::console::time_log("Resize file", "Done writing");
nodejs_helper::console::time_end("Resize file");
}
pub fn resize_impl(src: &Picture) -> Picture {
// ... use the img crate to resize ...
}
Build and create the pkg
folder for the compiled wasm
bytecode file and the Javascript shim file my_project_name.js
.
$ ssvmup build
The Javascript calling program in Node.js looks like the following.
const { resize_file } = require('../pkg/nodejs_example.js');
const dim = {
width: 100,
height: 100
};
resize_file(JSON.stringify([dim, 'cat.png', `test.png`]));
Run the Node.js app shows the following result.
$ node image.js
Resize file: 5.603ms Done reading
Resize file: 1506.694ms Done resizing
Resize file: 1507.634ms Done writing
Resize file: 1507.977ms
Sqlite database access
First, you must have the better-sqlite3
module installed in
your Node.js setup. The Rust function will access a sqlite database through this module.
$ npm i better-sqlite3
The Rust functions to create, update, and query a Sqlite database on the local file system are as follows.
use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct User {
pub id: u32,
pub full_name: String,
pub created: String,
}
#[wasm_bindgen]
pub fn create_sqlite(path: &str) {
let sql_create = "
CREATE TABLE users (
id INTEGER PRIMARY KEY NOT NULL,
full_name TEXT NOT NULL,
created DATE NOT NULL
);";
let sql_insert = "
INSERT INTO users
VALUES
(1, 'Bob McFett', '32-01-01'),
(2, 'Angus Vader', '02-03-04'),
(3, 'Imperator Colin', '01-01-01');";
nodejs_helper::sqlite3::create(path);
nodejs_helper::sqlite3::update(path, sql_create);
nodejs_helper::sqlite3::update(path, sql_insert);
}
#[wasm_bindgen]
pub fn query_sqlite(path: &str) {
let sql_query = "SELECT * FROM users;";
let rows: String = nodejs_helper::sqlite3::query(path, sql_query);
let users: Vec<User> = serde_json::from_str(&rows).unwrap();
for user in users.into_iter() {
nodejs_helper::console::log(&(user.id.to_string() + " : " + &user.full_name));
}
}
Build and create the pkg
folder for the compiled wasm
bytecode file and the Javascript shim file my_project_name.js
.
$ ssvmup build
The Javascript calling program in Node.js looks like the following.
const { create_sqlite, query_sqlite } = require('pkg/nodejs_example.js');
create_sqlite("test.sqlite");
query_sqlite("test.sqlite");
Run the Node.js app shows the following result.
$ node db.js
1 : Bob McFett
2 : Angus Vader
3 : Imperator Colin
HTTP request
First, you must have the sync-request
module installed in
your Node.js setup. The Rust function will make synchronous HTTP requests through this module.
$ npm i sync-request
The Rust functions to access web services via HTTP/HTTPS and then save content on the local file system are as follows.
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn fetch(url: &str) {
let content = nodejs_helper::request::fetch_as_string(url);
nodejs_helper::console::log(url);
nodejs_helper::console::log(&content);
}
#[wasm_bindgen]
pub fn download(url: &str, path: &str) {
let content = nodejs_helper::request::fetch(url);
nodejs_helper::fs::write_file_sync(path, &content);
}
Build and create the pkg
folder for the compiled wasm
bytecode file and the Javascript shim file my_project_name.js
.
$ ssvmup build
The Javascript calling program in Node.js looks like the following.
const { fetch, download } = require('../pkg/nodejs_example.js');
fetch("https://raw.githubusercontent.com/second-state/nodejs-helper/master/LICENSE");
download("https://www.secondstate.io/", "test.html");
Run the Node.js app shows the following result.
$ node http.js
https://raw.githubusercontent.com/second-state/nodejs-helper/master/LICENSE
MIT License
Copyright (c) 2020 Second State
Permission is hereby granted, free of charge, to any person obtaining a copy
... ...