-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[PM-13134] update documentation with wasm bindgen recommendations #455
Merged
coroiu
merged 12 commits into
main
from
PM-13134-update-documentation-with-wasm-bindgen-recommendations
Oct 29, 2024
+289
โ0
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
22dcb87
feat: add some intermediary sections
coroiu 00d8cd3
feat: write interop introduction
coroiu 66a7918
move pargraph from intro to `wasm-bindgen` vs. `tsify`
coroiu 417d32f
add `tsify` documentation
coroiu ead1a5d
feat: add `wasm-bindgen` section
coroiu 981d86b
tweaks and rewrites for readability and simplicity
coroiu f1d0060
remove line
coroiu 368040d
re-add custom types information
coroiu 414ae49
fix: remove wrong line
coroiu 1928fde
feat: add some information about
coroiu 8424719
fix: remove unnecessary tsify paragraph
coroiu b7be6c2
chore: move docs to architecture
coroiu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,6 +67,7 @@ signtool | |
signup | ||
sqlcmd | ||
struct | ||
structs | ||
subfolders | ||
subprocessor | ||
toolset | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Password Manager | ||
|
||
The Password Manager SDK is designed for internal use within Bitwarden and supports key | ||
functionality for managing encrypted data, vault access, and user authentication. Written in Rust, | ||
the SDK is versatile and provides bindings for a variety of platforms, including mobile clients | ||
(Kotlin and Swift) and web clients (JavaScript/TypeScript). | ||
|
||
This section will provide guidance on developing with the SDK in a way that ensures compatibility | ||
across both mobile and web platforms. It will cover best practices for structuring code, addressing | ||
platform-specific challenges, and ensuring that your implementation works seamlessly across | ||
Bitwardenโs mobile and web applications. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Web | ||
|
||
The Password Manager SDK supports web-based clients, allowing developers to integrate core password | ||
management functionality into JavaScript/TypeScript environments. The SDK is packaged as an NPM | ||
module under `@bitwarden/sdk-internal` and uses WebAssembly (`wasm`) to enable interaction with Rust | ||
code. This section will provide guidance and best practices for working with the SDK in a browser | ||
environment. |
270 changes: 270 additions & 0 deletions
270
docs/architecture/sdk/password-manager/web/interoperability.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
# Interoperability | ||
|
||
When working with the SDK, one of the key challenges is managing Rust/JavaScript bindings to enable | ||
smooth interactions. In our project, we use both `wasm-bindgen` and `tsify` to bridge this gap and | ||
make it easier for JavaScript to work with Rust code. While both tools facilitate communication | ||
between Rust and JavaScript, they serve distinct purposes, and it's important to understand when to | ||
use each. | ||
|
||
## `wasm-bindgen` vs. `tsify` | ||
|
||
:::tip | ||
|
||
**In short:** Use `tsify` unless the web-side needs to call functions or interact with Rust objects | ||
directly, in which case, use `wasm-bindgen`. | ||
|
||
::: | ||
|
||
At first glance, `wasm-bindgen` and `tsify` may appear to do similar things. Both generate | ||
TypeScript definitions, and both allow JavaScript to work with Rust data. However, while they share | ||
a common goal, they handle Rust/JavaScript interop in different ways and are suitable for different | ||
use cases. | ||
|
||
`wasm-bindgen` focuses on creating WebAssembly bindings that allow JavaScript to directly call Rust | ||
functions and manipulate Rust objects. This works well for exposing APIs and interacting with simple | ||
Rust types like `u32`, `f64`, and `String`. However, `wasm-bindgen` has limitations when it comes to | ||
handling more complex Rust types such as enums with values or deeply nested structures, as these | ||
need additional work to be represented correctly in JavaScript. | ||
|
||
On the other hand, `tsify` leverages Rust's `serde` ecosystem to expose Rust data models to | ||
JavaScript and automatically provide TypeScript bindings. It accomplishes this by serializing Rust | ||
data structures into JavaScript objects, allowing them to be treated as native, typed TypesScript | ||
objects. This differs from `wasm-bindgen`, which allows JavaScript to interact directly with | ||
Rust-owned structures in memory. `tsify` is generally better for handling complex data types, | ||
especially when working with Rust crates that already support `serde`. | ||
|
||
## Choosing Between `wasm-bindgen` and `tsify` | ||
|
||
Here's a quick overview of when to use each tool: | ||
|
||
| Tool | Purpose | Strengths | Typical Use Case | | ||
| -------------- | --------------------------------------- | ------------------------------ | ----------------------------------------------------------------------- | | ||
| `wasm-bindgen` | Expose Rust functions and objects to JS | Direct Rust function calls | When JavaScript needs to call Rust functions or manipulate Rust objects | | ||
| `tsify` | Provide TypeScript bindings via `serde` | Handle complex data structures | For exchanging structured data between Rust and JavaScript | | ||
|
||
Use `wasm-bindgen` when you need JavaScript to call into Rust directly or work with live Rust | ||
objects (like a struct or a function in Rust). Otherwise, default to `tsify` for easier handling of | ||
complex types and data exchange. | ||
|
||
## How to use `wasm-bindgen` | ||
|
||
### Basic functions | ||
|
||
To create functions that can be called from JavaScript using `wasm-bindgen`, you need to apply the | ||
`wasm_bindgen` attribute to the function and ensure the return type is something that can be | ||
converted into a JavaScript type. For instance, simple types like `u32`, `f64`, `bool`, and `String` | ||
can be returned directly: | ||
|
||
```rust | ||
use wasm_bindgen::prelude::*; | ||
|
||
#[wasm_bindgen] | ||
pub fn do_something() -> u32 { | ||
42 | ||
} | ||
|
||
#[wasm_bindgen] | ||
pub fn say_hello() -> String { | ||
"Hello, World!".to_owned() | ||
} | ||
``` | ||
|
||
This will generate the following TypeScript definitions: | ||
|
||
```typescript | ||
/** | ||
* @returns {number} | ||
*/ | ||
export function do_something(): number; | ||
|
||
/** | ||
* @returns {string} | ||
*/ | ||
export function say_hello(): string; | ||
``` | ||
|
||
### Structs | ||
|
||
If you want to return more complex types, you can annotate a Rust struct with the `wasm_bindgen` | ||
attribute. Make sure that any fields you want to access from JavaScript are public: | ||
|
||
```rust | ||
use wasm_bindgen::prelude::*; | ||
|
||
#[wasm_bindgen] | ||
pub struct User { | ||
pub age: u32, | ||
} | ||
``` | ||
|
||
Which generates the following TypeScript definitions: | ||
|
||
```typescript | ||
export class User { | ||
free(): void; | ||
age: number; | ||
} | ||
``` | ||
|
||
For structs containing `String` or other types that are not `Copy`, use the `getter_with_clone` | ||
attribute to ensure JavaScript can access the value without directly referencing it in WebAssembly | ||
memory: | ||
|
||
```rust | ||
#[wasm_bindgen(getter_with_clone)] | ||
pub struct User { | ||
pub name: String, | ||
pub age: u32, | ||
} | ||
``` | ||
|
||
Which generates the following TypeScript definitions: | ||
|
||
```typescript | ||
export class User { | ||
free(): void; | ||
age: number; | ||
name: string; | ||
} | ||
``` | ||
|
||
This ensures that values like `String` are properly cloned before being passed to JavaScript. | ||
|
||
### Structs with Functions | ||
|
||
You can also return types with methods: | ||
|
||
```rust | ||
#[wasm_bindgen(getter_with_clone)] | ||
pub struct User { | ||
pub name: String, | ||
pub age: u32, | ||
} | ||
|
||
#[wasm_bindgen] | ||
impl User { | ||
pub fn say_hello(&self) -> String { | ||
format!("Hello, {}!", self.name) | ||
} | ||
} | ||
``` | ||
|
||
Which generates the following TypeScript definitions: | ||
|
||
```typescript | ||
export class User { | ||
free(): void; | ||
say_hello(): string; | ||
age: number; | ||
name: string; | ||
} | ||
``` | ||
|
||
### About the `free` Method | ||
|
||
When working with `wasm-bindgen`, you may notice that all generated classes have a `free` method. | ||
This method is used to forcibly free the memory allocated for the Rust object when it is no longer | ||
needed in JavaScript. In most cases you can safely ignore this method, as the memory will be cleaned | ||
up automatically. For more information see | ||
[Support for Weak References](https://rustwasm.github.io/docs/wasm-bindgen/reference/weak-references.html). | ||
|
||
## How to use `tsify` | ||
|
||
### Basic Types | ||
|
||
To return a struct from Rust to JavaScript using `tsify`, derive `Serialize`, `Deserialize`, and | ||
`Tsify`: | ||
|
||
```rust | ||
use serde::{Deserialize, Serialize}; | ||
#[cfg(feature = "wasm")] | ||
use {tsify_next::Tsify, wasm_bindgen::prelude::*}; | ||
|
||
#[derive(Serialize, Deserialize)] | ||
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] | ||
pub struct User { | ||
pub name: String, | ||
pub age: u32, | ||
pub address: Address, | ||
} | ||
|
||
#[derive(Serialize, Deserialize)] | ||
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] | ||
pub struct Address { | ||
pub street: String, | ||
pub city: String, | ||
} | ||
``` | ||
|
||
Which generates TypeScript interfaces for both `User` and `Address`: | ||
|
||
```typescript | ||
export interface User { | ||
name: string; | ||
age: number; | ||
address: Address; | ||
} | ||
|
||
export interface Address { | ||
street: string; | ||
city: string; | ||
} | ||
``` | ||
|
||
Note how `tsify` creates TypeScript interfaces, whereas `wasm-bindgen` creates classes that | ||
reference Rust objects stored in WebAssembly memory. | ||
|
||
### External Types | ||
|
||
With `tsify`, fields can be any type that `serde` can serialize or deserialize, including external | ||
types from other crates. For example: | ||
|
||
```rust | ||
use serde::{Deserialize, Serialize}; | ||
use chrono::{DateTime, Utc}; | ||
use uuid::Uuid; | ||
|
||
#[derive(Serialize, Deserialize)] | ||
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] | ||
pub struct FolderView { | ||
pub id: Option<Uuid>, | ||
pub name: String, | ||
pub revision_date: DateTime<Utc>, | ||
} | ||
``` | ||
|
||
Which generates the following TypeScript definitions: | ||
|
||
```typescript | ||
export interface FolderView { | ||
id: Uuid | undefined; | ||
name: string; | ||
revisionDate: DateTime<Utc>; | ||
} | ||
``` | ||
|
||
Note that, while `tsify` does generate TypeScript definitions, external types that don't derive | ||
`tsify` (like `Uuid` or `DateTime<Utc>`) are missing from the definitions. To fix this, external | ||
types need to be manually added to the TypeScript definitions found in | ||
`bitwarden-wasm-internal/src/custom_types.rs`, for example: | ||
|
||
```rust | ||
#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)] | ||
const TS_CUSTOM_TYPES: &'static str = r#" | ||
export type Uuid = string; | ||
|
||
/** | ||
* RFC3339 compliant date-time string. | ||
* @typeParam T - Not used in JavaScript. | ||
*/ | ||
export type DateTime<T = unknown> = string; | ||
|
||
/** | ||
* UTC date-time string. Not used in JavaScript. | ||
*/ | ||
export type Utc = unknown; | ||
"#; | ||
|
||
``` | ||
|
||
This ensures the TypesScript definitions match the serialized Rust structure. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the future, you can assign a custom title to admonitions, https://docusaurus.io/docs/markdown-features/admonitions#specifying-title