Clone in a terminal:
Commit f7d721a
Johan Mattsson - 2026-06-28T11:56:43+00:00
f7d721a3e6e0a0e60aab60f5283c8b6ab4cd4849johan.mattsson.m@gmail.com

Init state function

Changed files (3)

Diff

diff --git a/src/init.rs b/src/init.rsnew file mode 100644index 0000000..6ea5a9b--- /dev/null+++ b/src/init.rs@@ -0,0 +1,133 @@+use proc_macro as pm;+use proc_macro2::Span;+use quote::quote;+use syn::parse2;+use syn::{Ident, ItemStruct, LitStr};++pub fn component_impl(attr: pm::TokenStream, item: pm::TokenStream) -> pm::TokenStream {+    // Parse this #[component("my-tag")]) for a struct+    let html_tag_ts = proc_macro2::TokenStream::from(attr.clone());+    let tag_lit = match parse2::<LitStr>(html_tag_ts) {+        Ok(lit) => lit,+        Err(err) => return err.to_compile_error().into(),+    };++    if tag_lit.value().is_empty() {+        return syn::Error::new_spanned(+            &tag_lit,+            "expected a tag name or component: #[component(\"example-component-tag\")]",+        )+        .to_compile_error()+        .into();+    }++    // Parse the annotated item as a struct+    let component_struct_ts = proc_macro2::TokenStream::from(item.clone());+    let component_struct_item = match syn::parse2::<ItemStruct>(component_struct_ts.clone()) {+        Ok(s) => s,+        Err(_) => {+            return syn::Error::new_spanned(+                component_struct_ts,+                "the #[component] attribute can only be applied to structs",+            )+            .to_compile_error()+            .into();+        }+    };++    let struct_name_ident = &component_struct_item.ident;+    let create_html_component_fn_name = format!("createComponent{}", struct_name_ident);+    let fn_create_ident = Ident::new(&create_html_component_fn_name, Span::call_site());++    let component_rs_state_fn = format!("initComponentState{}", struct_name_ident);+    let component_rs_state_fn_ident = Ident::new(&component_rs_state_fn, Span::call_site());++    let create_fn = generate_create_component_fn(+        fn_create_ident,+        tag_lit.clone(),+        component_rs_state_fn.as_str(),+    );++    let rs_state_fn = generate_default_component_rs_fn(component_rs_state_fn_ident);++    let expanded = quote! {+        #component_struct_item+        #rs_state_fn+        #create_fn+    };++    expanded.into()+}++fn generate_default_component_rs_fn(name: Ident) -> proc_macro2::TokenStream {+    let expanded = quote! {+        #[allow(non_snake_case)]+        pub fn #name() { // initComponentStateStructName+        }+    };++    expanded+}++fn generate_create_component_fn(+    name: Ident,+    tag: LitStr,+    init_state_fn: &str,+) -> proc_macro2::TokenStream {+    let js_init = String::from(+        r#"+            let componentId = -1;++            return class extends HTMLElement {+                connectedCallback() {+                    if (componentId == -1) {+                        componentId = __INIT_CALLBACK;+                    }++                    counterComponentConnect(this, componentId);+                }++                disconnectedCallback() {+                    counterComponentDisconnect(this, componentId);+                }++                attributeChangedCallback(name, oldValue, newValue) {+                    //callbacks.attributeChanged(this, name, oldValue, newValue);+                }++                static get observedAttributes() {+                    return [];+                }+            }+    "#,+    );++    let js_init_with_state = js_init.replace("__INIT_CALLBACK", #init_state_fn);++    let expanded = quote! {+        #[allow(non_snake_case)]+        pub fn #name() { // createComponentTheStructName+            let wasm_id = 0;+            let class_init_fn = format!()+            let component = Function::new_no_args(r#"js_init_with_state"#);++            let class = component.call0(&JsValue::NULL);++            if class.is_err() {+                error!("Failed to register class for {}", #tag);+                return;+            }++            let class_fn = class.unwrap();+            let registry = web_sys::window().unwrap().custom_elements();++            let result = registry.define(#tag, &class_fn.unchecked_into());++            if result.is_err() {+                error!("Failed to register component {}", #tag);+            }+        }+    };++    expanded+}diff --git a/src/lib.rs b/src/lib.rsindex a07249e..3d2f38e 100644--- a/src/lib.rs+++ b/src/lib.rs@@ -1,106 +1,8 @@-use proc_macro2::Span;-use proc_macro2::TokenStream;-use quote::quote;-use syn::parse2;-use syn::{Ident, ItemStruct, LitStr};+mod init; -#[proc_macro_attribute]-pub fn component(-    attr: proc_macro::TokenStream,-    item: proc_macro::TokenStream,-) -> proc_macro::TokenStream {-    let html_tag_name = proc_macro2::TokenStream::from(attr.clone());-    let tag_value = parse2::<LitStr>(html_tag_name.clone());--    if tag_value.is_empty() {-        return syn::Error::new_spanned(-            tag_value,-            "expected a tag name or component: #[component(example-component-tag)]",-        )-        .to_compile_error()-        .into();-    }--    let component_struct_name = proc_macro2::TokenStream::from(item.clone());-    let component_struct_item = match syn::parse2::<ItemStruct>(component_struct_name.clone()) {-        Ok(s) => s,-        Err(_) => {-            return syn::Error::new_spanned(-                component_struct_name,-                "the #[component] attribute can only be applied to structs",-            )-            .to_compile_error()-            .into();-        }-    };--    let struct_name_ident = &component_struct_item.ident;-    let create_html_component_fn_name = format!("createComponent{}", struct_name_ident);-    let fn_create_ident = Ident::new(&create_html_component_fn_name, Span::call_site());-    let html_tag = LitStr::new(&tag_value, Span::call_site());--    let create_fn = generate_create_component_fn(fn_create_ident);--    let expanded = quote! {-        #component_struct_item-        #create_fn-    };--    TokenStream::from(expanded)-}--fn generate_create_component_fn(name: Ident) -> TokenStream {-    let expanded = quote! {-        #[allow(non_snake_case)]-        pub fn #name() { // createComponentTheStructName--            let wasm_id = 0;--            let component = Function::new_no_args(-                r#"-                let componentId = -1;+use proc_macro as pm; -                return class extends HTMLElement {-                    connectedCallback() {-                        if (componentId == -1) {-                            componentId = counterInit();-                        }--                        counterComponentConnect(this, componentId);-                    }--                    disconnectedCallback() {-                        counterComponentDisconnect(this, componentId);-                    }--                    attributeChangedCallback(name, oldValue, newValue) {-                        //callbacks.attributeChanged(this, name, oldValue, newValue);-                    }--                    static get observedAttributes() {-                        return [];-                    }-                }-            "#,-            );--            let class = component.call0(&JsValue::NULL);--            if class.is_err() {-                error!("Failed to register class for", tag_name);-                return;-            }--            let class_fn = class.unwrap();-            let registry = web_sys::window().unwrap().custom_elements();--            let result = registry.define(tag_name, &class_fn.unchecked_into());--            if result.is_err() {-                error!("Failed to register component", tag_name);-            }-        }-    };--    expanded+#[proc_macro_attribute]+pub fn component(attr: pm::TokenStream, item: pm::TokenStream) -> pm::TokenStream {+    init::component_impl(attr, item) }diff --git a/src/prelude.rs b/src/prelude.rsdeleted file mode 100644index e69de29..0000000--- a/src/prelude.rs+++ /dev/null

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112