Commit 2d79b82
New component macro
Changed files (2)
Diff
@@ -15,7 +15,7 @@ proc-macro2 = "1" gloo = "0.12.0" wasm-bindgen = "0.2.122" js-sys = "0.3"-web-sys = { version = "0.3.99", features = ["Window", "HtmlElement", "CustomElementRegistry", "ElementDefinitionOptions"] }+web-sys = { version = "0.3.99", features = ["Window", "Document", "HtmlDocument", "Element", "HtmlElement", "HtmlInputElement", "NodeList", "CssStyleDeclaration", "DomTokenList", "MessageEvent", "CustomElementRegistry", "ElementDefinitionOptions"] } inventory = "0.3.24" [dev-dependencies]@@ -2,7 +2,7 @@ use proc_macro as pm; use proc_macro2::Span; use quote::quote; use syn::parse2;-use syn::{Ident, ItemStruct, LitStr};+use syn::{Fields, Ident, ItemStruct, LitStr}; pub fn component_impl(attr: pm::TokenStream, item: pm::TokenStream) -> pm::TokenStream { // Parse this #[component("my-tag")]) for a struct@@ -37,8 +37,6 @@ pub fn component_impl(attr: pm::TokenStream, item: pm::TokenStream) -> pm::Token let struct_name_ident = &component_struct_item.ident; - let global_store = generate_component_store(struct_name_ident.clone());- let create_class_js = format!("createComponentClass{}", struct_name_ident); let init_self_js = format!("initSelf{}", struct_name_ident); let attach_js = format!("attachComponent{}", struct_name_ident);@@ -66,55 +64,37 @@ pub fn component_impl(attr: pm::TokenStream, item: pm::TokenStream) -> pm::Token let observed_fn = observed_quote(&observed_js, struct_name_ident); let changed_fn = changed_quote(&changed_js, struct_name_ident); let component_impl = component_impl_quote(struct_name_ident);+ let component_full_impl = create_component(&component_struct_item); let expanded = quote! {- #global_store- #rs_self #component_struct_item+ #component_impl+ #component_full_impl+ #create_js_class_fn++ #rs_self+ #attach_fn #detach_fn #observed_fn #changed_fn- #component_impl }; expanded.into() } -fn storage_for_struct_impl(struct_name: &Ident) -> proc_macro2::TokenStream {- let store_name_str = store_name(struct_name);- let store_ident = Ident::new(&store_name_str, Span::call_site());-- let expanded = quote! {- impl ::kaja_web::ComponentStorage for #struct_name {- fn storage() -> &'static std::thread::LocalKey<- std::cell::RefCell<- std::collections::HashMap<String, Rc<RefCell<#struct_name>>>,- >,- > {- &#store_ident- }- }- };-- expanded-}- fn attach_quote(atach_state_js_fn: &str, struct_name: &Ident) -> proc_macro2::TokenStream { let js_callback_ident = Ident::new(atach_state_js_fn, Span::call_site()); let rs_name_str = from_pascal_to_snake(atach_state_js_fn); let rs_name_ident = Ident::new(&rs_name_str, Span::call_site());- let store_name_str = store_name(struct_name);- let store_ident = Ident::new(&store_name_str, Span::call_site());+ let component_name = Ident::new(&format!("{}Component", struct_name), Span::call_site()); let expanded = quote! { #[callback(#js_callback_ident)] fn #rs_name_ident(id: String) {- let exists = #store_ident.with(|store| store.borrow().contains_key(&id));-- if exists {- #struct_name::connected(&id);+ if let Some(component) = #component_name::get_component(&id) {+ component.connected(); } else { error!("Component id not found in connect.", id.clone()); }@@ -131,14 +111,13 @@ fn detach_quote(detach_state_js_fn: &str, struct_name: &Ident) -> proc_macro2::T let store_name_str = store_name(struct_name); let store_ident = Ident::new(&store_name_str, Span::call_site());+ let component_name = Ident::new(&format!("{}Component", struct_name), Span::call_site()); let expanded = quote! { #[callback(#js_callback_ident)] fn #rs_name_ident(wasm_id: String) {- let exists = #store_ident.with(|store| store.borrow().contains_key(&wasm_id));-- if exists {- #struct_name::disconnected(&wasm_id);+ if let Some(component) = #component_name::get_component(&wasm_id) {+ component.disconnected(); #store_ident.with(|store| { store.borrow_mut().remove(&wasm_id); });@@ -173,18 +152,15 @@ fn changed_quote(changed_state_js_fn: &str, struct_name: &Ident) -> proc_macro2: let js_callback_ident = Ident::new(changed_state_js_fn, Span::call_site()); let rs_name_str = from_pascal_to_snake(changed_state_js_fn); let rs_name_ident = Ident::new(&rs_name_str, Span::call_site());-- let store_name_str = store_name(struct_name);- let store_ident = Ident::new(&store_name_str, Span::call_site());+ let component_name = Ident::new(&format!("{}Component", struct_name), Span::call_site()); let expanded = quote! { #[callback(#js_callback_ident)] fn #rs_name_ident(element_id: String, key: String, from: String, to: String) {- let exists = #store_ident.with(|store| store.borrow().contains_key(&element_id));-- if exists {+ if let Some(component) = #component_name::get_component(&element_id) { let name = key.as_str();- #struct_name::attribute_changed(&element_id, name, &from, &to);+ component.internal_attributes_update(name, &from, &to);+ component.attribute_changed(name, &from, &to); } else { error!("Component id not found in changed attribute.", element_id.clone()); }@@ -198,13 +174,12 @@ fn observed_quote(observed_js_fn: &str, struct_name: &Ident) -> proc_macro2::Tok let js_callback_ident = Ident::new(observed_js_fn, Span::call_site()); let rs_name_str = from_pascal_to_snake(observed_js_fn); let rs_name_ident = Ident::new(&rs_name_str, Span::call_site());-- let struct_ident = struct_name;+ let component_name = Ident::new(&format!("{}Component", struct_name), Span::call_site()); let expanded = quote! { #[callback(#js_callback_ident)] fn #rs_name_ident() -> &'static [&'static str] {- #struct_ident::observed_attributes()+ #component_name::internal_observed_attributes() } }; @@ -216,37 +191,6 @@ fn store_name(struct_name: &Ident) -> String { store_name } -fn generate_component_store(struct_name: Ident) -> proc_macro2::TokenStream {- // Generate only the per-component store (HashMap) here.- let store_name = store_name(&struct_name);- let store_ident = Ident::new(&store_name, Span::call_site());- let store_hash_map_impl = storage_for_struct_impl(&struct_name);-- let expanded = quote! {- std::thread_local! {- pub static #store_ident: std::cell::RefCell<std::collections::HashMap<String, ::kaja_web::SharedComponent<#struct_name>>> =- std::cell::RefCell::new(std::collections::HashMap::new());- }- #store_hash_map_impl-- impl #struct_name {- pub fn get_component(- component_id: &str,- ) -> Option<Rc<RefCell<#struct_name>>> {- #store_ident.with(|store| store.borrow().get(component_id).cloned())- }-- pub fn component_exists(- component_id: &str,- ) -> bool {- #store_ident.with(|store| store.borrow().contains_key(component_id))- }- }- };-- expanded-}- fn from_pascal_to_snake(fn_name: &str) -> String { let mut result = String::new(); @@ -283,11 +227,15 @@ fn gen_rust_self(struct_item: ItemStruct, js_name: Ident) -> proc_macro2::TokenS kebab_case_name = kebab_case_name.replace("_", "-"); let rust_ident = Ident::new(&rust_name, Span::call_site());+ let struct_ref_container =+ Ident::new(&format!("{}RefContainer", struct_ident), Span::call_site()); let expanded = quote! { #[callback(#js_name)] #[allow(non_snake_case)] pub fn #rust_ident(current_id: String) -> String { // init_component_state_struct_name, returns id for the instance of the element+ log!("Compoent generation previous id:", current_id.clone());+ let #var_ident = #struct_ident::default(); let key = if current_id.is_empty() {@@ -297,11 +245,17 @@ fn gen_rust_self(struct_item: ItemStruct, js_name: Ident) -> proc_macro2::TokenS current_id }; + let component = std::rc::Rc::new(std::cell::RefCell::new(#struct_ref_container {+ id: key.clone(),+ attribute_values: std::rc::Rc::new(std::cell::RefCell::new(#var_ident)),+ }));+ #store_ident.with(|store| {- store- .borrow_mut()- .insert(key.clone(), std::rc::Rc::new(std::cell::RefCell::new(#var_ident)));+ store.borrow_mut().insert(key.clone(), component); });++ log!("created key:", key.clone());+ key } };@@ -333,42 +287,42 @@ fn create_class( let js_init = String::from( r#"- return class extends HTMLElement {- constructor() {- super();- let currentId = this.getAttribute("id");+ return class extends HTMLElement {+ constructor() {+ super();+ let currentId = this.getAttribute("id"); - if (currentId === null) {- currentId = "";- }+ if (currentId === null) {+ currentId = "";+ } - let resolvedId = __INIT_SELF_CALLBACK(currentId);+ let resolvedId = __INIT_SELF_CALLBACK(currentId); - if (!currentId) {- this.setAttribute("id", resolvedId);+ if (!currentId) {+ this.setAttribute("id", resolvedId);+ } }- } - connectedCallback() {- let componentid = this.getAttribute("id");- __ATTACH_CALLBACK(componentid);- }+ connectedCallback() {+ let componentid = this.getAttribute("id");+ __ATTACH_CALLBACK(componentid);+ } - disconnectedCallback() {- let componentid = this.getAttribute("id");- __DETACH_CALLBACK(componentid);- }+ disconnectedCallback() {+ let componentid = this.getAttribute("id");+ __DETACH_CALLBACK(componentid);+ } - attributeChangedCallback(name, oldValue, newValue) {- let componentid = this.getAttribute("id");- __ATTRIBUTE_CHANGED_CALLBACK(componentid, name, oldValue === null ? "" : String(oldValue), newValue === null ? "" : String(newValue));- }+ attributeChangedCallback(name, oldValue, newValue) {+ let componentid = this.getAttribute("id");+ __ATTRIBUTE_CHANGED_CALLBACK(componentid, name, oldValue === null ? "" : String(oldValue), newValue === null ? "" : String(newValue));+ } - static get observedAttributes() {- return __OBSERVED_CALLBACK();+ static get observedAttributes() {+ return __OBSERVED_CALLBACK();+ } }- }- "#,+ "#, ); let js_init_with_state = js_init.replace("__INIT_SELF_CALLBACK", #init_self_fn);@@ -398,3 +352,186 @@ fn create_class( expanded }++fn create_component(struct_item: &ItemStruct) -> proc_macro2::TokenStream {+ let struct_ident = &struct_item.ident;+ let struct_ref_container =+ Ident::new(&format!("{}RefContainer", struct_ident), Span::call_site());+ let component_ident = Ident::new(&format!("{}Component", struct_ident), Span::call_site());++ let store_name_str = store_name(struct_ident);+ let store_ident = Ident::new(&store_name_str, Span::call_site());++ let (setter_arms, getter_arms, observed_attributes) = match &struct_item.fields {+ Fields::Named(fields) => {+ let setter_arms = fields.named.iter().filter_map(|field| {+ let field_ident = field.ident.as_ref()?;+ let field_name = field_ident.to_string();+ let field_ty = &field.ty;+ Some(quote! {+ #field_name => {+ self.#field_ident = value.parse::<#field_ty>().map_err(|_| ::kaja_web::Error)?;+ }+ })+ });++ let getter_arms = fields.named.iter().filter_map(|field| {+ let field_ident = field.ident.as_ref()?;+ let field_name = field_ident.to_string();+ Some(quote! {+ #field_name => Some(self.#field_ident.to_string())+ })+ });++ let observed_attributes = fields.named.iter().filter_map(|field| {+ let field_ident = field.ident.as_ref()?;+ let field_name = LitStr::new(&field_ident.to_string(), Span::call_site());+ Some(quote! { #field_name })+ });++ (+ quote! { #(#setter_arms,)* },+ quote! { #(#getter_arms,)* },+ quote! { #(#observed_attributes,)* },+ )+ }+ _ => (quote! {}, quote! {}, quote! {}),+ };++ let struct_name_lit = LitStr::new(&struct_ident.to_string(), Span::call_site());++ let expanded = quote! {+ thread_local! {+ static #store_ident: std::cell::RefCell<std::collections::HashMap<String, std::rc::Rc<std::cell::RefCell<#struct_ref_container>>>> =+ std::cell::RefCell::new(std::collections::HashMap::new());+ }++ impl #struct_ident {+ fn set_test_attribute(&mut self, name: &str, value: &str) {+ let _ = self.try_set_test_attribute(name, value);+ }++ fn try_set_test_attribute(&mut self, name: &str, value: &str) -> ::kaja_web::Result<()> {+ match name {+ #setter_arms+ _ => {+ error!("No field", name, "in", #struct_name_lit, "can't set it to", value);+ return Err(::kaja_web::Error);+ }+ }++ Ok(())+ }++ fn get_attribute(&self, name: &str) -> Option<String> {+ match name {+ #getter_arms+ _ => {+ error!("No field", name, "in", #struct_name_lit, "can't get it");+ None+ }+ }+ }+ }++ #[derive(Default)]+ struct #struct_ref_container {+ id: String,+ attribute_values: std::rc::Rc<std::cell::RefCell<#struct_ident>>,+ }++ impl #struct_ref_container {+ fn create_component(id: &str) {+ let handle = std::rc::Rc::new(std::cell::RefCell::new(#struct_ref_container {+ id: id.to_string(),+ attribute_values: std::rc::Rc::new(std::cell::RefCell::new(#struct_ident::default())),+ }));++ Self::register_component(handle.clone());+ let component = #component_ident::get_component(&id);++ if component.is_none() {+ error!("Failed to register component.");+ return;+ }++ let component_impl = component.unwrap();+ component_impl.connected();+ }++ fn register_component(component: std::rc::Rc<std::cell::RefCell<#struct_ref_container>>) {+ let id = component.borrow().id.clone();++ #store_ident.with(|components| {+ components.borrow_mut().insert(id, component);+ });+ }+ }++ #[derive(Default)]+ struct #component_ident {+ handle: std::rc::Rc<std::cell::RefCell<#struct_ref_container>>,+ }++ impl #component_ident {+ fn get_component(id: &str) -> Option<#component_ident> {+ let handle = #store_ident.with(|components| components.borrow().get(id).cloned());++ if !handle.is_some() {+ return None;+ }++ Some(#component_ident {+ handle: handle.unwrap(),+ })+ }++ fn internal_observed_attributes() -> &'static [&'static str] {+ &[#observed_attributes]+ }++ fn internal_attributes_update(&self, name: &str, _old_value: &str, new_value: &str) {+ let handle = self.handle.borrow();+ let mut attr = handle.attribute_values.borrow_mut();+ attr.set_test_attribute(name, new_value);+ }++ fn set_attribute(&self, name: &str, value: &str) {+ let id = self.get_id();+ let element = get_element_by_id(&id);++ if element.is_none() {+ error!("Element not found for id:", id, "in set_attribute");+ return;+ }++ if !Self::internal_observed_attributes().contains(&name) {+ error!("Attribute not observed:", name, "in set_attribute");+ return;+ }++ let res = element.unwrap().set_attribute(name, value);++ if res.is_err() {+ error!("Failed to set attribute:", name, "in set_attribute");+ }+ }++ fn get_attribute(&self, name: &str) -> Option<String> {+ let handle = self.handle.borrow();+ let attr = handle.attribute_values.borrow();+ attr.get_attribute(name)+ }++ fn get_attributes(&self) -> std::rc::Rc<std::cell::RefCell<#struct_ident>> {+ self.handle.borrow().attribute_values.clone()+ }++ fn get_id(&self) -> String {+ self.handle.borrow().id.clone()+ }+ }+ };++ expanded+}