27 个版本 (7 个破坏性更新)

0.8.3 2024 年 8 月 8 日
0.8.0 2024 年 7 月 29 日
0.3.0 2024 年 1 月 2 日
0.2.1 2023 年 11 月 13 日

#140 in 网页编程

Download history 390/week @ 2024-04-29 86/week @ 2024-05-06 79/week @ 2024-05-13 31/week @ 2024-05-20 22/week @ 2024-05-27 317/week @ 2024-06-03 9/week @ 2024-06-10 21/week @ 2024-06-17 7/week @ 2024-07-01 290/week @ 2024-07-08 443/week @ 2024-07-15 415/week @ 2024-07-22 482/week @ 2024-07-29 326/week @ 2024-08-05 36/week @ 2024-08-12

每月 1,266 次下载
ejs 中使用

MIT/Apache

345KB
5.5K SLoC

Rust 5K SLoC // 0.0% comments JavaScript 716 SLoC // 0.1% comments

为 Rust 提供轻松的 JS 集成

Crates.io Build Status License

此 crate 旨在提供一种快速简单的方式,在 Rust 中集成运行时 JavaScript 或 TypeScript 组件。

它通过 deno_core crate 使用 v8 引擎,旨在尽可能简单易用,同时不牺牲灵活性和性能。

我还尝试抽象化 v8 引擎的细节,以便您大部分情况下可以直接在 Rust 类型上操作。

  • 默认情况下,正在运行的代码完全隔离于主机,没有文件系统或网络访问。
    • 如果需要,可以扩展以包括这些功能和其他功能 - 请参阅 web 特性和 runtime_extensions 示例
  • 支持异步 JS 代码(建议在创建运行时时使用超时选项)
  • 加载的 JS 模块可以导入其他模块
  • 默认支持 TypeScript,并会在执行时将其转换为 JS

以下是如何使用此 crate 执行 JS 模块的一个非常基本的例子。它会

  • 创建一个基本的运行时
  • 加载一个 JavaScript 模块
  • 调用作为入口点的注册函数
  • 返回结果值
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);

还可以使用 Module::loadModule::load_dir 从文件系统中加载模块,如果您想收集给定目录中的所有模块。


如果您只需要单个 JavaScript 表达式的结果,可以使用

let result: i64 = rustyscript::evaluate("5 + 5").expect("The expression was invalid!");

或者仅导入单个模块以供使用

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!");

还包括一些其他实用工具,如 validateresolve_path


以下是一个更详细的 crate 使用说明,它分解了步骤而不是使用一行代码 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!())?;

大多数运行时函数也有 '_async' 和 'immediate' 版本;'_async' 函数返回一个解析到操作结果的 future,而 '_immediate' 函数将不会尝试等待事件循环,因此它们适合用于 crate::js_value::Promise

Rust 函数也可以注册为从 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)?;

异步 JS 可以通过两种方式调用;

第一种是使用 JS 中的 'async' 关键字,然后使用 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);

第二种是使用 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);
  • 有关在 JS 中注册和调用异步 Rust 的信息,请参阅 Runtime::register_async_function
  • 有关使用异步 JS 的更详细示例,请参阅 examples/async_javascript.rs

为了提高调用 Rust 代码的性能,考虑使用扩展而不是模块 - 请参阅 runtime_extensions 示例以获取详细信息


可以使用线程化的工作线程来在单独的线程中运行代码,或者允许多个并发运行时。

worker 模块提供了一个简单的接口来创建和交互工作线程。可以实现 worker::InnerWorker trait 来提供自定义工作线程行为。

它还提供了一个默认的工作线程实现,无需任何额外的设置即可使用。

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(())
}

实用函数

这些函数提供了一种简单的一行代码访问此 crate 常见功能的方法

  • evaluate;评估单个 JS 表达式并返回结果值
  • import;获取一个 JS 模块的句柄,从中可以获取导出的值和函数
  • resolve_path;将相对路径解析为当前工作目录
  • validate;验证 JS 表达式的语法
  • init_platform;为多线程应用程序初始化 V8 平台

Crate 功能

下表列出了此 crate 可用的功能。标记为 Preserves Sandbox: NO 的功能会破坏加载的 JS 模块和宿主系统之间的隔离。请谨慎使用。

有关功能的更多详细信息,请参阅 Cargo.toml

请注意,web 功能还会启用 fs_importurl_import,允许对导入语句进行任意文件系统和网络访问

  • 这是因为 deno_web crate 已经允许进行 fetch 和 FS 读取
功能 描述 Preserves Sandbox 依赖项
cache 实现了 Deno 的 Cache API NO deno_cachedeno_webidldeno_webdeno_cryptodeno_fetchdeno_urldeno_net
控制台 提供来自JS的console.*功能 deno_console
加密 提供来自JS的crypto.*功能 deno_cryptodeno_webidl
URL 从JS内部提供URLURLPattern API deno_webidldeno_url
输入/输出 提供诸如stdio流和文件系统文件抽象等IO原语 NO deno_iorustylinewinapinixlibconce_cell
Web 从JS内部提供EventTextEncoderTextDecoderFile,Web Cryptography和fetch API NO deno_webidldeno_webdeno_cryptodeno_fetchdeno_urldeno_net
Web存储 提供WebStorage API NO deno_webidldeno_webstorage
WebSocket 提供WebSocket API NO deno_webdeno_websocket
WebIDL 提供webidl API deno_webidl
默认 仅提供那些保留沙盒的扩展 deno_consoledeno_cryptodeno_webidldeno_url
无扩展 禁用所有JS运行时的扩展 - 您仍然可以在这种模式下添加自己的扩展
全部 提供所有可用功能 NO deno_consoledeno_webidldeno_webdeno_netdeno_cryptodeno_fetchdeno_url
fs_import 启用通过JS从文件系统导入任意代码 NO
url_import 启用通过JS从网络位置导入任意代码 NO reqwest
worker 启用对线程化worker API的访问 worker
snapshot_builder 启用对SnapshotBuilder的访问,这是一个用于创建可提高启动时间的快照的运行时
web_stub 启用一组不会破坏沙盒的web功能 deno_webidl

有关此crate的使用示例,请参阅Lavendeux

请参阅@Bromeon/js_sandbox,这是该领域另一个优秀的crate

依赖项

~107MB
~2M SLoC