diff --git a/Cargo.toml b/Cargo.toml index afaf819..90647d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,41 +1,2 @@ -[package] -name = "web-component-rs" -version = "0.1.0" -edition = "2021" -author = "Jeremy Wall " - -[lib] -crate-type = ["cdylib", "rlib"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies.wasm-bindgen-test] -version = "0.3" - -[dependencies.wasm-bindgen] -version = "= 0.2.81" - -[dependencies.js-sys] -version = "0.3" - -[dependencies.web-sys] -version = "0.3" -features = [ - "CustomElementRegistry", - "Document", - #"DocumentFragment", - "KeyboardEvent", - "Event", - "EventTarget", - "Element", - "Node", - "Text", - "HtmlBaseElement", - "HtmlElement", - "HtmlTemplateElement", - "HtmlSlotElement", - "Node", - "ShadowRoot", - "ShadowRootInit", - "ShadowRootMode", - "Window", -] \ No newline at end of file +[workspace] +members = ["web-component", "derive"] \ No newline at end of file diff --git a/derive/Cargo.toml b/derive/Cargo.toml new file mode 100644 index 0000000..7ed1376 --- /dev/null +++ b/derive/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "web-component-derive" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +quote = "1.0" +proc-macro2 = "1.0" + +[dependencies.syn] +version = "1.0.101" +features = ["full"] \ No newline at end of file diff --git a/derive/src/lib.rs b/derive/src/lib.rs new file mode 100644 index 0000000..b2cb91c --- /dev/null +++ b/derive/src/lib.rs @@ -0,0 +1,126 @@ +// Copyright 2022 Jeremy Wall (Jeremy@marzhilsltudios.com) +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::quote; +use syn::{parse_macro_input, AttributeArgs, ItemStruct, Lit, LitStr, Meta, NestedMeta}; + +#[proc_macro_attribute] +pub fn web_component(attr: TokenStream, item: TokenStream) -> TokenStream { + // TODO(jwall): Attrs for class name and element name + // Gather our attributes + let args = parse_macro_input!(attr as AttributeArgs); + let mut class_name = None; + let mut element_name = None; + for arg in args { + if let NestedMeta::Meta(Meta::NameValue(nv)) = arg { + if nv.path.is_ident("class_name") { + if let Lit::Str(nm) = nv.lit { + class_name = Some(nm); + } + } else if nv.path.is_ident("element_name") { + if let Lit::Str(nm) = nv.lit { + element_name = Some(nm); + } + } + } + } + let element_name = element_name + .map(|n| n.token()) + .unwrap_or_else(|| LitStr::new("", Span::call_site()).token()); + let class_name = class_name + .map(|n| n.token()) + .unwrap_or_else(|| LitStr::new("", Span::call_site()).token()); + let item_struct = parse_macro_input!(item as ItemStruct); + let struct_name = item_struct.ident.clone(); + let expanded = quote! { + #[wasm_bindgen] + #item_struct + + impl #struct_name { + pub fn element_name() -> &'static str { + #element_name + } + + pub fn class_name() -> &'static str { + #class_name + } + + pub fn define() -> Result> { + use js_sys::Function; + use web_sys::{window, Element, HtmlElement}; + let registry = web_sys::window().unwrap().custom_elements(); + let maybe_element = registry.get(Self::element_name()); + if maybe_element.is_truthy() { + return Err("Custom Element has already been defined".into()); + } + let body = format!( + "class {name} extends HTMLElement {{ + constructor() {{ + super(); + this._impl = impl(); + }} + + connectedCallback() {{ + this._impl.connected_impl(this); + }} + + disconnectedCallback() {{ + this._impl.disconnected_impl(this); + }} + + static get observedAttributes() {{ + console.log('observed attributes: ', attrs); + return attrs; + }} + + adoptedCallback() {{ + console.log('In adoptedCallback'); + this._impl.adopted_impl(this); + }} + + attributeChangedCallback(name, oldValue, newValue) {{ + this._impl.attribute_changed_impl(this, name, oldValue, newValue); + }} +}} +customElements.define(\"{element_name}\", {name}); +var element = customElements.get(\"{element_name}\"); +return element;", + name = Self::class_name(), + element_name = Self::element_name(), + ); + let fun = Function::new_with_args("impl, attrs", &body); + let f: Box Self> = Box::new(|| { + let obj = Self::new(); + obj + }); + let constructor_handle = Closure::wrap(f); + let element = fun + .call2( + &window().unwrap(), + constructor_handle.as_ref().unchecked_ref::(), + &Self::observed_attributes(), + )? + .dyn_into()?; + Ok(WebComponentHandle { + element_constructor: element, + impl_handle: constructor_handle, + }) + } + } + }; + + TokenStream::from(expanded) +} diff --git a/web-component/Cargo.lock b/web-component/Cargo.lock new file mode 100644 index 0000000..2874df0 --- /dev/null +++ b/web-component/Cargo.lock @@ -0,0 +1,200 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bumpalo" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "js-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "proc-macro2" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd7356a8122b6c4a24a82b278680c73357984ca2fc79a0f9fa6dea7dced7c58" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "syn" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" + +[[package]] +name = "wasm-bindgen" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b30cf2cba841a812f035c40c50f53eb9c56181192a9dd2c71b65e6a87a05ba" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ad594bf33e73cafcac2ae9062fc119d4f75f9c77e25022f91c9a64bd5b6463" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "web-component-rs" +version = "0.1.0" +dependencies = [ + "js-sys", + "wasm-bindgen", + "wasm-bindgen-test", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +dependencies = [ + "js-sys", + "wasm-bindgen", +] diff --git a/web-component/Cargo.toml b/web-component/Cargo.toml new file mode 100644 index 0000000..002ca28 --- /dev/null +++ b/web-component/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "web-component-rs" +version = "0.1.0" +edition = "2021" +author = "Jeremy Wall " + +[lib] +crate-type = ["cdylib", "rlib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +web-component-derive = {path = "../derive"} + +[dependencies.wasm-bindgen-test] +version = "0.3" + +[dependencies.wasm-bindgen] +version = "= 0.2.81" + +[dependencies.js-sys] +version = "0.3" + +[dependencies.web-sys] +version = "0.3" +features = [ + "CustomElementRegistry", + "Document", + #"DocumentFragment", + "KeyboardEvent", + "Event", + "EventTarget", + "Element", + "Node", + "Text", + "HtmlBaseElement", + "HtmlElement", + "HtmlTemplateElement", + "HtmlSlotElement", + "Node", + "ShadowRoot", + "ShadowRootInit", + "ShadowRootMode", + "Window", +] \ No newline at end of file diff --git a/src/lib.rs b/web-component/src/lib.rs similarity index 71% rename from src/lib.rs rename to web-component/src/lib.rs index f74fb55..810bbbf 100644 --- a/src/lib.rs +++ b/web-component/src/lib.rs @@ -2,6 +2,8 @@ use js_sys::Function; use wasm_bindgen::{convert::IntoWasmAbi, prelude::*, JsCast, JsValue}; use web_sys::{window, Element, HtmlElement}; +use web_component_derive::web_component; + type Result = std::result::Result; // TODO(jwall): Trait methods can't be exported out to js yet so we'll need a wrapper object or we'll need to `Derive` this api in a prop-macro. @@ -20,82 +22,12 @@ mod tests { use web_sys::Text; wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); - #[wasm_bindgen] + #[web_component(class_name = "MyElement", element_name = "my-element")] #[derive(Default, Debug)] pub struct MyElementImpl {} impl CustomElementImpl for MyElementImpl {} - impl MyElementImpl { - pub fn class_name() -> &'static str { - "MyElement" - } - - pub fn element_name() -> &'static str { - "my-element" - } - - pub fn define() -> Result> { - let registry = window().unwrap().custom_elements(); - let maybe_element = registry.get(Self::element_name()); - if maybe_element.is_truthy() { - return Err("Custom Element has already been defined".into()); - } - let body = format!( - "class {name} extends HTMLElement {{ - constructor() {{ - super(); - this._impl = impl(); - }} - - connectedCallback() {{ - this._impl.connected_impl(this); - }} - - disconnectedCallback() {{ - this._impl.disconnected_impl(this); - }} - - static get observedAttributes() {{ - console.log('observed attributes: ', attrs); - return attrs; - }} - - adoptedCallback() {{ - console.log('In adoptedCallback'); - this._impl.adopted_impl(this); - }} - - attributeChangedCallback(name, oldValue, newValue) {{ - this._impl.attribute_changed_impl(this, name, oldValue, newValue); - }} -}} -customElements.define(\"{element_name}\", {name}); -var element = customElements.get(\"{element_name}\"); -return element;", - name = Self::class_name(), - element_name = Self::element_name(), - ); - let fun = Function::new_with_args("impl, attrs", &body); - let f: Box Self> = Box::new(|| { - let obj = Self::new(); - obj - }); - let constructor_handle = Closure::wrap(f); - let element = fun - .call2( - &window().unwrap(), - constructor_handle.as_ref().unchecked_ref::(), - &Self::observed_attributes(), - )? - .dyn_into()?; - Ok(WebComponentHandle { - element_constructor: element, - impl_handle: constructor_handle, - }) - } - } - #[wasm_bindgen] impl MyElementImpl { #[wasm_bindgen(constructor)]