Skip to content

Commit

Permalink
feat: add additional docs on using wasm-bindgen (#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
gbj authored Feb 16, 2024
1 parent 8b6ead3 commit 3ac3bd6
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 21 deletions.
2 changes: 1 addition & 1 deletion src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
- [`<Form/>`](./router/20_form.md)
- [Interlude: Styling](./interlude_styling.md)
- [Metadata](./metadata.md)
- [Using `web_sys` and `HtmlElement`](./web_sys.md)
- [Integrating with JavaScript: `wasm-bindgen`, `web_sys`, and `HtmlElement`](./web_sys.md)
- [Client-Side Rendering: Wrapping Up](./csr_wrapping_up.md)
- [Part 2: Server Side Rendering](./ssr/README.md)
- [`cargo-leptos`](./ssr/21_cargo_leptos.md)
Expand Down
92 changes: 72 additions & 20 deletions src/web_sys.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,61 @@
# `web_sys` and `HtmlElement`
# Integrating with JavaScript: `wasm-bindgen`, `web_sys` and `HtmlElement`

The [`web_sys`](https://docs.rs/web-sys/latest/web_sys/) crate provides bindings to JavaScript web APIs that you can call in your Rust code.
If possible, you probably want to use something provided by Leptos, or the fantastic utilities crate [`leptos-use`](https://leptos-use.rs/).
However, if neither provides the API you are looking for, you will have to fall back to using `web_sys`.
Here are a few things to keep in mind while using it.
Leptos provides a variety of tools to allow you to build declarative web applications without leaving the world
of the framework. Things like the reactive system, `component` and `view` macros, and router allow you to build
user interfaces without directly interacting with the Web APIs provided by the browser. And they let you do it
all directly in Rust, which is great—assuming you like Rust. (And if you’ve gotten this far in the book, we assume
you like Rust.)

*Supplementary reading: [The `wasm-bindgen` Guide: `web-sys`](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/index.html)*
Ecosystem crates like the fantastic set of utilities provided by [`leptos-use`](https://leptos-use.rs/) can take you
even further, by providing Leptos-specific reactive wrappers around many Web APIs.

## Enabling features
Nevertheless, in many cases you will need to access JavaScript libraries or Web APIs directly. This chapter can help.

`web_sys` is heavily feature-gated to keep compile times low.
If you would like to use one of its many APIs, you may need to enable a feature to use it.
## Using JS Libraries with `wasm-bindgen`

The features required to use an item is always listed in it's documentation.
Your Rust code can be compiled to a WebAssembly (WASM) module and loaded to run in the browser. However, WASM does not
have direct access to browser APIs. Instead, the Rust/WASM ecosystem depends on generating bindings from your Rust code
to the JavaScript browser environment that hosts it.

The [`wasm-bindgen`](https://rustwasm.github.io/docs/wasm-bindgen/) crate is at the center of that ecosystem. It provides
both an interface for marking parts of Rust code with annotations telling it how to call JS, and a CLI tool for generating
the necessary JS glue code. You’ve been using this without knowing it all along: both `trunk` and `cargo-leptos` rely on
`wasm-bindgen` under the hood.

If there is a JavaScript library that you want to call from Rust, you should refer to the `wasm-bindgen` docs on
[importing functions from JS](https://rustwasm.github.io/docs/wasm-bindgen/examples/import-js.html). It is relatively
easy to import individual functions, classes, or values from JavaScript to use in your Rust app.

It is not always easy to integrate JS libraries into your app directly. In particular, any library that depends on a
particular JS framework like React may be hard to integrated. Libraries that manipulate DOM state in some way (for example,
rich text editors) should also be used with care: both Leptos and the JS library will probably assume that they are
the ultimate source of truth for the app’s state, so you should be careful to separate their responsibilities.

## Acccessing Web APIs with `web-sys`

If you just need to access some browser APIs without pulling in a separate JS library, you can do so using the
[`web_sys`](https://docs.rs/web-sys/latest/web_sys/) crate. This provides bindings for all of the Web APIs provided by
the browser, with 1:1 mappings from browser types and functions to Rust structs and methods.

In general, if you’re asking “how do I *do X* with Leptos?” where *do X* is accessing some Web API, looking up a vanilla
JavaScript solution and translating it to Rust using the [`web-sys` docs](https://docs.rs/web-sys/latest/web_sys/) is a
good approach.


> After this section, you might find
> [the `wasm-bindgen` guide chapter on `web-sys`](https://rustwasm.github.io/docs/wasm-bindgen/web-sys/index.html)
> useful for additional reading.
### Enabling features

`web_sys` is heavily feature-gated to keep compile times low. If you would like to use one of its many APIs, you may
need to enable a feature to use it.

The features required to use an item are always listed in its documentation.
For example, to use [`Element::get_bounding_rect_client`](https://docs.rs/web-sys/latest/web_sys/struct.Element.html#method.get_bounding_client_rect), you need to enable the `DomRect` and `Element` features.

Leptos already enables [a whole bunch](https://github.com/leptos-rs/leptos/blob/main/leptos_dom/Cargo.toml#L41) of features - if the required feature is already enabled here, you won't have to enable it in your own app.
Otherwise, add it to your `Cargo.toml` and you're good to go!
Otherwise, add it to your `Cargo.toml` and youre good to go!

```toml
[dependencies.web-sys]
Expand All @@ -41,16 +80,30 @@ In `.cargo/config.toml`:
RUSTFLAGS = "--cfg=web_sys_unstable_apis"
```

## `web_sys::HtmlElement`
## Accessing raw `HtmlElement`s from your `view`

The declarative style of the framework means that you don’t need to directly manipulate DOM nodes to build up your user interface.
However, in some cases you want direct access to the underlying DOM element that represents part of your view. The section of the book
on [“uncontrolled inputs”](/view/05_forms.html?highlight=NodeRef#uncontrolled-inputs) showed how to do this using the
[`NodeRef`](https://docs.rs/leptos/latest/leptos/struct.NodeRef.html) type.

You may notice that [`NodeRef::get`](https://docs.rs/leptos/latest/leptos/struct.NodeRef.html#method.get) returns an `Option<leptos::HtmlElement<T>>`. This is *not* the same type as a [`web_sys::HtmlElement`](https://docs.rs/web-sys/latest/web_sys/struct.HtmlElement.html), although they
are related. So what is this [`HtmlElement<T>`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html) type, and how do you use it?

### Overview

If you've used the [`NodeRef`](https://docs.rs/leptos/latest/leptos/struct.NodeRef.html) type, you may notice that [`get`](https://docs.rs/leptos/latest/leptos/struct.NodeRef.html#method.get)ting it returns an `Option<leptos::HtmlElement<T>>`.
What is this [`HtmlElement<T>`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html) type, and how do you use it?
`web_sys::HtmlElement` is the Rust equivalent of the browser’s [`HTMLElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement)
interface, which is implemented for all HTML elements. It provides access to a minimal set of functions and APIs that are guaranteed to be
available for any HTML element. Each particular HTML element then has its own element class, which implements additional functionality.
The goal of `leptos::HtmlElement<T>` is to bridge the gap between elements in your view and these more specific JavaScript types, so that you
can access the particular functionality of those elements.

While related, `leptos::HtmlElement<T>` and [`web_sys::HtmlElement`](https://docs.rs/web-sys/latest/web_sys/struct.HtmlElement.html) are different types.
This is implement by using the Rust `Deref` trait to allow you to dereference a `leptos::HtmlElement<T>` to the appropriately-typed JS object
for that particular element type `T`.

### Definition

There's a bunch of related structs and traits, so I'll gather all the relevant ones here.
Understanding this relationship involves understanding some related traits.

The following simply defines what types are allowed inside the `T` of `leptos::HtmlElement<T>` and how it links to `web_sys`.

Expand Down Expand Up @@ -106,7 +159,7 @@ If you wish to use the `web_sys` method instead, you can manually deref with `(*

If you want to have even more control over which type you are calling a method from, `AsRef<T>` is implemented for all types that are part of the deref chain, so you can explicitly state which type you want.

*See also: [The `wasm-bindgen` Guide: Inheritance in `web-sys`](https://rustwasm.github.io/wasm-bindgen/web-sys/inheritance.html).*
> See also: [The `wasm-bindgen` Guide: Inheritance in `web-sys`](https://rustwasm.github.io/wasm-bindgen/web-sys/inheritance.html).
### Clones

Expand All @@ -132,7 +185,7 @@ let on_click = |ev: MouseEvent| {
}
```

*See the [`event_target_*` functions here](https://docs.rs/leptos/latest/leptos/fn.event_target.html?search=event_target), if you're curious.*
> See the [`event_target_*` functions here](https://docs.rs/leptos/latest/leptos/fn.event_target.html?search=event_target), if you're curious.
### `leptos::HtmlElement`

Expand All @@ -151,5 +204,4 @@ Here are some of the common methods you may want to use, for example in event li
Specify the event type through one of [`leptos::ev::*`](https://docs.rs/leptos/latest/leptos/ev/index.html) (it's the ones in all lowercase).
- [`child`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html#method.child): adds an element as the last child of the element.

Take a look at the rest of the [`leptos::HtmlElement`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html) methods too - if none of them fit your requirements, also take a look at [`leptos-use`](https://leptos-use.rs/).
Otherwise, you'll have to use the `web_sys` APIs.
Take a look at the rest of the [`leptos::HtmlElement`](https://docs.rs/leptos/latest/leptos/struct.HtmlElement.html) methods too. If none of them fit your requirements, also take a look at [`leptos-use`](https://leptos-use.rs/). Otherwise, you’ll have to use the `web_sys` APIs.

0 comments on commit 3ac3bd6

Please sign in to comment.