4个版本
0.1.3 | 2024年4月16日 |
---|---|
0.1.2 | 2024年1月13日 |
0.1.1 | 2024年1月13日 |
0.1.0 | 2023年11月21日 |
在 过程宏 中排名第 1364
每月下载量 21
160KB
3.5K SLoC
tslink
tslink
将Rust类型表示为 TypeScript
类型。它帮助创建包含所有必要定义和类型的npm包(基于node模块)。
目录
- 属性
- 多个属性
- 结构体到TypeScript类
- 结构体/枚举到TypeScript接口
- 异步方法/函数
- 方法/函数中的回调
- 命名方法/字段
- 绑定数据。参数绑定。
- 绑定数据。结果/错误绑定。
- 异常抑制
- 与node-bindgen一起使用
它如何有用?
Node模块
如果您正在开发基于Rust的Node模块,例如使用 node-bindgen
crate,tslink
将生成一个包含所有必要TypeScript定义的npm包。这有助于Node模块的集成和测试。
共享类型
如果您正在开发例如基于Rust的服务器部分,并且客户端部分在TypeScript中,您可能对将一些Rust类型共享到TypeScript世界感兴趣。请求数据或响应可以表示为 *.ts
文件中的TypeScript定义。
构建
由于tslink生成工件,默认情况下,tslink侧的任何I/O操作都会被跳过。这是因为编译可以由多个原因触发(clippy、rust analyzer等),并且在同一文件范围内会产生不可预测的I/O操作和I/O错误。要允许tslink生成工件,应使用具有任何正值的环境变量 TSLINK_BUILD
(true
、1
、on
)。
export TSLINK_BUILD=true && cargo build
☞ 注意:tslink仅在JavaScript和TypeScript中创建未来节点模块的表示。要创建本地节点模块,可以使用
node-bindgen
。
输出
基于Rust代码tslink
生成
- JavaScript(
*.js
)用于npm包(库) - 类型定义文件(
*.d.ts
) - 可选的TypeScript文件(
*.ts
)与接口
例如,对于npm包tslink
,它会生成
- destination_path
- lib.d.ts # TypeScript definition
- lib.js # Javascript module representation
- package.json # NPM package description
可选地,tslink
可以生成*.ts
文件。这些文件不是npm包的一部分,仅用于在Rust和TypeScript之间“共享”类型。一旦*.ts
文件不是npm包的一部分,应该单独定义其目标路径。
# #[macro_use] extern crate tslink;
# use tslink::tslink;
#[tslink(target = "./target/selftests/interfaces/interfaces.ts")]
struct TestingA {
pub p8: u8,
pub p16: u16,
pub p32: u32,
pub p64: u64,
pub a64: u64,
}
将生成./target/selftests/interfaces/interfaces.ts
,包含
export interface TestingA {
p8: number;
p16: number;
p32: number;
p64: number;
a64: number;
}
结构体
tslint
默认将结构体表示为接口,但也可以表示为class
。如果结构体具有一些方法且这些方法被传播到节点模块中,应使用类表示。
如果将结构体用作类型定义,最好使用接口表示。
对于下一个Rust代码tslink
生成*.js
和*.d.ts
文件。
# #[macro_use] extern crate tslink;
# use tslink::tslink;
# use std::collections::HashMap;
#[tslink(class)]
struct StructureA {
pub p8: u8,
pub p16: u16,
pub p32: u32,
pub p64: u64,
pub a: (u32, u64),
pub b: Vec<u64>,
pub c: HashMap<String, u64>,
pub s: String,
pub k: Option<String>,
}
#[tslink]
impl StructureA {
#[tslink]
pub fn method_a(&self, abs: u8) -> u8 {
0
}
}
TypeScript类型定义(*.d.ts
)表示
export declare class StructureA {
p8: number;
p16: number;
p32: number;
p64: number;
a: [number, number];
b: number[];
c: { [key: string]: number };
s: string;
k: string | null;
method_a(abs: number): number;
}
枚举
扁平枚举将表示为经典的TypeScript枚举
# #[macro_use] extern crate tslink;
# use tslink::tslink;
#[tslink]
enum FlatEnum {
One,
Two,
Three,
Four,
Five,
Six,
Seven,
Nine,
}
在*.d.ts
中
export enum FlatEnum {
One,
Two,
Three,
Four,
Five,
Six,
Seven,
Nine,
}
但任何具有嵌套类型的枚举在TypeScript侧将表示为interface
。
# #[macro_use] extern crate tslink;
# use tslink::tslink;
#[tslink]
enum SomeEnum {
One,
Two,
Three(u8),
Four(u8, u16, u32),
Five((String, String)),
Six(Vec<u8>),
}
在*.d.ts
中
export interface SomeEnum {
One?: null;
Two?: null;
Three?: number;
Four?: [number, number, number];
Five?: [string, string];
Six?: number[];
}
使用方法
属性
属性 | 使用方法 | 描述 | 应用 |
---|---|---|---|
类 |
#[tslink(类)] |
告诉tslink创建TypeScript类而不是interface |
结构体 |
忽略 |
#[tslink(忽略)] |
忽略当前结构体的字段或方法 | 结构体方法 |
忽略= "列表" |
#[tslink(忽略= "field_a; field_b; method_a")] |
要忽略的字段/方法的列表。只能在结构体声明上定义。 | 结构体 |
snake_case_naming |
#[tslink(snake_case_naming)] |
将结构体的字段或方法重命名为snake_case命名(my_field_a 变为myFieldA ) |
结构体方法,函数 |
重命名= "名称" |
#[tslink(重命名= "新的字段或方法名称")] |
将结构体的方法或函数重命名为给定的名称 | 结构体方法和函数 |
构造函数 |
#[tslink(构造函数)] |
将当前方法标记为构造函数。实际上,只能为返回Self 的方法定义。 |
结构体方法返回Self |
目标= "路径" |
#[tslink(目标= "./path_to/file.ts")] |
告诉tslink将TypeScript定义*.ts / *.d.ts 保存到指定的文件 |
结构体,枚举 |
异常抑制 |
#[tslink(异常抑制)] |
默认情况下,如果出错,方法/函数会抛出JavaScript异常。如果使用“异常抑制”,方法/函数将返回JavaScript Error而不是抛出异常 | 结构体方法,函数 |
结果= "json" |
#[tslink(结果= "json")] |
将 Ok 情况转换为 Result<T, _> 的 JSON 格式 |
结构体方法,函数 |
错误= "json" |
#[tslink(错误= "json")] |
将 Err 情况转换为 Result<_, E> 的 JSON 格式 |
结构体方法,函数 |
fn_arg_name= "ref_to_entity" |
#[tslink(data= "MyStruct")] |
在 Rust 端将参数类型与结构体/类型/枚举绑定 | 结构体方法,函数 |
多个属性
可以定义多个属性
# #[macro_use] extern crate tslink;
# use tslink::tslink;
#[tslink(
class,
target = "./target/selftests/interfaces/interfaces.ts; ./target/selftests/interfaces/interfaces.d.ts",
ignore = "_p8;_p16;_p32"
)]
struct MyStruct {
pub _p8: u8,
pub _p16: u16,
pub _p32: u32,
pub _p64: u64,
pub a64: u64,
}
impl MyStruct {
#[tslink(snake_case_naming, exception_suppression)]
fn my_method(&self) -> Result<i32, String> {
Err("test".to_string())
}
}
#[tslink(snake_case_naming, exception_suppression)]
fn my_function() -> Result<i32, String> {
Err("test".to_string())
}
结构体到TypeScript类
要反射 struct
到 TypeScript 类 #[tslink(class)]
,因为默认情况下 tslink 将 struct
表示为 interface
。
如果结构体有特定的构造函数,则应使用 #[tslink(constructor)]
标记该方法。
# #[macro_use] extern crate tslink;
# use tslink::tslink;
#[tslink(class)]
struct MyStruct {
pub field_a: u8,
}
impl MyStruct {
#[tslink(constructor)]
fn new() -> Self {
Self { field_a: 0 }
}
#[tslink]
fn my_method(&self) -> Result<i32, String> {
Err("test".to_string())
}
}
如果 struct
没有字段 #[tslink(class)] 可以直接应用于 impl
。
# #[macro_use] extern crate tslink;
# use tslink::tslink;
struct MyStruct { }
#[tslink(class)]
impl MyStruct {
#[tslink(constructor)]
fn new() -> Self {
Self { }
}
}
☞ 注意:如果您的结构体有构造函数,则必须使用
#[tslink(constructor)]
标记该方法,以便 tslink 在 JS 反射中表示构造函数。
结构体/枚举到TypeScript接口
要反射 struct
或 enum
到 TypeScript interface
,应使用 #[tslink]
。
# #[macro_use] extern crate tslink;
# use tslink::tslink;
#[tslink]
struct MyStruct {
pub field_a: u8,
pub field_b: u8,
pub field_c: u8,
}
#[tslink]
enum MyFlatEnum {
One,
Two,
Three,
}
#[tslink]
enum MyEnum {
One(String),
Two(i32, i32),
Three,
}
注意,“扁平”枚举(MyFlatEnum
)将转换为经典的 TypeScript enum
,但复合枚举(MyEnum
)将转换为 interface
。
异步方法/函数
异步方法/函数的结果将在 TypeScript 端表示为 Promise
。
# #[macro_use] extern crate tslink;
# use tslink::tslink;
struct MyStruct {
}
#[tslink(class)]
impl MyStruct {
#[tslink]
async fn my_async_method(&self) -> i32 {
0
}
}
将表示为
export declare class MyStruct {
my_async_method(): Promise<number>;
}
☞ 注意:使用 promises 抑制 JS 异常没有意义,使用此属性将不会产生影响。
方法/函数中的回调
定义回调的最佳方式是使用泛型类型。
# #[macro_use] extern crate tslink;
# use tslink::tslink;
struct MyStruct {}
#[tslink(class)]
impl MyStruct {
#[tslink]
fn test_a<F: Fn(i32, i32)>(&self, callback: F) {
callback(1, 2);
}
}
将表示为
export declare class MyStruct {
testA(callback: (arg0: number, arg1: number) => void): void;
}
命名方法/字段
TypeScript/JavaScript 命名标准:snake_case 命名。一些 crate,如 node-bindgen
,将根据此规则自动重命名字段和方法。为了适应这种行为,tslink
应该知道哪些字段/方法应该被重命名。
最简单的方法是在方法/字段级别使用 #[tslink(snake_case_naming)]
。或者在非常具体的用例中,可以使用 #[tslink(rename = "newNameOfFieldOrMethod")]
来给方法/字段指定特定的名称。
# #[macro_use] extern crate tslink;
# use tslink::tslink;
#[tslink(class, snake_case_naming)]
struct MyStruct {
field_a: i32,
}
#[tslink(class)]
impl MyStruct {
#[tslink(snake_case_naming)]
fn my_method_a(&self) -> i32 {
0
}
#[tslink(rename = "newNameOfMethod")]
fn my_method_b(&self) -> i32 {
0
}
}
将表示为
export declare class MyStruct {
thisIsFieldA: number;
myMethodA(): number;
newNameOfMethod(): number;
}
☞ 注意:
#[tslink(rename = "CustomName")]
不能用于重命名字段,但可以将snake_case_naming
应用到结构体上的字段。
绑定数据。参数绑定。
在 Rust 中,可以使用一些数据类型将方法/函数参数类型绑定到 #[tslink(data = "MyStruct")]
。
#[macro_use] extern crate tslink;
use serde::{Deserialize, Serialize};
use tslink::tslink;
// Define error type for bound method
#[tslink]
#[derive(Serialize, Deserialize)]
struct MyError {
msg: String,
code: usize,
}
// Make possible convert serde_json error into our error implementation
impl From<serde_json::Error> for MyError {
fn from(value: serde_json::Error) -> Self {
MyError {
msg: value.to_string(),
code: 1,
}
}
}
#[tslink]
#[derive(Serialize, Deserialize)]
struct MyData {
pub a: i32,
pub b: i32,
}
struct MyStruct { }
#[tslink(class)]
impl MyStruct {
#[tslink(
my_data = "MyData",
error = "json",
)]
fn get_data(&self, my_data: String) -> Result<i32, MyError> {
println!("my_data.a = {}", my_data.a);
println!("my_data.b = {}", my_data.b);
Ok(my_data.a + my_data.b)
}}
将表示为
export declare class MyStruct {
getData(my_data: MyData): number;
}
重要
- tslink 将绑定的数据转换为
JSON 字符串
。它需要在您的项目中将serde
和serde_json
作为依赖项。 - 由于解析
JSON 字符串
可能会出错,因此方法/函数应仅返回Result<T, E>
- 因为
serde_json
返回serde_json::Error
错误类型应该可从serde_json::Error
转换。 - 在大多数情况下,您会使用带有
#[tslink(error = "json")]
的数据绑定,因为它允许您使用自己的错误实现。这是一种推荐的方式。 - 在 Rust 端方法/函数的声明中,参数类型应为
String
(例如:fn get_data(&self, my_data: String) -> Result<MyData, MyError>
),但在方法/函数的主体中,此参数将被视为绑定的类型。 - 绑定的类型和错误应该实现
Serialize
和Deserialize
绑定数据。结果/错误绑定。
要绑定错误与您的自定义类型之一,应使用 #[tslink(error = "json")]
,就像在 "绑定数据. 参数绑定" 中显示的那样。例如,错误参数将序列化为 Rust 级别的 JSON 字符串
,并在 TypeScript/JavaScript 级别解析 JSON 字符串
。
要将结果与自定义数据类型绑定,应使用以下代码:#[tslink(result = "json")]
。
#[macro_use] extern crate tslink;
use serde::{Deserialize, Serialize};
use tslink::tslink;
// Define error type for bound method
#[tslink]
#[derive(Serialize, Deserialize)]
struct MyError {
msg: String,
code: usize,
}
// Make possible convert serde_json error into our error implementation
impl From<serde_json::Error> for MyError {
fn from(value: serde_json::Error) -> Self {
MyError {
msg: value.to_string(),
code: 1,
}
}
}
#[tslink]
#[derive(Serialize, Deserialize)]
struct MyData {
pub a: i32,
pub b: i32,
pub c: String,
}
struct MyStruct { }
#[tslink(class)]
impl MyStruct {
#[tslink(
my_data = "MyData",
result = "json",
error = "json",
)]
fn get_data(&self, my_data: String) -> Result<MyData, MyError> {
Ok(MyData {
a: my_data.a + 1,
b: my_data.b + 1,
c: format!("{}{}", my_data.c, my_data.c),
})
}}
将表示为
export declare class MyStruct {
getData(my_data: MyData): MyData;
}
重要
- tslink 将绑定的数据转换为
JSON 字符串
。它需要在您的项目中将serde
和serde_json
作为依赖项。 - 由于解析
JSON 字符串
可能会出错,因此方法/函数应仅返回Result<T, E>
- 因为
serde_json
返回serde_json::Error
错误类型应该可从serde_json::Error
转换。 - 在大多数情况下,您会使用带有
#[tslink(error = "json")]
的数据绑定,因为它允许您使用自己的错误实现。这是一种推荐的方式。 - 在 Rust 端方法/函数的声明中,参数类型应为
String
(例如:fn get_data(&self, my_data: String) -> Result<MyData, MyError>
),但在方法/函数的主体中,此参数将被视为绑定的类型。 - 结果类型和错误实现应使用
Serialize
和Deserialize
。
异常抑制
抛出异常或没有异常取决于创建节点模块使用的库/crate。例如,node-bindgen
在方法/函数完成时遇到错误就会在JavaScript级别抛出异常。但tslink允许自定义此场景。
默认情况下,异常抑制是关闭的,Rust级别的任何错误都会在JavaScript级别成为异常。
让我们看看前面的示例。
# #[macro_use] extern crate tslink;
# use serde::{Deserialize, Serialize};
# use tslink::tslink;
# #[tslink]
# #[derive(Serialize, Deserialize)]
# struct MyError {
# msg: String,
# code: usize,
# }
# // Make possible convert serde_json error into our error implementation
# impl From<serde_json::Error> for MyError {
# fn from(value: serde_json::Error) -> Self {
# MyError {
# msg: value.to_string(),
# code: 1,
# }
# }
# }
struct MyStruct { }
#[tslink(class)]
impl MyStruct {
#[tslink(
error = "json",
)]
fn get_data(&self, my_data: String) -> Result<i32, MyError> {
Err(MyError { msg: "Test".to_string(), code: 1})
}}
将表示为
export declare class MyStruct {
getData(my_data: MyData): number;
}
方法 getData
返回 MyData
,但如果发生错误,将抛出JavaScript异常。
使用 #[tslink(exception_suppression)]
我们可以改变它。
# #[macro_use] extern crate tslink;
# use serde::{Deserialize, Serialize};
# use tslink::tslink;
# #[tslink]
# #[derive(Serialize, Deserialize)]
# struct MyError {
# msg: String,
# code: usize,
# }
# // Make possible convert serde_json error into our error implementation
# impl From<serde_json::Error> for MyError {
# fn from(value: serde_json::Error) -> Self {
# MyError {
# msg: value.to_string(),
# code: 1,
# }
# }
# }
struct MyStruct { }
#[tslink(class)]
impl MyStruct {
#[tslink(
error = "json",
exception_suppression
)]
fn get_data(&self, my_data: String) -> Result<i32, MyError> {
Err(MyError { msg: "Test".to_string(), code: 1})
}}
将表示为
export declare class MyStruct {
getData(my_data: MyData): number | (Error & { err?: MyError});
}
现在 getData
返回或 number
,或错误时返回 Error & { err?: MyError}
,但异常被抑制。
是否使用此功能取决于开发者,但通常这是一种减少JavaScript/TypeScript侧的try/catch块并准备好可能发生错误的地方的好方法。
与node-bindgen一起使用
node-bindgen
crate 允许创建原生节点模块,并通过tslink获得完整的npm项目。
通用用法中只有一个规则——调用 #[tslink]
应始终在调用 #[node_bindgen]
之前。
#[macro_use] extern crate tslink;
use tslink::tslink;
use node_bindgen::derive::node_bindgen;
struct MyScruct {}
#[tslink(class)]
#[node_bindgen]
impl MyScruct {
#[tslink(constructor)]
#[node_bindgen(constructor)]
pub fn new() -> Self {
Self {}
}
#[tslink(snake_case_naming)]
#[node_bindgen]
fn inc_my_number(&self, a: i32) -> i32 {
a + 1
}
}
请注意,node-bindgen
默认将方法名称转换为蛇形命名。您应使用 #[tslink(snake_case_naming)]
来考虑这一点。
默认情况下,node-bindgen
在 root
的 ./dist
文件夹中创建 index.node
。在 Cargo.toml
文件中应在 [tslink]
节中定义适当的路径。
文件:./Cargo.toml
(项目根目录)
[project]
...
[lib]
...
[tslink]
node = "./dist/index.node"
[dependencies]
...
有关 node-bindgen
的完整示例,请参阅此处。要启动它
git clone https://github.com/icsmw/tslink.git
cd tslink/examples/node_bindgen
sh ./run_test.sh
配置
tslink的全局配置可以在项目根目录下的Cargo.toml文件的[tslink]部分定义。在大多数情况下,这是必需的。此设置允许定义一个指向本地Node模块的路径,该模块将与npm包绑定。
但如果tslink仅用于在*.ts
文件中生成接口,则可以跳过配置文件。
./Cargo.toml中的tslink设置示例
[package]
name = "tslink-test"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
path = "rs/lib.rs"
[tslink]
# [required] path to native node module
node = "./dist/index.node"
# [optional] global rule of renaming (can be: "method" or "fields" or both - "methods,fields")
snake_case_naming = "methods"
# [optional] global rule for javascript exception suppression
exception_suppression = true
字段 | 必需 | 值 | 描述 |
---|---|---|---|
node= "path_to_native_node_module" |
yes | 文件路径 | 本地Node模块路径 |
snake_case_naming= "规则" |
"methods ", "fields "或"methods,fields " |
全局重命名规则 | |
异常抑制= true |
布尔值 |
全局JavaScript异常抑制规则 |
QA和故障排除
问题:tslink没有创建任何文件
答案:确保环境变量
TSLINK_BUILD
已导出,值为true
或1
问题:rust-analyzer报告tslink的IO错误
答案:删除环境变量
TSLINK_BUILD
或将它设置为false
或0
问题:什么是
./target/selftests
?答案:这些是tslink使用
cargo test
创建的工件。可以安全删除。
问题:tslink创建本地Node模块(如
index.node
)吗?问题:不,tslink仅创建未来Node模块在JavaScript和TypeScript中的表示。要创建本地Node模块,可以使用crate
node-bindgen
。
问题:使用
node-bindgen
时,在JavaScript端出现错误,例如"no method_call_b() on undefined"。问题:注意,
node-bindgen
默认将方法名应用为蛇形命名。您应该使用#[tslink(snake_case_naming)]
来考虑这一点。
依赖项
~1.3–2MB
~39K SLoC