#elements #component #web #create #traits #shadow #dom

custom-elements

A CustomElement trait to create Rust/WASM Web Components/Custom Elements easily without writing any JavaScript

3 unstable releases

0.2.1 Jan 27, 2024
0.2.0 Aug 21, 2021
0.1.0 Aug 20, 2021

#3 in #shadow

Download history 11/week @ 2023-12-04 3/week @ 2023-12-11 3/week @ 2023-12-18 8/week @ 2023-12-25 2/week @ 2024-01-22 23/week @ 2024-01-29 7/week @ 2024-02-05 2/week @ 2024-02-12 24/week @ 2024-02-19 39/week @ 2024-02-26 12/week @ 2024-03-04 23/week @ 2024-03-11 3/week @ 2024-03-18

80 downloads per month

Apache-2.0/MIT

21KB
210 lines

crates.io docs.rs

A framework-agnostic CustomElement trait to create Rust/WASM Web Components/Custom Elements easily without writing any JavaScript.

Overview

The Web Components standard creates a browser feature that allows you to create reusable components, called Custom Elements. (People often use the label “Web Components” when they mean Custom Elements in particular; the Web Components standard also includes Shadow DOM and HTML Templates.)

While web_sys exposes the browser’s CustomElementRegistry interface, it can be hard to use. Creating a Custom Element requires calling customElements.define() and passing it an ES2015 class that extends HTMLElement, which is not currently possible to do directly from Rust.

This crate provides a CustomElement trait that, when implemented, allows you to encapsulate any Rust structure as a reusable web component without writing any JavaScript. In theory it should be usable with any Rust front-end framework; the examples directory contains examples for Yew and for a vanilla Rust/WASM component.

impl CustomElement for MyWebComponent {
  fn inject_children(&mut self, this: &HtmlElement) {
      inject_style(&this, "p { color: green; }");
      let node = self.view();
      this.append_child(&node).unwrap_throw();
  }

  fn observed_attributes() -> &'static [&'static str] {
      &["name"]
  }

  fn attribute_changed_callback(
      &mut self,
      _this: &HtmlElement,
      name: String,
      _old_value: Option<String>,
      new_value: Option<String>,
  ) {
      if name == "name" {
          /* do something... */
      }
  }

  fn connected_callback(&mut self, _this: &HtmlElement) {
      log("connected");
  }

  fn disconnected_callback(&mut self, _this: &HtmlElement) {
      log("disconnected");
  }

  fn adopted_callback(&mut self, _this: &HtmlElement) {
      log("adopted");
  }
}

#[wasm_bindgen]
pub fn define_custom_elements() {
    MyWebComponent::define("my-component");
}

Shadow DOM

By default, these custom elements use the Shadow DOM (in “open” mode) to encapsulate the styles and content of the element. You can override that choice simply by implementing the shadow method and returning false:

fn shadow() -> bool {
    false
}

Lifecycle Methods

You can implement each of the custom element’s lifecycle callbacks. Each of the callbacks is passed both the component for which the trait is being implemented, and the HtmlElement of the custom element.


fn connected_callback(&mut self, this: &HtmlElement) {
    log("connected");
}

fn disconnected_callback(&mut self, this: &HtmlElement) {
    log("disconnected");
}

fn adopted_callback(&mut self, this: &HtmlElement) {
    log("adopted");
}

fn attribute_changed_callback(
    &mut self,
    this: &HtmlElement,
    name: String,
    old_value: Option<String>,
    new_value: Option<String>,
) {
    if name == "name" {
        // do something
    }
}

Using Rust Frameworks

The minimum needed to implement CustomElement is some way to inject children into the custom element. It’s also generally helpful to have it respond to changes in its attributes via the attribute_changed_callback. Depending on the framework, these may be more or less difficult to accomplish; in particular, for Elm-inspired frameworks you may need to create a wrapper that owns some way of updating the app’s state.

See the Yew example for an example of how to work with a framework’s API.

Customized built-in elements

Custom elements can either be autonomous (<my-component></my-component>) or customized built-in elements (<p is="my-paragraph-component"></p>). This crate offers support for creating customized built-in elements via the superclass` method.

Resources

This is a fairly minimal wrapper for the Custom Elements API. The following MDN sources should give you more than enough information to start creating custom elements:

Running the Examples

The examples use wasm-pack and a simple Python server. You should be able to run them with a simple

./build.sh && ./runserver

Dependencies

~6.5–9MB
~172K SLoC