Deno是一个用Rust和V8构建的新的JavaScript运行时,使你能够在浏览器之外运行JavaScript。Deno比Node.js更安全,因为它默认限制网络和文件系统访问。
Deno的一个很酷的地方是,你可以用Rust编写插件,并在Deno代码中使用它们。在本教程中,我们将向你展示如何在Rust中创建Deno插件。
我们将涵盖以下内容。
为什么要用Rust编写Deno插件?
Deno中的插件通常会提高性能,并提供对更多工具的访问。
由于它们的性能,插件经常被用于计算重型任务,如图像处理。插件还可以让你访问用其他语言编写的各种库,包括高质量的Rust crates。
Deno插件项目结构
插件项目结构与任何Deno模块相同。为了本教程的目的,你可以使用这个锅炉板。
git clone https://github.com/anshulrgoyal/deno-rust-starter.git my_module
首先,为该插件建立Rust模板。
cd my_module/native
cargo build
接下来,运行一个测试来验证Deno是否接收到了正确的库。
cd my_module/native
deno test --unstable --allow-plugin
该模板包括一个位于native 目录下的Rust项目和一个位于根目录下的Deno模块。
构建一个Rust项目
Rust项目编译了一个动态库,由Deno运行时加载。该库的文件类型和名称取决于操作系统。Rust项目可能会编译成一个so 文件--dylib 或dll --而且编译后的文件名称也可能不同。该模板可以处理三个主要平台。Linux、macOS和Windows。
[package]
name = "native"
version = "0.1.0"
authors = ["anshul <anshulgoel151999@gmail.com>"]
edition = "2018"
[lib]
name = "native"
crate-type = ["cdylib"]
[dependencies]
deno_core = "0.75.0"
├── README.md
├── deps.ts
├── mod.ts
├── mod_test.ts
├── native
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── src
│ └── lib.rs
├── test.ts
├── test_deps.ts
└── tsconfig.json
mod.ts 文件是由另一个使用你的模块的应用程序导入的主文件。
添加Rust代码
在本教程中,我们将向你展示如何使用oxipng crate构建一个PNG优化器。每个Deno插件都必须导出deno_plugin_init 函数,并注册该插件导出的所有方法。
#[no_mangle] 属性告诉编译器不要改变函数的名称。
#[no_mangle]
pub fn deno_plugin_init(interface: &mut dyn Interface) {
// register the function. Pass name and function to register method
interface.register_op("hello_world",hello_world);
}
创建优化器函数
每个导出的函数都有相同的签名。Deno插件只能导出函数。这些函数可以是同步的或异步的,取决于返回类型。
fn optimise(_interface: &mut dyn Interface,
zero_copy: &mut [ZeroCopyBuf],
) -> Op {
// get first argument
let first=zero_copy.first().unwrap();
let opts: oxipng::Options = Default::default();
// convert vector
let result = oxipng::optimize_from_memory(&first.to_vec(), &opts).unwrap();
// move to heap so that deno can use it
Op::Sync(Box::from(result))
}
函数的第二个参数包含一个缓冲区的数组。数组中的每个缓冲区代表调用时传递给导出函数的参数。这些缓冲区根据要求被序列化为字符串或其他数据类型。
上面的代码获取了zero_copy 的第一个元素,并将其传递给optimize_from_memory 。数组的第一个元素是在Deno代码中调用时传递给optimize 函数的文件。该文件是以字节形式传递的。该函数处理该文件并将结果作为Box 。返回类型是Op enum,有两个变体sync 和async 。
使用cargo build 命令构建代码。现在这个插件可以在Deno中使用了。
在Deno中加载一个Rust插件
现在这个插件已经编译完成,让我们用Deno来加载它。
这个插件还在开发中,是不稳定的API的一部分,所以需要--unstable 标志,同样也需要--allow-plugin 。
let path = ""
// check the type of OS to load correct file
if (Deno.build.os === "linux") {
// linux file emited by rust compiler
path = "./native/target/debug/libnative.so"
} else if (Deno.build.os === "windows") {
// windows file emited by rust compiler
path = "./native/target/debug/native.dll"
} else if (Deno.build.os === "darwin") {
// macos file emited by rust comipler
path = "./native/target/debug/libnative.dylib"
}
// load plugin from file system
const rid = Deno.openPlugin(path);
// Get available methods on plugin
//@ts-Expect-Error
const { optimise:optimise_native } = (Deno as any).core.ops();
export async function optimise(fileName: string): Promise<Uint8Array> {
// reading a file
const file = await Deno.open(fileName);
// getting content
const value = await Deno.readAll(file)
// closing file
await Deno.close(file.rid)
// running the native plugin method using Deno dispatch method
return (Deno as any).core.dispatch(optimise_native, value)
}
每个插件都是使用openPlugin 方法加载的。然后,使用ops 方法来获得方法标识符,执行插件导出的代码。
dispatch 是用来运行本地插件导出的代码的。第一个参数是方法标识符;其余参数是为本地函数传递的。在这种情况下,文件被传递。
编写异步插件
由于Deno是单线程的,阻断主线程是不明智的。Deno允许你从本地函数中返回一个未来,你可以用OS线程来编写一个不阻塞主线程的函数。
fn optimise_async(_interface: &mut dyn Interface,
zero_copy: &mut [ZeroCopyBuf],
) -> Op {
// get first argument
let first=zero_copy.first().unwrap();
let opts: oxipng::Options = Default::default();
let arg=first.to_vec();
// create a new future
let fut = async move {
// create a channel to send result once done to main thread
let (tx, rx) = futures::channel::oneshot::channel::<oxipng::PngResult<Vec<u8>>>();
// create a new thread
std::thread::spawn(move || {
// perform work
let result = oxipng::optimize_from_memory(&arg, &opts);
// send result to main thread
tx.send(result).unwrap();
});
// receive the result
let result=rx.await;
// create a boxed slice
let result_box = result.unwrap().unwrap().into_boxed_slice();
// return boxed slice from the future
result_box
};
// return the future
Op::Async(fut.boxed())
}
一个未来是使用async 块创建的,并作为一个盒式未来返回。Deno处理未来的完成,并通知插件的Deno方。一个通道被用来在新线程和主线程之间通信。
Deno的代码不需要太多更新--只需要一个新的asyncHandler 来处理任务的完成。
let path = ""
if (Deno.build.os === "linux") {
path = "./native/target/debug/libnative.so"
} else if (Deno.build.os === "windows") {
path = "./native/target/debug/native.dll"
} else if (Deno.build.os === "darwin") {
path = "./native/target/debug/libnative.dylib"
}
const rid = Deno.openPlugin(path);
const { optimise_async } = (Deno as any).core.ops();
export async function optimise(fileName: string){
const file = await Deno.open(fileName);
const value = await Deno.readAll(file);
await Deno.close(file.rid);
// new handler
(Deno as any).core.setAsyncHandler(optimise_async, (response:any) => {
Deno.writeFile("l.png",response)
});
// executing the native code.
(Deno as any).core.dispatch(optimise_async,value);
}
await optimise("t.png")
await Deno.close(rid);
结论
在本教程中,我们介绍了如何使用Rust构建一个简单的Deno插件,以及如何使用Rust futures和deno_core crate创建一个异步插件。
Rust有一个庞大的生态系统,拥有高质量的板条箱。你可以通过创建插件在Deno中使用所有这些板块。无论是图像处理插件、数据库连接器等,对Rust插件的访问有助于扩展Deno生态系统。
The postHow to create a Deno plugin in Rustappeared first onLogRocket Blog.