7 releases
new 0.1.3 | May 18, 2024 |
---|---|
0.1.2 | May 13, 2024 |
0.0.3 | May 1, 2024 |
0.0.2 | Apr 28, 2024 |
#100 in WebAssembly
743 downloads per month
20KB
260 lines
Dioxus Web Component
This crate provides a bridge to expose a Dioxus component as a web component.
This crate supports web component attributes and custom events. You can also add CSS style to your web component.
Take a look at the examples to see the usage in a full project: https://github.com/ilaborie/dioxus-web-component/tree/main/examples
If you are new to WebAssembly with Rust, take a look at the Rust WebAssembly book first.
Usage with macro
Ideally, you only need to replace the Dioxus #[component]
by #[web_component]
.
Then you should register the web component with wasm-bindgen.
To finish, you can create the npm package with wasm-pack.
use dioxus::prelude::*;
use dioxus_web_component::web_component;
#[web_component]
fn MyWebComponent(
attribute: String,
on_event: EventHandler<i64>,
) -> Element {
todo!()
}
// Function to call from the JS side
#[wasm_bindgen]
pub fn register() {
// Register the web component (aka custom element)
register_my_web_component();
}
Then call the function from the JS side.
Customization of the web component
The #[web_component]
annotation can be configured with:
tag
to set the HTML custom element tag name. By default, it's the kebab case version of the function name.style
to provide theInjectedStyle
to your component.
Parameters of the component could be either an attribute or an event.
Attributes can be customized with the #[attribute]
annotation with:
name
to set the HTML attribute name. By default, it's the kebab-case of the parameter name.option
to mark the attribute optional.true
by default if the type isOption<...>
.initial
to set the default value when the HTML attribute is missing By default use thestd::defaultDefault
implementation of the type.parse
to provide the conversion between the HTML attribute value (a string) to the type value. By default use thestd::str::FromStr
implementation, and fall to the default value if it fails.
Events are parameters with the Dioxus EventHandler<...>
type.
You can customize the event with these attributes:
name
to set the HTML event name. By default use the parameter name without theon
prefix (if any)no_bubble
to forbid the custom event to bubbleno_cancel
to remove the ability to cancel the custom event
This example uses all annotations:
use dioxus::prelude::*;
use dioxus_web_component::web_component;
#[web_component]
fn MyWebComponent(
#[attribute(name= "attr1", option = false, initial = String::new(), parse = |value| Some(value.to_string()))]
attr1: String,
#[attribute(name = "attr-option", option = true, initial = None, parse = |value| Some(value.to_string()))]
attr_option: Option<String>,
#[event(name = "event", no_bubble = false, no_cancel = false)] event: EventHandler<i64>,
) -> Element {
todo!()
}
See dioxus-web-component-macro documentation for more details.
Usage without macro
The usage without macro is discouraged
You can provide your manual implementation of DioxusWebComponent
and call
register_dioxus_web_component
to register your web component.
For example, the greeting example could be written with
use std::borrow::Cow::Borrowed;
use dioxus::prelude::*;
use dioxus_web_component::{register_dioxus_web_component, Context, DioxusWebComponent, Message};
use dioxus_web_component::{
register_dioxus_web_component, Context, DioxusWebComponent, InjectedStyle, Message,
};
use wasm_bindgen::prelude::*;
/// Install (register) the web component
///
/// # Errors
///
/// Registering the web-component may fail
#[wasm_bindgen(start)]
pub fn register() -> Result<(), JsValue> {
register_dioxus_web_component::<GreetingsWebComponent>("plop-greeting");
Ok(())
}
/// The Dioxus component
#[component]
fn Greetings(name: String) -> Element {
rsx! { p { "Hello {name}!" } }
}
struct GreetingsWebComponent;
impl DioxusWebComponent for GreetingsWebComponent {
fn style() -> InjectedStyle {
let css = include_str!("./style.css");
InjectedStyle::Css(Borrowed(css))
}
fn attributes() -> &'static [&'static str] {
&["name"]
}
fn element() -> Element {
let mut name_signal = use_signal(String::new);
let Context { rx, .. } = use_context();
let _change_handler = use_coroutine::<(), _, _>(|_| async move {
while let Ok(Message::AttributeChanged { new_value, .. }) = rx.recv().await {
let value = new_value.unwrap_or_else(|| "World".to_owned());
name_signal.set(value);
}
});
rsx! { Greetings { name: name_signal } }
}
}
The counter example looks like this:
use std::borrow::Cow;
use dioxus::prelude::*;
use dioxus_web_component::{
custom_event_handler, register_dioxus_web_component, Context, CustomEventOptions,
DioxusWebComponent, InjectedStyle,
};
use wasm_bindgen::prelude::*;
/// Install (register) the web component
///
/// # Errors
///
/// Registering the web-component may fail
#[wasm_bindgen(start)]
pub fn register() -> Result<(), JsValue> {
register_dioxus_web_component::<CounterWebComponent>("plop-counter");
Ok(())
}
/// The Dioxus component
#[component]
fn Counter(on_count: EventHandler<i32>) -> Element {
let mut counter = use_signal(|| 0);
rsx! {
button {
onclick: move |_| {
counter += 1;
on_count(*counter.read());
},
"+"
}
output { "{counter}" }
}
}
struct CounterWebComponent;
impl DioxusWebComponent for CounterWebComponent {
fn style() -> InjectedStyle {
let url = Cow::Borrowed("./style.css");
InjectedStyle::Stylesheet(url)
}
fn attributes() -> &'static [&'static str] {
&["name"]
}
fn element() -> Element {
let Context { event_target, .. } = use_context();
let on_count = custom_event_handler(event_target, "count", CustomEventOptions::default());
rsx! { Counter { on_count } }
}
}
Limitations
- web component properties not (yet) supported
- only extends
HTMLElement
- only work as a replacement of Dioxus
#[component]
annotation (does not work with handmadeProps
) - no validation of the custom element name
Contributions
Contributions are welcome ❤️.
Dependencies
~17–27MB
~410K SLoC