插件开发入门
October 8, 2020 · View on GitHub
前言
// TODO
环境
- MacOS
v10.15+- Xcode
v11.4.4+
- Xcode
- Rust
v1.41.0 +
开发插件
例子源码
初始化Rust项目
# 创建目录
mkdir plugin_hello
# 进入目录
cd plugin_hello
# 执行 Rust项目初始化
cargo init --lib
初始化插件设置
配置Rust依赖
在项目目录的Rust ./Cargo.toml 添加开发 Deno 插件的 配置Rust依赖
[dependencies]
futures = "0.3"
deno_core = "0.43"
设置编译类型
[lib]
crate-type = ["cdylib"]
最后完整Cargo.toml结果为
[package]
name = "plugin_hello"
version = "0.1.0"
authors = [""]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
[dependencies]
futures = "0.3"
deno_core = "0.43"
编写Rust插件内容
插件源码
- 在项目的
./src/目录下,新建lib.rs文件。 - 在文件里定义
Deno插件的异步和同步的两个方法
extern crate deno_core;
extern crate futures;
use deno_core::Op;
use deno_core::CoreIsolate;
use deno_core::{Buf, ZeroCopyBuf};
use futures::future::FutureExt;
// 初始化
#[no_mangle]
pub fn deno_plugin_init(isolate: &mut CoreIsolate) {
isolate.register_op("testSync", op_test_sync);
isolate.register_op("testAsync", op_test_async);
}
// 定义插件的同步方法
pub fn op_test_sync(
_isolate: &mut CoreIsolate,
data: &[u8],
zero_copy: Option<ZeroCopyBuf>
) -> Op {
// 解析接收到的两个 参数信息,转换成字符串
let controll = std::str::from_utf8(&data[..]).unwrap().to_string();
let &mut opts;
match zero_copy {
// 如果为空
None => opts = "".to_string(),
// 如果存在
Some(buf) => opts = std::str::from_utf8(&buf[..]).unwrap().to_string(),
}
// 打印接收到的 两个参数
println!("[Rust] op_test_sync:receive (\"{}\", \"{}\")", controll, opts);
// 返回结果
let result = b"ok!";
let result_box: Buf = Box::new(*result);
Op::Sync(result_box)
}
// 定义插件的异步方法
pub fn op_test_async(
_isolate: &mut CoreIsolate,
data: &[u8],
zero_copy: Option<ZeroCopyBuf>,
) -> Op {
// 解析接收到的第一个 参数信息,转换成字符串
let data_str = std::str::from_utf8(&data[..]).unwrap().to_string();
let fut = async move {
// 异步解析第二个参数
if let Some(buf) = zero_copy {
let opts = std::str::from_utf8(&buf[..]).unwrap();
println!("[Rust] op_test_async:receive (\"{}\", \"{}\")", data_str, opts);
}
let (tx, rx) = futures::channel::oneshot::channel::<Result<(), ()>>();
std::thread::spawn(move || {
std::thread::sleep(std::time::Duration::from_secs(1));
// 结束异步操作
tx.send(Ok(())).unwrap();
});
assert!(rx.await.is_ok());
let result = b"test";
let result_box: Buf = Box::new(*result);
result_box
};
Op::Async(fut.boxed())
}
编译插件
在项目目录下执行 Cargo对Rust的编译
cargo build
初次编译会等待很久,因为要下载 deno_core 这个 Rust 依赖。
最后编译成功会显示这样子

插件编译成后,在目录 ./target/debug/ 下会生成一系列在不同系统下的可执行文件。
当前是在MacOS系统下生成的,所以这里要使用 ./target/debug/libplugin_hello.dylib 这个生成结果
libplugin_hello.dylib这个结果命名是lib+{{项目名称}}+{{生成库的后缀}}
编写Deno调用的测试用例
在项目目录 ./tests/ 下建一个 test.js 文件
注意: 由于目前 Deno v1.0.0-rc1 对插件的支持属于 实验阶段,所以对 *.ts 支持不稳定,选用 *.js 的文件格式进行操作
const filenameBase = "libplugin_hello";
let filenameSuffix = ".so";
let filenamePrefix = "lib";
if (Deno.build.os === "windows") {
filenameSuffix = ".dll";
filenamePrefix = "";
}
if (Deno.build.os === "darwin") {
filenamePrefix = "";
filenameSuffix = ".dylib";
}
const filename = `../target/debug/${filenamePrefix}${filenameBase}${filenameSuffix}`;
// 开启插件,并获取进程的 ID
const rid = Deno.openPlugin(filename);
const { testSync, testAsync } = Deno.core.ops();
if (!(testSync > 0)) {
throw "bad op id for testSync";
}
if (!(testAsync > 0)) {
throw "bad op id for testAsync";
}
const textDecoder = new TextDecoder();
const textEncoder = new TextEncoder();
// 执行 调用插件的 同步方法
const response = Deno.core.dispatch(
testSync,
textEncoder.encode('hello'),
textEncoder.encode('sync'),
);
console.log(`[Deno] testSync Response: ${textDecoder.decode(response)}`);
console.log('-------------------------------')
// 执行 调用插件的 异步方法
// 注册异步的回调操作
Deno.core.setAsyncHandler(testAsync, (res) => {
console.log(`[Deno] testAsync Response: ${textDecoder.decode(res)}`);
});
// 触发异步方法事件
Deno.core.dispatch(
testAsync,
textEncoder.encode('test'),
textEncoder.encode('test'),
);
// 关闭 插件调用
Deno.close(rid);
测试结果
在 ./test/ 下执行
deno run --unstable --allow-plugin test.js
会输出一下结果,就说明 Rust 插件调通了
