Proc macro

This commit is contained in:
Jeremy Wall 2022-10-05 18:36:26 -04:00
parent ef59c1769c
commit 4daaa03093
6 changed files with 393 additions and 112 deletions

View File

@ -1,41 +1,2 @@
[package]
name = "web-component-rs"
version = "0.1.0"
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-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",
]
[workspace]
members = ["web-component", "derive"]

17
derive/Cargo.toml Normal file
View File

@ -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"]

126
derive/src/lib.rs Normal file
View File

@ -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<WebComponentHandle<#struct_name>> {
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<dyn FnMut() -> 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::<Function>(),
&Self::observed_attributes(),
)?
.dyn_into()?;
Ok(WebComponentHandle {
element_constructor: element,
impl_handle: constructor_handle,
})
}
}
};
TokenStream::from(expanded)
}

200
web-component/Cargo.lock generated Normal file
View File

@ -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",
]

45
web-component/Cargo.toml Normal file
View File

@ -0,0 +1,45 @@
[package]
name = "web-component-rs"
version = "0.1.0"
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]
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",
]

View File

@ -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<T> = std::result::Result<T, JsValue>;
// 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<WebComponentHandle<Self>> {
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<dyn FnMut() -> 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::<Function>(),
&Self::observed_attributes(),
)?
.dyn_into()?;
Ok(WebComponentHandle {
element_constructor: element,
impl_handle: constructor_handle,
})
}
}
#[wasm_bindgen]
impl MyElementImpl {
#[wasm_bindgen(constructor)]