Add an api for TemplateElement definition.

This commit is contained in:
Jeremy Wall 2023-11-12 17:29:00 -05:00
parent f5791b9355
commit e71a28b4e3
4 changed files with 107 additions and 12 deletions

View File

@ -17,3 +17,6 @@ str_inflector = "0.12.0"
[dependencies.syn]
version = "1.0.101"
features = ["full"]
[features]
HtmlTemplateElement = []

View File

@ -96,7 +96,11 @@ fn expand_component_def(
}
}
fn expand_struct_trait_shim(struct_name: &Ident, once_name: &Ident, observed_attrs: Literal) -> syn::ItemImpl {
fn expand_wc_struct_trait_shim(
struct_name: &Ident,
once_name: &Ident,
observed_attrs: Literal,
) -> syn::ItemImpl {
let trait_path = expand_crate_ref("wasm-web-component", parse_quote!(WebComponentDef));
let handle_path = expand_crate_ref("wasm-web-component", parse_quote!(WebComponentHandle));
parse_quote! {
@ -239,16 +243,20 @@ fn expand_binding(struct_name: &Ident) -> syn::ItemImpl {
)
}
fn expand_struct(
fn expand_web_component_struct(
item_struct: ItemStruct,
class_name: Literal,
element_name: Literal,
observed_attributes: Literal,
) -> TokenStream {
let struct_name = item_struct.ident.clone();
let struct_once_name = Ident::new(&(struct_name.to_string().to_snake_case().to_uppercase() + "_ONCE"), Span::call_site());
let struct_once_name = Ident::new(
&(struct_name.to_string().to_snake_case().to_uppercase() + "_ONCE"),
Span::call_site(),
);
let component_def = expand_component_def(&struct_name, &class_name, &element_name);
let non_wasm_impl = expand_struct_trait_shim(&struct_name, &struct_once_name, observed_attributes);
let non_wasm_impl =
expand_wc_struct_trait_shim(&struct_name, &struct_once_name, observed_attributes);
let wasm_shim = expand_wasm_shim(&struct_name);
let binding_trait = expand_binding(&struct_name);
let expanded = quote! {
@ -268,6 +276,36 @@ fn expand_struct(
TokenStream::from(expanded)
}
#[cfg(feature = "HtmlTemplateElement")]
fn expand_template_struct(item_struct: ItemStruct) -> TokenStream {
let struct_name = item_struct.ident.clone();
let struct_once_name = Ident::new(
&(struct_name.to_string().to_snake_case().to_uppercase() + "_ONCE"),
Span::call_site(),
);
let trait_path = expand_crate_ref("wasm-web-component", parse_quote!(TemplateElement));
let expanded = quote! {
use std::sync::Once;
use web_sys::Node;
static #struct_once_name: Once = Once::new();
#item_struct
impl #trait_path for #struct_name {}
impl #struct_name {
#[doc = "Defines this HtmlTemplateElement and adds it to the document exactly once. Subsequent calls are noops."]
pub fn define_once() { // TODO(jwall): Should this return the element?
#struct_once_name.call_once(|| {
let template_element = Self::render();
let body = web_sys::window().expect("Failed to get window")
.document().expect("Failed to get window document").
body().expect("Failed to get document body");
body.append_child(template_element.as_ref()).expect("Failed to add template element to document");
})
}
}
};
TokenStream::from(expanded)
}
/// Creates the necessary Rust and Javascript shims for a Web Component.
#[proc_macro_attribute]
pub fn web_component(attr: TokenStream, item: TokenStream) -> TokenStream {
@ -279,5 +317,13 @@ pub fn web_component(attr: TokenStream, item: TokenStream) -> TokenStream {
let (class_name, element_name, observed_attributes) =
get_class_and_element_names(args, &item_struct.ident);
expand_struct(item_struct, class_name, element_name, observed_attributes)
expand_web_component_struct(item_struct, class_name, element_name, observed_attributes)
}
/// Creates the neccessary Rust and Javascript shims for rendering an HtmlTemplateElement
#[cfg(feature = "HtmlTemplateElement")]
#[proc_macro_attribute]
pub fn template_element(_attr: TokenStream, item: TokenStream) -> TokenStream {
let item_struct = parse_macro_input!(item as ItemStruct);
expand_template_struct(item_struct)
}

View File

@ -2,15 +2,14 @@
name = "wasm-web-component"
version = "0.1.1"
edition = "2021"
author = "Jeremy Wall <jeremy@marzhillstudios.com>"
[lib]
crate-type = ["cdylib", "rlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
wasm-web-component-macros = { path = "../macros" }
[dependencies.wasm-web-component-macros]
path = "../macros"
[dependencies.wasm-bindgen-test]
version = "0.3"
@ -26,7 +25,6 @@ version = "0.3"
features = [
"CustomElementRegistry",
"Document",
#"DocumentFragment",
"KeyboardEvent",
"Event",
"EventTarget",
@ -35,7 +33,6 @@ features = [
"Text",
"HtmlBaseElement",
"HtmlElement",
"HtmlTemplateElement",
"HtmlSlotElement",
"Node",
"ShadowRoot",
@ -44,3 +41,10 @@ features = [
"Window",
"console"
]
[features]
default = ["HtmlTemplateElement"]
HtmlTemplateElement = [
"web-sys/HtmlTemplateElement",
"wasm-web-component-macros/HtmlTemplateElement",
]

View File

@ -1,5 +1,7 @@
use js_sys::Function;
use wasm_bindgen::{convert::IntoWasmAbi, JsValue};
#[cfg(feature = "HtmlTemplateElement")]
use web_sys::HtmlTemplateElement;
use web_sys::{window, Element, Event, HtmlElement, Window};
/// This attribute proc-macro will generate the following trait implementations
@ -82,10 +84,22 @@ pub trait WebComponentBinding: WebComponentDef {
}
}
/// Marker trait used in the generated shims to assert that their are Rust implemtntations
/// Marker trait used in the generated shims to assert that there are Rust implemtntations
/// of the callback functions for the component.
pub trait WebComponent: WebComponentBinding {}
/// Defines the template element rendering method.
#[cfg(feature = "HtmlTemplateElement")]
pub trait TemplateElementRender {
// Creates and returns an HtmlTemplateElement.
fn render() -> HtmlTemplateElement;
}
/// Marker trait used in the generated shims to assert that there are Rust implemtntations
/// of the rendering function for the component.
#[cfg(feature = "HtmlTemplateElement")]
pub trait TemplateElement: TemplateElementRender {}
/// A handle for your WebComponent Definition. Offers easy access to construct your
/// element.
pub struct WebComponentHandle {
@ -301,4 +315,32 @@ mod tests {
assert_eq!(ThisElement::class_name(), "ThisElement");
assert_eq!(ThisElement::element_name(), "this-old-element");
}
// TODO(jwall): Benchmarks for TemplateElements?
#[cfg(feature = "HtmlTemplateElement")]
#[wasm_bindgen_test]
fn test_template_element_render_once() {
use wasm_web_component_macros::template_element;
#[template_element]
pub struct MyTemplate();
impl TemplateElementRender for MyTemplate {
fn render() -> HtmlTemplateElement {
let val: JsValue = window()
.unwrap()
.document()
.unwrap()
.create_element("template")
.unwrap()
.into();
let el: HtmlTemplateElement = val.into();
el
}
}
let body = window().unwrap().document().unwrap().body().unwrap();
assert!(!body.last_child().unwrap().has_type::<HtmlTemplateElement>());
MyTemplate::define_once();
assert!(body.last_child().unwrap().has_type::<HtmlTemplateElement>());
}
}