Crate rustyscript
source ·Expand description
This crate is meant to provide a quick and simple way to integrate a runtime javascript or typescript component from within rust.
It uses the v8 engine through the deno_core
crate, and is meant to be as simple as possible to use without sacrificing flexibility or performance.
I also have attempted to abstract away the v8 engine details so you can for the most part operate directly on rust types.
- By default, the code being run is entirely sandboxed from the host, having no filesystem or network access.
- It can be extended to include those capabilities and more if desired - please see the
web
feature, and theruntime_extensions
example
- It can be extended to include those capabilities and more if desired - please see the
- Asynchronous JS code is supported (I suggest using the timeout option when creating your runtime)
- Loaded JS modules can import other modules
- Typescript is supported by default, and will be transpiled into JS for execution
Here is a very basic use of this crate to execute a JS module. It will:
- Create a basic runtime
- Load a javascript module,
- Call a function registered as the entrypoint
- Return the resulting value
use rustyscript::{json_args, Runtime, Module, Error};
let module = Module::new(
"test.js",
"
export default (string, integer) => {
console.log(`Hello world: string=${string}, integer=${integer}`);
return 2;
}
"
);
let value: usize = Runtime::execute_module(
&module, vec![],
Default::default(),
json_args!("test", 5)
)?;
assert_eq!(value, 2);
Modules can also be loaded from the filesystem with Module::load
or Module::load_dir
if you want to collect all modules in a given directory.
If all you need is the result of a single javascript expression, you can use:
let result: i64 = rustyscript::evaluate("5 + 5").expect("The expression was invalid!");
Or to just import a single module for use:
use rustyscript::{json_args, import};
let mut module = import("js/my_module.js").expect("Something went wrong!");
let value: String = module.call("exported_function_name", json_args!()).expect("Could not get a value!");
There are a few other utilities included, such as validate
and resolve_path
A more detailed version of the crate’s usage can be seen below, which breaks down the steps instead of using the one-liner Runtime::execute_module
:
use rustyscript::{json_args, Runtime, RuntimeOptions, Module, Error, Undefined};
use std::time::Duration;
let module = Module::new(
"test.js",
"
let internalValue = 0;
export const load = (value) => internalValue = value;
export const getValue = () => internalValue;
"
);
// Create a new runtime
let mut runtime = Runtime::new(RuntimeOptions {
timeout: Duration::from_millis(50), // Stop execution by force after 50ms
default_entrypoint: Some("load".to_string()), // Run this as the entrypoint function if none is registered
..Default::default()
})?;
// The handle returned is used to get exported functions and values from that module.
// We then call the entrypoint function, but do not need a return value.
//Load can be called multiple times, and modules can import other loaded modules
// Using `import './filename.js'`
let module_handle = runtime.load_module(&module)?;
runtime.call_entrypoint::<Undefined>(&module_handle, json_args!(2))?;
// Functions don't need to be the entrypoint to be callable!
let internal_value: i64 = runtime.call_function(Some(&module_handle), "getValue", json_args!())?;
There are also ‘_async’ and ‘immediate’ versions of most runtime functions;
‘_async’ functions return a future that resolves to the result of the operation, while
‘_immediate’ functions will make no attempt to wait for the event loop, making them suitable
for using crate::js_value::Promise
Rust functions can also be registered to be called from javascript:
use rustyscript::{ Runtime, Module, serde_json::Value };
let module = Module::new("test.js", " rustyscript.functions.foo(); ");
let mut runtime = Runtime::new(Default::default())?;
runtime.register_function("foo", |args| {
if let Some(value) = args.get(0) {
println!("called with: {}", value);
}
Ok(Value::Null)
})?;
runtime.load_module(&module)?;
Asynchronous JS can be called in 2 ways;
The first is to use the ‘async’ keyword in JS, and then call the function using Runtime::call_function_async
use rustyscript::{ Runtime, Module, json_args };
let module = Module::new("test.js", "export async function foo() { return 5; }");
let mut runtime = Runtime::new(Default::default())?;
// The runtime has its own tokio runtime; you can get a handle to it with [Runtime::tokio_runtime]
// You can also build the runtime with your own tokio runtime, see [Runtime::with_tokio_runtime]
let tokio_runtime = runtime.tokio_runtime();
let result: i32 = tokio_runtime.block_on(async {
// Top-level await is supported - we can load modules asynchronously
let handle = runtime.load_module_async(&module).await?;
// Call the function asynchronously
runtime.call_function_async(Some(&handle), "foo", json_args!()).await
})?;
assert_eq!(result, 5);
The second is to use crate::js_value::Promise
use rustyscript::{ Runtime, Module, js_value::Promise, json_args };
let module = Module::new("test.js", "export async function foo() { return 5; }");
let mut runtime = Runtime::new(Default::default())?;
let handle = runtime.load_module(&module)?;
// We call the function without waiting for the event loop to run, or for the promise to resolve
// This way we can store it and wait for it later, without blocking the event loop or borrowing the runtime
let result: Promise<i32> = runtime.call_function_immediate(Some(&handle), "foo", json_args!())?;
// We can then wait for the promise to resolve
// We can do so asynchronously, using [crate::js_value::Promise::into_future]
// But we can also block the current thread:
let result = result.into_value(&mut runtime)?;
assert_eq!(result, 5);
- See
Runtime::register_async_function
for registering and calling async rust from JS - See
examples/async_javascript.rs
for a more detailed example of using async JS
For better performance calling rust code, consider using an extension instead of a module - see the runtime_extensions
example for details
A threaded worker can be used to run code in a separate thread, or to allow multiple concurrent runtimes.
the worker
module provides a simple interface to create and interact with workers.
The worker::InnerWorker
trait can be implemented to provide custom worker behavior.
It also provides a default worker implementation that can be used without any additional setup:
use rustyscript::{Error, worker::{Worker, DefaultWorker, DefaultWorkerOptions}};
use std::time::Duration;
fn main() -> Result<(), Error> {
let worker = DefaultWorker::new(DefaultWorkerOptions {
default_entrypoint: None,
timeout: Duration::from_secs(5),
})?;
let result: i32 = worker.eval("5 + 5".to_string())?;
assert_eq!(result, 10);
Ok(())
}
§Utility Functions
These functions provide simple one-liner access to common features of this crate:
evaluate
; Evaluate a single JS expression and return the resulting valueimport
; Get a handle to a JS module from which you can get exported values and functionsresolve_path
; Resolve a relative path to the current working dirvalidate
; Validate the syntax of a JS expressioninit_platform
; Initialize the V8 platform for multi-threaded applications
§Crate features
The table below lists the available features for this crate. Features marked at Preserves Sandbox: NO
break isolation between loaded JS modules and the host system.
Use with caution.
More details on the features can be found in Cargo.toml
Please note that the web
feature will also enable fs_import
and url_import
, allowing arbitrary filesystem and network access for import statements
- This is because the
deno_web
crate allows both fetch and FS reads already
Feature | Description | Preserves Sandbox | Dependencies |
---|---|---|---|
cache | Implements the Cache API for Deno | NO | deno_cache , deno_webidl , deno_web , deno_crypto , deno_fetch , deno_url , deno_net |
console | Provides console.* functionality from JS | yes | deno_console |
crypto | Provides crypto.* functionality from JS | yes | deno_crypto , deno_webidl |
url | Provides the URL , and URLPattern APIs from within JS | yes | deno_webidl , deno_url |
io | Provides IO primitives such as stdio streams and abstraction over File System files. | NO | deno_io , rustyline , winapi , nix , libc , once_cell |
web | Provides the Event , TextEncoder , TextDecoder , File , Web Cryptography, and fetch APIs from within JS | NO | deno_webidl , deno_web , deno_crypto , deno_fetch , deno_url , deno_net |
webstorage | Provides the WebStorage API | NO | deno_webidl , deno_webstorage |
websocket | Provides the WebSocket API | NO | deno_web , deno_websocket |
webidl | Provides the webidl API | yes | deno_webidl |
default | Provides only those extensions that preserve sandboxing | yes | deno_console , deno_crypto , deno_webidl , deno_url |
no_extensions | Disables all extensions to the JS runtime - you can still add your own extensions in this mode | yes | None |
all | Provides all available functionality | NO | deno_console , deno_webidl , deno_web , deno_net , deno_crypto , deno_fetch , deno_url |
fs_import | Enables importing arbitrary code from the filesystem through JS | NO | None |
url_import | Enables importing arbitrary code from network locations through JS | NO | reqwest |
worker | Enables access to the threaded worker API worker | yes | None |
snapshot_builder | Enables access to [SnapshotBuilder ], a runtime for creating snapshots that can improve start-times | yes | None |
web_stub | Enables a subset of web features that do not break sandboxing | yes | deno_webidl |
For an example of this crate in use, see Lavendeux
Please also check out @Bromeon/js_sandbox, another great crate in this niche
Re-exports§
pub use error::Error;
pub use deno_core;
pub use deno_core::serde_json;
pub use tokio;
Modules§
- Contains the error type for the runtime And some associated utilities
- This module provides a way to store and use javascript values, functions, and promises The are a deserialized version of the
v8::Value
- Module loader implementation for rustyscript This module provides tools for caching module data, resolving module specifiers, and loading modules
- Provides a worker thread that can be used to run javascript code in a separate thread through a channel pair It also provides a default worker implementation that can be used without any additional setup:
Macros§
- A simple helper macro to create a callback for use with
Runtime::register_async_function
Takes care of deserializing arguments and serializing the result - Map a series of values into a form which javascript functions can understand This forms a
Vec<serde_json::Value>
from the provided arguments - Map a series of values into a form which javascript functions can understand Accepts a maximum of 16 arguments, of any combination of compatible types For more than 16 arguments, use
big_json_args!
instead - Creates a static module
- A simple helper macro to create a callback for use with
Runtime::register_function
Takes care of deserializing arguments and serializing the result
Structs§
- Options for configuring extensions
- Represents a pice of javascript for execution.
- Represents a loaded instance of a module within a runtime
- A wrapper type representing a runtime instance loaded with a single module Exactly equivalent to
Runtime::new
followed byRuntime::load_module
- A runtime instance that can be used to execute JavaScript code and interact with it Most runtime functions have 3 variants - blocking, async, and immediate For example:
- A builder for creating a new runtime Just a helper wrapper around
RuntimeOptions
forRuntime
andSnapshotBuilder
- Represents the set of options accepted by the runtime constructor Represents the set of options accepted by the runtime constructor
- A static representation of a module use
.to_module()
to get a module instance to use with a runtime
Traits§
- Represents an async function that can be registered with the runtime
- Represents a function that can be registered with the runtime
Functions§
- Evaluate a piece of non-ECMAScript-module JavaScript code Effects on the global scope will not persist For a persistant variant, see
Runtime::eval
- Imports a JS module into a new runtime
- Explicitly initialize the V8 platform Note that all runtimes must have a common parent thread that initalized the V8 platform
- Resolve a path to absolute path, relative to the current working directory or an optional base directory
- Validates the syntax of some JS
Type Aliases§
- For functions returning nothing. Acts as a placeholder for the return type Should accept any type of value from javascript