Generate wasm-bindgen Rust bindings from TypeScript .d.ts declaration files.
ts-gen reads TypeScript .d.ts files and produces Rust source code containing #[wasm_bindgen] extern blocks, ready to compile against wasm-bindgen.
.d.ts files
-> oxc_parser (AST)
-> First Pass phase 1: collect all type names into TypeRegistry
-> First Pass phase 2: populate full IR (resolve references, merge var+interface patterns)
-> Assembly: group by ModuleContext, resolve inheritance chains, classify types
-> Code Generation: IR -> syn::File -> prettyplease -> .rs files
cargo install ts-gencargo add ts-gen --no-default-featuresThe cli feature (enabled by default) pulls in clap and walkdir for the
binary. Disable it if you only need the library API.
ts-gen --input types.d.ts --output src/bindings.rsProcess a directory of declaration files:
ts-gen --input ./typings/ --output src/bindings.rs --lib-name my-js-libMap external types from other crates:
ts-gen --input types.d.ts --output src/bindings.rs \
--external "Blob=::web_sys::Blob" \
--external "node:buffer=node_buffer_sys" \
--external "Intl.DateTimeFormatOptions=::my_intl::Options"Route untyped JS exceptions into your own error type (must implement
From<JsValue>):
ts-gen --input types.d.ts --output src/bindings.rs --error-type crate::ErrorKeep @throws {A | B} unions as a generated JsException enum instead of
collapsing them to their common ancestor:
ts-gen --input types.d.ts --output src/bindings.rs --error-enumSelect the output surface. --input defines the type universe; --export
picks what is emitted: a file (its global declarations), a JS module
specifier (its declarations, in the module's pub mod), or a declaration name
(that export of any file or module, plus the types its bindings reference):
ts-gen --input node_modules/@cloudflare/workers-types/index.d.ts --output src/bindings.rs \
--export Request --export Response --export KVNamespace --export cloudflare:workersOpt generic imports into wasm-bindgen's experimental per-monomorphization codegen:
ts-gen --input types.d.ts --output src/bindings.rs --experimental-generic-monoEmit one Result form per sync method and function instead of a trapping
primary plus a try_<name> companion:
ts-gen --input types.d.ts --output src/bindings.rs --fallibleuse ts_gen::{parse_source, codegen};
let source = r#"
export declare class Greeter {
constructor(name: string);
greet(): string;
}
"#;
let (module, gctx) = parse_source(source, Some("my-lib"))?;
let rust_code = codegen::generate(&module, &gctx)?;
println!("{rust_code}");Options are set through GenerateOptions builder methods (its fields are
private):
let options = codegen::GenerateOptions::new()
.error_type("crate::Error")
.experimental_generic_mono(true);
let rust_code = codegen::generate_with_options(&module, &gctx, &options)?;- Classes (including abstract classes and inheritance)
- Interfaces (classified as class-like or dictionary types)
- Functions and variables
- String and numeric enums
- Type aliases
- Namespaces
- Module declarations
- Generics (
JsGenericerasure by default, with experimental per-monomorphization support)
- External generic type arguments are not preserved unless the target is a
recognized
js_syscontainer; locally declared generic types keep theirs. - The parsed IR uses
Rc<str>internally and is!Send/!Sync.
Licensed under either of Apache License, Version 2.0 or MIT license at your option.