├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── customized_builtin │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.sh │ ├── index.html │ ├── runserver │ └── src │ │ └── lib.rs ├── vanilla │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.sh │ ├── index.html │ ├── runserver │ └── src │ │ └── lib.rs └── yew │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.sh │ ├── component_style.css │ ├── index.html │ ├── runserver │ └── src │ ├── component.rs │ └── lib.rs └── src ├── lib.rs └── make_custom_element.js /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "bumpalo" 5 | version = "3.7.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" 8 | 9 | [[package]] 10 | name = "cfg-if" 11 | version = "1.0.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 14 | 15 | [[package]] 16 | name = "custom-elements" 17 | version = "0.2.0" 18 | dependencies = [ 19 | "js-sys", 20 | "wasm-bindgen", 21 | "web-sys", 22 | ] 23 | 24 | [[package]] 25 | name = "js-sys" 26 | version = "0.3.52" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "ce791b7ca6638aae45be056e068fc756d871eb3b3b10b8efa62d1c9cec616752" 29 | dependencies = [ 30 | "wasm-bindgen", 31 | ] 32 | 33 | [[package]] 34 | name = "lazy_static" 35 | version = "1.4.0" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 38 | 39 | [[package]] 40 | name = "log" 41 | version = "0.4.14" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 44 | dependencies = [ 45 | "cfg-if", 46 | ] 47 | 48 | [[package]] 49 | name = "proc-macro2" 50 | version = "1.0.28" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" 53 | dependencies = [ 54 | "unicode-xid", 55 | ] 56 | 57 | [[package]] 58 | name = "quote" 59 | version = "1.0.9" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 62 | dependencies = [ 63 | "proc-macro2", 64 | ] 65 | 66 | [[package]] 67 | name = "syn" 68 | version = "1.0.74" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" 71 | dependencies = [ 72 | "proc-macro2", 73 | "quote", 74 | "unicode-xid", 75 | ] 76 | 77 | [[package]] 78 | name = "unicode-xid" 79 | version = "0.2.2" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 82 | 83 | [[package]] 84 | name = "wasm-bindgen" 85 | version = "0.2.75" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "b608ecc8f4198fe8680e2ed18eccab5f0cd4caaf3d83516fa5fb2e927fda2586" 88 | dependencies = [ 89 | "cfg-if", 90 | "wasm-bindgen-macro", 91 | ] 92 | 93 | [[package]] 94 | name = "wasm-bindgen-backend" 95 | version = "0.2.75" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "580aa3a91a63d23aac5b6b267e2d13cb4f363e31dce6c352fca4752ae12e479f" 98 | dependencies = [ 99 | "bumpalo", 100 | "lazy_static", 101 | "log", 102 | "proc-macro2", 103 | "quote", 104 | "syn", 105 | "wasm-bindgen-shared", 106 | ] 107 | 108 | [[package]] 109 | name = "wasm-bindgen-macro" 110 | version = "0.2.75" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "171ebf0ed9e1458810dfcb31f2e766ad6b3a89dbda42d8901f2b268277e5f09c" 113 | dependencies = [ 114 | "quote", 115 | "wasm-bindgen-macro-support", 116 | ] 117 | 118 | [[package]] 119 | name = "wasm-bindgen-macro-support" 120 | version = "0.2.75" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "6c2657dd393f03aa2a659c25c6ae18a13a4048cebd220e147933ea837efc589f" 123 | dependencies = [ 124 | "proc-macro2", 125 | "quote", 126 | "syn", 127 | "wasm-bindgen-backend", 128 | "wasm-bindgen-shared", 129 | ] 130 | 131 | [[package]] 132 | name = "wasm-bindgen-shared" 133 | version = "0.2.75" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "2e0c4a743a309662d45f4ede961d7afa4ba4131a59a639f29b0069c3798bbcc2" 136 | 137 | [[package]] 138 | name = "web-sys" 139 | version = "0.3.52" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "01c70a82d842c9979078c772d4a1344685045f1a5628f677c2b2eab4dd7d2696" 142 | dependencies = [ 143 | "js-sys", 144 | "wasm-bindgen", 145 | ] 146 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "custom-elements" 3 | version = "0.2.1" 4 | authors = ["Greg Johnston "] 5 | edition = "2018" 6 | description = "A CustomElement trait to create Rust/WASM Web Components/Custom Elements easily without writing any JavaScript." 7 | license = "Apache-2.0/MIT" 8 | readme = "./README.md" 9 | repository = "https://github.com/gbj/custom-elements" 10 | homepage = "https://github.com/gbj/custom-elements" 11 | 12 | [dependencies] 13 | wasm-bindgen = "0.2" 14 | js-sys = "0.3" 15 | 16 | [dependencies.web-sys] 17 | version = "0.3" 18 | features = [ 19 | "Document", 20 | "Element", 21 | "HtmlElement", 22 | "Node", 23 | "ShadowRoot", 24 | "ShadowRootInit", 25 | "ShadowRootMode", 26 | "Window" 27 | ] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Greg Johnston 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![crates.io](https://img.shields.io/crates/v/custom_elements.svg)](https://crates.io/crates/custom_elements) 2 | [![docs.rs](https://docs.rs/custom_elements/badge.svg)](https://docs.rs/custom_elements) 3 | 4 | A framework-agnostic CustomElement trait to create Rust/WASM Web Components/Custom Elements easily without writing any JavaScript. 5 | 6 | # Overview 7 | 8 | The [Web Components](https://github.com/WICG/webcomponents) standard creates a browser feature that allows you to create reusable components, called Custom Elements. (People often use the label “Web Components” when they mean Custom Elements in particular; the Web Components standard also includes Shadow DOM and HTML Templates.) 9 | 10 | While `web_sys` exposes the browser’s [CustomElementRegistry](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.CustomElementRegistry.html) interface, it can be hard to use. Creating a Custom Element requires calling `customElements.define()` and passing it an ES2015 class that `extends HTMLElement`, which is not currently possible to do directly from Rust. 11 | 12 | This crate provides a `CustomElement` trait that, when implemented, allows you to encapsulate any Rust structure as a reusable web component without writing any JavaScript. In theory it should be usable with any Rust front-end framework; the `examples` directory contains examples for Yew and for a vanilla Rust/WASM component. 13 | 14 | ```rust 15 | impl CustomElement for MyWebComponent { 16 | fn inject_children(&mut self, this: &HtmlElement) { 17 | inject_style(&this, "p { color: green; }"); 18 | let node = self.view(); 19 | this.append_child(&node).unwrap_throw(); 20 | } 21 | 22 | fn observed_attributes() -> &'static [&'static str] { 23 | &["name"] 24 | } 25 | 26 | fn attribute_changed_callback( 27 | &mut self, 28 | _this: &HtmlElement, 29 | name: String, 30 | _old_value: Option, 31 | new_value: Option, 32 | ) { 33 | if name == "name" { 34 | /* do something... */ 35 | } 36 | } 37 | 38 | fn connected_callback(&mut self, _this: &HtmlElement) { 39 | log("connected"); 40 | } 41 | 42 | fn disconnected_callback(&mut self, _this: &HtmlElement) { 43 | log("disconnected"); 44 | } 45 | 46 | fn adopted_callback(&mut self, _this: &HtmlElement) { 47 | log("adopted"); 48 | } 49 | } 50 | 51 | #[wasm_bindgen] 52 | pub fn define_custom_elements() { 53 | MyWebComponent::define("my-component"); 54 | } 55 | ``` 56 | 57 | ## Shadow DOM 58 | 59 | By default, these custom elements use the Shadow DOM (in “open” mode) to encapsulate the styles and content of the element. You can override that choice simply by implementing the `shadow` method and returning `false`: 60 | 61 | ```rust 62 | fn shadow() -> bool { 63 | false 64 | } 65 | ``` 66 | 67 | ## Lifecycle Methods 68 | 69 | You can implement each of the custom element’s lifecycle callbacks. Each of the callbacks is passed both the component for which the trait is being implemented, and the `HtmlElement` of the custom element. 70 | 71 | ```rust 72 | 73 | fn connected_callback(&mut self, this: &HtmlElement) { 74 | log("connected"); 75 | } 76 | 77 | fn disconnected_callback(&mut self, this: &HtmlElement) { 78 | log("disconnected"); 79 | } 80 | 81 | fn adopted_callback(&mut self, this: &HtmlElement) { 82 | log("adopted"); 83 | } 84 | 85 | fn attribute_changed_callback( 86 | &mut self, 87 | this: &HtmlElement, 88 | name: String, 89 | old_value: Option, 90 | new_value: Option, 91 | ) { 92 | if name == "name" { 93 | // do something 94 | } 95 | } 96 | ``` 97 | 98 | ## Using Rust Frameworks 99 | 100 | The minimum needed to implement `CustomElement` is some way to inject children into the custom element. It’s also generally helpful to have it respond to changes in its attributes via the `attribute_changed_callback`. Depending on the framework, these may be more or less difficult to accomplish; in particular, for Elm-inspired frameworks you may need to create a wrapper that owns some way of updating the app’s state. 101 | 102 | See the Yew example for an example of how to work with a framework’s API. 103 | 104 | ## Customized built-in elements 105 | 106 | Custom elements can either be autonomous (``) or customized built-in elements (`

). This crate offers support for creating customized built-in elements via the `[superclass](https://docs.rs/custom-elements/0.2.0/custom_elements/trait.CustomElement.html#method.superclass)` method. 107 | 108 | # Resources 109 | 110 | This is a fairly minimal wrapper for the Custom Elements API. The following MDN sources should give you more than enough information to start creating custom elements: 111 | - [Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components) 112 | - [Using custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) 113 | - [Using the lifecycle callbacks](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks) 114 | 115 | # Running the Examples 116 | 117 | The examples use `wasm-pack` and a simple Python server. You should be able to run them with a simple 118 | 119 | `./build.sh && ./runserver` 120 | -------------------------------------------------------------------------------- /examples/customized_builtin/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "bumpalo" 5 | version = "3.7.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" 8 | 9 | [[package]] 10 | name = "cfg-if" 11 | version = "1.0.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 14 | 15 | [[package]] 16 | name = "custom-elements" 17 | version = "0.2.0" 18 | dependencies = [ 19 | "js-sys", 20 | "wasm-bindgen", 21 | "web-sys", 22 | ] 23 | 24 | [[package]] 25 | name = "customized-builtin-element" 26 | version = "0.1.0" 27 | dependencies = [ 28 | "custom-elements", 29 | "js-sys", 30 | "wasm-bindgen", 31 | "web-sys", 32 | ] 33 | 34 | [[package]] 35 | name = "js-sys" 36 | version = "0.3.52" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "ce791b7ca6638aae45be056e068fc756d871eb3b3b10b8efa62d1c9cec616752" 39 | dependencies = [ 40 | "wasm-bindgen", 41 | ] 42 | 43 | [[package]] 44 | name = "lazy_static" 45 | version = "1.4.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 48 | 49 | [[package]] 50 | name = "log" 51 | version = "0.4.14" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 54 | dependencies = [ 55 | "cfg-if", 56 | ] 57 | 58 | [[package]] 59 | name = "proc-macro2" 60 | version = "1.0.28" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" 63 | dependencies = [ 64 | "unicode-xid", 65 | ] 66 | 67 | [[package]] 68 | name = "quote" 69 | version = "1.0.9" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 72 | dependencies = [ 73 | "proc-macro2", 74 | ] 75 | 76 | [[package]] 77 | name = "syn" 78 | version = "1.0.74" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" 81 | dependencies = [ 82 | "proc-macro2", 83 | "quote", 84 | "unicode-xid", 85 | ] 86 | 87 | [[package]] 88 | name = "unicode-xid" 89 | version = "0.2.2" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 92 | 93 | [[package]] 94 | name = "wasm-bindgen" 95 | version = "0.2.75" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "b608ecc8f4198fe8680e2ed18eccab5f0cd4caaf3d83516fa5fb2e927fda2586" 98 | dependencies = [ 99 | "cfg-if", 100 | "wasm-bindgen-macro", 101 | ] 102 | 103 | [[package]] 104 | name = "wasm-bindgen-backend" 105 | version = "0.2.75" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "580aa3a91a63d23aac5b6b267e2d13cb4f363e31dce6c352fca4752ae12e479f" 108 | dependencies = [ 109 | "bumpalo", 110 | "lazy_static", 111 | "log", 112 | "proc-macro2", 113 | "quote", 114 | "syn", 115 | "wasm-bindgen-shared", 116 | ] 117 | 118 | [[package]] 119 | name = "wasm-bindgen-macro" 120 | version = "0.2.75" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "171ebf0ed9e1458810dfcb31f2e766ad6b3a89dbda42d8901f2b268277e5f09c" 123 | dependencies = [ 124 | "quote", 125 | "wasm-bindgen-macro-support", 126 | ] 127 | 128 | [[package]] 129 | name = "wasm-bindgen-macro-support" 130 | version = "0.2.75" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "6c2657dd393f03aa2a659c25c6ae18a13a4048cebd220e147933ea837efc589f" 133 | dependencies = [ 134 | "proc-macro2", 135 | "quote", 136 | "syn", 137 | "wasm-bindgen-backend", 138 | "wasm-bindgen-shared", 139 | ] 140 | 141 | [[package]] 142 | name = "wasm-bindgen-shared" 143 | version = "0.2.75" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "2e0c4a743a309662d45f4ede961d7afa4ba4131a59a639f29b0069c3798bbcc2" 146 | 147 | [[package]] 148 | name = "web-sys" 149 | version = "0.3.52" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "01c70a82d842c9979078c772d4a1344685045f1a5628f677c2b2eab4dd7d2696" 152 | dependencies = [ 153 | "js-sys", 154 | "wasm-bindgen", 155 | ] 156 | -------------------------------------------------------------------------------- /examples/customized_builtin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Greg Johnston "] 3 | categories = ["wasm"] 4 | description = "Example Web Component using vanilla web_sys and custom-elements" 5 | license = "Apache-2.0/MIT" 6 | name = "customized-builtin-element" 7 | readme = "./README.md" 8 | repository = "https://github.com/gbj/custom-elements" 9 | version = "0.1.0" 10 | edition = "2018" 11 | 12 | [lib] 13 | crate-type = ["cdylib"] 14 | 15 | [dependencies] 16 | custom-elements = { path = "../.."} 17 | wasm-bindgen = "0.2" 18 | js-sys = "0.3" 19 | 20 | [dependencies.web-sys] 21 | version = "0.3" 22 | features = [ 23 | "Window", 24 | "Document", 25 | "HtmlElement", 26 | "Node", 27 | "Text" 28 | ] -------------------------------------------------------------------------------- /examples/customized_builtin/build.sh: -------------------------------------------------------------------------------- 1 | wasm-pack build --target web -------------------------------------------------------------------------------- /examples/customized_builtin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Custom Elements - Vanilla 7 | 15 | 19 | 20 | 21 |

This text should be blue, because it's a regular paragraph.

22 |

This text should be purple, because it's a PurpleParagraph!

23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/customized_builtin/runserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import BaseHTTPServer 4 | import SimpleHTTPServer 5 | SimpleHTTPServer.SimpleHTTPRequestHandler.extensions_map['.wasm'] = 'application/wasm' 6 | port = 8000 7 | httpd = BaseHTTPServer.HTTPServer(('localhost', 8000), SimpleHTTPServer.SimpleHTTPRequestHandler) 8 | 9 | print "Now serving at http://localhost:8000" 10 | 11 | httpd.serve_forever() 12 | -------------------------------------------------------------------------------- /examples/customized_builtin/src/lib.rs: -------------------------------------------------------------------------------- 1 | use custom_elements::{inject_style, CustomElement}; 2 | use wasm_bindgen::prelude::*; 3 | use web_sys::{window, HtmlElement}; 4 | 5 | #[wasm_bindgen] 6 | extern "C" { 7 | #[wasm_bindgen(js_name = HTMLParagraphElement, js_namespace = window)] 8 | pub static HtmlParagraphElementConstructor: js_sys::Function; 9 | } 10 | 11 | struct PurpleParagraph {} 12 | 13 | impl PurpleParagraph { 14 | fn new() -> Self { 15 | Self {} 16 | } 17 | } 18 | 19 | impl Default for PurpleParagraph { 20 | fn default() -> Self { 21 | Self::new() 22 | } 23 | } 24 | 25 | // Here's the interesting part: configuring the Custom Element 26 | impl CustomElement for PurpleParagraph { 27 | fn inject_children(&mut self, this: &HtmlElement) { 28 | inject_style(&this, "* { color: purple; }"); 29 | let slot = window() 30 | .unwrap_throw() 31 | .document() 32 | .unwrap_throw() 33 | .create_element("slot") 34 | .unwrap_throw(); 35 | this.append_child(&slot).unwrap_throw(); 36 | } 37 | 38 | fn superclass() -> (Option<&'static str>, &'static js_sys::Function) { 39 | (Some("p"), &HtmlParagraphElementConstructor) 40 | } 41 | } 42 | 43 | // wasm_bindgen entry point defines the Custom Element, then creates a few of them 44 | #[wasm_bindgen] 45 | pub fn run() -> Result<(), JsValue> { 46 | // define the Custom Element 47 | PurpleParagraph::define("purple-paragraph"); 48 | 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /examples/vanilla/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "bumpalo" 5 | version = "3.7.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" 8 | 9 | [[package]] 10 | name = "cfg-if" 11 | version = "1.0.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 14 | 15 | [[package]] 16 | name = "custom-elements" 17 | version = "0.2.0" 18 | dependencies = [ 19 | "js-sys", 20 | "wasm-bindgen", 21 | "web-sys", 22 | ] 23 | 24 | [[package]] 25 | name = "js-sys" 26 | version = "0.3.52" 27 | source = "registry+https://github.com/rust-lang/crates.io-index" 28 | checksum = "ce791b7ca6638aae45be056e068fc756d871eb3b3b10b8efa62d1c9cec616752" 29 | dependencies = [ 30 | "wasm-bindgen", 31 | ] 32 | 33 | [[package]] 34 | name = "lazy_static" 35 | version = "1.4.0" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 38 | 39 | [[package]] 40 | name = "log" 41 | version = "0.4.14" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 44 | dependencies = [ 45 | "cfg-if", 46 | ] 47 | 48 | [[package]] 49 | name = "proc-macro2" 50 | version = "1.0.28" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" 53 | dependencies = [ 54 | "unicode-xid", 55 | ] 56 | 57 | [[package]] 58 | name = "quote" 59 | version = "1.0.9" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 62 | dependencies = [ 63 | "proc-macro2", 64 | ] 65 | 66 | [[package]] 67 | name = "syn" 68 | version = "1.0.74" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" 71 | dependencies = [ 72 | "proc-macro2", 73 | "quote", 74 | "unicode-xid", 75 | ] 76 | 77 | [[package]] 78 | name = "unicode-xid" 79 | version = "0.2.2" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 82 | 83 | [[package]] 84 | name = "vanilla-web-component" 85 | version = "0.1.0" 86 | dependencies = [ 87 | "custom-elements", 88 | "wasm-bindgen", 89 | "web-sys", 90 | ] 91 | 92 | [[package]] 93 | name = "wasm-bindgen" 94 | version = "0.2.75" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "b608ecc8f4198fe8680e2ed18eccab5f0cd4caaf3d83516fa5fb2e927fda2586" 97 | dependencies = [ 98 | "cfg-if", 99 | "wasm-bindgen-macro", 100 | ] 101 | 102 | [[package]] 103 | name = "wasm-bindgen-backend" 104 | version = "0.2.75" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "580aa3a91a63d23aac5b6b267e2d13cb4f363e31dce6c352fca4752ae12e479f" 107 | dependencies = [ 108 | "bumpalo", 109 | "lazy_static", 110 | "log", 111 | "proc-macro2", 112 | "quote", 113 | "syn", 114 | "wasm-bindgen-shared", 115 | ] 116 | 117 | [[package]] 118 | name = "wasm-bindgen-macro" 119 | version = "0.2.75" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "171ebf0ed9e1458810dfcb31f2e766ad6b3a89dbda42d8901f2b268277e5f09c" 122 | dependencies = [ 123 | "quote", 124 | "wasm-bindgen-macro-support", 125 | ] 126 | 127 | [[package]] 128 | name = "wasm-bindgen-macro-support" 129 | version = "0.2.75" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "6c2657dd393f03aa2a659c25c6ae18a13a4048cebd220e147933ea837efc589f" 132 | dependencies = [ 133 | "proc-macro2", 134 | "quote", 135 | "syn", 136 | "wasm-bindgen-backend", 137 | "wasm-bindgen-shared", 138 | ] 139 | 140 | [[package]] 141 | name = "wasm-bindgen-shared" 142 | version = "0.2.75" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "2e0c4a743a309662d45f4ede961d7afa4ba4131a59a639f29b0069c3798bbcc2" 145 | 146 | [[package]] 147 | name = "web-sys" 148 | version = "0.3.52" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "01c70a82d842c9979078c772d4a1344685045f1a5628f677c2b2eab4dd7d2696" 151 | dependencies = [ 152 | "js-sys", 153 | "wasm-bindgen", 154 | ] 155 | -------------------------------------------------------------------------------- /examples/vanilla/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Greg Johnston "] 3 | categories = ["wasm"] 4 | description = "Example Web Component using vanilla web_sys and custom-elements" 5 | license = "Apache-2.0/MIT" 6 | name = "vanilla-web-component" 7 | readme = "./README.md" 8 | repository = "https://github.com/gbj/custom-elements" 9 | version = "0.1.0" 10 | edition = "2018" 11 | 12 | [lib] 13 | crate-type = ["cdylib"] 14 | 15 | [dependencies] 16 | custom-elements = { path = "../.."} 17 | wasm-bindgen = "0.2" 18 | 19 | [dependencies.web-sys] 20 | version = "0.3" 21 | features = [ 22 | "Window", 23 | "Document", 24 | "HtmlElement", 25 | "Node", 26 | "Text" 27 | ] -------------------------------------------------------------------------------- /examples/vanilla/build.sh: -------------------------------------------------------------------------------- 1 | wasm-pack build --target web -------------------------------------------------------------------------------- /examples/vanilla/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Custom Elements - Vanilla 7 | 15 | 19 | 20 | 21 |

This text should be blue, because it's not affected by the encapsulated style. (The counts should be green!)

22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/vanilla/runserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import BaseHTTPServer 4 | import SimpleHTTPServer 5 | SimpleHTTPServer.SimpleHTTPRequestHandler.extensions_map['.wasm'] = 'application/wasm' 6 | port = 8000 7 | httpd = BaseHTTPServer.HTTPServer(('localhost', 8000), SimpleHTTPServer.SimpleHTTPRequestHandler) 8 | 9 | print "Now serving at http://localhost:8000" 10 | 11 | httpd.serve_forever() 12 | -------------------------------------------------------------------------------- /examples/vanilla/src/lib.rs: -------------------------------------------------------------------------------- 1 | use custom_elements::{inject_style, CustomElement}; 2 | use wasm_bindgen::prelude::*; 3 | use wasm_bindgen::JsCast; 4 | use web_sys::{window, HtmlElement, Node, Text}; 5 | 6 | // The boring part: a basic DOM component 7 | struct MyWebComponent { 8 | name_node: Text, 9 | } 10 | 11 | impl MyWebComponent { 12 | fn new() -> Self { 13 | let window = window().unwrap(); 14 | let document = window.document().unwrap(); 15 | let name_node = document.create_text_node("friend"); 16 | Self { name_node } 17 | } 18 | 19 | fn view(&self) -> Node { 20 | let window = window().unwrap(); 21 | let document = window.document().unwrap(); 22 | let el = document.create_element("p").unwrap(); 23 | let t1 = document.create_text_node("Welcome to my web component, "); 24 | let t3 = document.create_text_node("!"); 25 | el.append_child(&t1).unwrap(); 26 | el.append_child(&self.name_node).unwrap(); 27 | el.append_child(&t3).unwrap(); 28 | 29 | el.unchecked_into() 30 | } 31 | } 32 | 33 | impl Default for MyWebComponent { 34 | fn default() -> Self { 35 | Self::new() 36 | } 37 | } 38 | 39 | // Here's the interesting part: configuring the Custom Element 40 | impl CustomElement for MyWebComponent { 41 | fn inject_children(&mut self, this: &HtmlElement) { 42 | inject_style(&this, "p { color: green; }"); 43 | let node = self.view(); 44 | this.append_child(&node).unwrap_throw(); 45 | } 46 | 47 | fn observed_attributes() -> &'static [&'static str] { 48 | &["name"] 49 | } 50 | 51 | fn attribute_changed_callback( 52 | &mut self, 53 | _this: &HtmlElement, 54 | name: String, 55 | _old_value: Option, 56 | new_value: Option, 57 | ) { 58 | if name == "name" { 59 | self.name_node 60 | .set_data(&new_value.unwrap_or_else(|| "friend".to_string())); 61 | } 62 | } 63 | 64 | fn connected_callback(&mut self, _this: &HtmlElement) { 65 | log("connected"); 66 | } 67 | 68 | fn disconnected_callback(&mut self, _this: &HtmlElement) { 69 | log("disconnected"); 70 | } 71 | 72 | fn adopted_callback(&mut self, _this: &HtmlElement) { 73 | log("adopted"); 74 | } 75 | } 76 | 77 | // wasm_bindgen entry point defines the Custom Element, then creates a few of them 78 | #[wasm_bindgen] 79 | pub fn run() -> Result<(), JsValue> { 80 | // define the Custom Element 81 | MyWebComponent::define("ce-vanilla"); 82 | 83 | Ok(()) 84 | } 85 | 86 | #[wasm_bindgen] 87 | extern "C" { 88 | #[wasm_bindgen(js_namespace = console)] 89 | fn log(s: &str); 90 | } 91 | -------------------------------------------------------------------------------- /examples/yew/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "anyhow" 5 | version = "1.0.43" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf" 8 | 9 | [[package]] 10 | name = "anymap" 11 | version = "0.12.1" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344" 14 | 15 | [[package]] 16 | name = "autocfg" 17 | version = "1.0.1" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 20 | 21 | [[package]] 22 | name = "bincode" 23 | version = "1.3.3" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 26 | dependencies = [ 27 | "serde", 28 | ] 29 | 30 | [[package]] 31 | name = "boolinator" 32 | version = "2.4.0" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" 35 | 36 | [[package]] 37 | name = "bumpalo" 38 | version = "3.7.0" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631" 41 | 42 | [[package]] 43 | name = "bytes" 44 | version = "1.0.1" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" 47 | 48 | [[package]] 49 | name = "cfg-if" 50 | version = "0.1.10" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 53 | 54 | [[package]] 55 | name = "cfg-if" 56 | version = "1.0.0" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 59 | 60 | [[package]] 61 | name = "cfg-match" 62 | version = "0.2.1" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "8100e46ff92eb85bf6dc2930c73f2a4f7176393c84a9446b3d501e1b354e7b34" 65 | 66 | [[package]] 67 | name = "console_error_panic_hook" 68 | version = "0.1.6" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211" 71 | dependencies = [ 72 | "cfg-if 0.1.10", 73 | "wasm-bindgen", 74 | ] 75 | 76 | [[package]] 77 | name = "custom-elements" 78 | version = "0.2.0" 79 | dependencies = [ 80 | "js-sys", 81 | "wasm-bindgen", 82 | "web-sys", 83 | ] 84 | 85 | [[package]] 86 | name = "fnv" 87 | version = "1.0.7" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 90 | 91 | [[package]] 92 | name = "gloo" 93 | version = "0.2.1" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "68ce6f2dfa9f57f15b848efa2aade5e1850dc72986b87a2b0752d44ca08f4967" 96 | dependencies = [ 97 | "gloo-console-timer", 98 | "gloo-events", 99 | "gloo-file", 100 | "gloo-timers", 101 | ] 102 | 103 | [[package]] 104 | name = "gloo-console-timer" 105 | version = "0.1.0" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "b48675544b29ac03402c6dffc31a912f716e38d19f7e74b78b7e900ec3c941ea" 108 | dependencies = [ 109 | "web-sys", 110 | ] 111 | 112 | [[package]] 113 | name = "gloo-events" 114 | version = "0.1.1" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "088514ec8ef284891c762c88a66b639b3a730134714692ee31829765c5bc814f" 117 | dependencies = [ 118 | "wasm-bindgen", 119 | "web-sys", 120 | ] 121 | 122 | [[package]] 123 | name = "gloo-file" 124 | version = "0.1.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "8f9fecfe46b5dc3cc46f58e98ba580cc714f2c93860796d002eb3527a465ef49" 127 | dependencies = [ 128 | "gloo-events", 129 | "js-sys", 130 | "wasm-bindgen", 131 | "web-sys", 132 | ] 133 | 134 | [[package]] 135 | name = "gloo-timers" 136 | version = "0.2.1" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" 139 | dependencies = [ 140 | "js-sys", 141 | "wasm-bindgen", 142 | "web-sys", 143 | ] 144 | 145 | [[package]] 146 | name = "hashbrown" 147 | version = "0.11.2" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 150 | 151 | [[package]] 152 | name = "http" 153 | version = "0.2.4" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" 156 | dependencies = [ 157 | "bytes", 158 | "fnv", 159 | "itoa", 160 | ] 161 | 162 | [[package]] 163 | name = "indexmap" 164 | version = "1.7.0" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" 167 | dependencies = [ 168 | "autocfg", 169 | "hashbrown", 170 | ] 171 | 172 | [[package]] 173 | name = "itoa" 174 | version = "0.4.7" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 177 | 178 | [[package]] 179 | name = "js-sys" 180 | version = "0.3.52" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "ce791b7ca6638aae45be056e068fc756d871eb3b3b10b8efa62d1c9cec616752" 183 | dependencies = [ 184 | "wasm-bindgen", 185 | ] 186 | 187 | [[package]] 188 | name = "lazy_static" 189 | version = "1.4.0" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 192 | 193 | [[package]] 194 | name = "log" 195 | version = "0.4.14" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 198 | dependencies = [ 199 | "cfg-if 1.0.0", 200 | ] 201 | 202 | [[package]] 203 | name = "proc-macro2" 204 | version = "1.0.28" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612" 207 | dependencies = [ 208 | "unicode-xid", 209 | ] 210 | 211 | [[package]] 212 | name = "quote" 213 | version = "1.0.9" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 216 | dependencies = [ 217 | "proc-macro2", 218 | ] 219 | 220 | [[package]] 221 | name = "ryu" 222 | version = "1.0.5" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 225 | 226 | [[package]] 227 | name = "serde" 228 | version = "1.0.127" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8" 231 | dependencies = [ 232 | "serde_derive", 233 | ] 234 | 235 | [[package]] 236 | name = "serde_derive" 237 | version = "1.0.127" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc" 240 | dependencies = [ 241 | "proc-macro2", 242 | "quote", 243 | "syn", 244 | ] 245 | 246 | [[package]] 247 | name = "serde_json" 248 | version = "1.0.66" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127" 251 | dependencies = [ 252 | "itoa", 253 | "ryu", 254 | "serde", 255 | ] 256 | 257 | [[package]] 258 | name = "slab" 259 | version = "0.4.4" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590" 262 | 263 | [[package]] 264 | name = "syn" 265 | version = "1.0.74" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" 268 | dependencies = [ 269 | "proc-macro2", 270 | "quote", 271 | "unicode-xid", 272 | ] 273 | 274 | [[package]] 275 | name = "thiserror" 276 | version = "1.0.26" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "93119e4feac1cbe6c798c34d3a53ea0026b0b1de6a120deef895137c0529bfe2" 279 | dependencies = [ 280 | "thiserror-impl", 281 | ] 282 | 283 | [[package]] 284 | name = "thiserror-impl" 285 | version = "1.0.26" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "060d69a0afe7796bf42e9e2ff91f5ee691fb15c53d38b4b62a9a53eb23164745" 288 | dependencies = [ 289 | "proc-macro2", 290 | "quote", 291 | "syn", 292 | ] 293 | 294 | [[package]] 295 | name = "unicode-xid" 296 | version = "0.2.2" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 299 | 300 | [[package]] 301 | name = "wasm-bindgen" 302 | version = "0.2.75" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "b608ecc8f4198fe8680e2ed18eccab5f0cd4caaf3d83516fa5fb2e927fda2586" 305 | dependencies = [ 306 | "cfg-if 1.0.0", 307 | "wasm-bindgen-macro", 308 | ] 309 | 310 | [[package]] 311 | name = "wasm-bindgen-backend" 312 | version = "0.2.75" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "580aa3a91a63d23aac5b6b267e2d13cb4f363e31dce6c352fca4752ae12e479f" 315 | dependencies = [ 316 | "bumpalo", 317 | "lazy_static", 318 | "log", 319 | "proc-macro2", 320 | "quote", 321 | "syn", 322 | "wasm-bindgen-shared", 323 | ] 324 | 325 | [[package]] 326 | name = "wasm-bindgen-futures" 327 | version = "0.4.25" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "16646b21c3add8e13fdb8f20172f8a28c3dbf62f45406bcff0233188226cfe0c" 330 | dependencies = [ 331 | "cfg-if 1.0.0", 332 | "js-sys", 333 | "wasm-bindgen", 334 | "web-sys", 335 | ] 336 | 337 | [[package]] 338 | name = "wasm-bindgen-macro" 339 | version = "0.2.75" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "171ebf0ed9e1458810dfcb31f2e766ad6b3a89dbda42d8901f2b268277e5f09c" 342 | dependencies = [ 343 | "quote", 344 | "wasm-bindgen-macro-support", 345 | ] 346 | 347 | [[package]] 348 | name = "wasm-bindgen-macro-support" 349 | version = "0.2.75" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "6c2657dd393f03aa2a659c25c6ae18a13a4048cebd220e147933ea837efc589f" 352 | dependencies = [ 353 | "proc-macro2", 354 | "quote", 355 | "syn", 356 | "wasm-bindgen-backend", 357 | "wasm-bindgen-shared", 358 | ] 359 | 360 | [[package]] 361 | name = "wasm-bindgen-shared" 362 | version = "0.2.75" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "2e0c4a743a309662d45f4ede961d7afa4ba4131a59a639f29b0069c3798bbcc2" 365 | 366 | [[package]] 367 | name = "web-sys" 368 | version = "0.3.52" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "01c70a82d842c9979078c772d4a1344685045f1a5628f677c2b2eab4dd7d2696" 371 | dependencies = [ 372 | "js-sys", 373 | "wasm-bindgen", 374 | ] 375 | 376 | [[package]] 377 | name = "yew" 378 | version = "0.18.0" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "e4d5154faef86dddd2eb333d4755ea5643787d20aca683e58759b0e53351409f" 381 | dependencies = [ 382 | "anyhow", 383 | "anymap", 384 | "bincode", 385 | "cfg-if 1.0.0", 386 | "cfg-match", 387 | "console_error_panic_hook", 388 | "gloo", 389 | "http", 390 | "indexmap", 391 | "js-sys", 392 | "log", 393 | "ryu", 394 | "serde", 395 | "serde_json", 396 | "slab", 397 | "thiserror", 398 | "wasm-bindgen", 399 | "wasm-bindgen-futures", 400 | "web-sys", 401 | "yew-macro", 402 | ] 403 | 404 | [[package]] 405 | name = "yew-macro" 406 | version = "0.18.0" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "d6e23bfe3dc3933fbe9592d149c9985f3047d08c637a884b9344c21e56e092ef" 409 | dependencies = [ 410 | "boolinator", 411 | "lazy_static", 412 | "proc-macro2", 413 | "quote", 414 | "syn", 415 | ] 416 | 417 | [[package]] 418 | name = "yew-web-component" 419 | version = "0.1.0" 420 | dependencies = [ 421 | "custom-elements", 422 | "wasm-bindgen", 423 | "web-sys", 424 | "yew", 425 | ] 426 | -------------------------------------------------------------------------------- /examples/yew/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Greg Johnston "] 3 | categories = ["wasm"] 4 | description = "Example Web Component using Yew and custom-elements" 5 | license = "Apache-2.0/MIT" 6 | name = "yew-web-component" 7 | readme = "./README.md" 8 | repository = "https://github.com/gbj/custom-elements" 9 | version = "0.1.0" 10 | edition = "2018" 11 | 12 | [lib] 13 | crate-type = ["rlib", "cdylib"] 14 | 15 | [dependencies] 16 | custom-elements = { path = "../.."} 17 | wasm-bindgen = "0.2" 18 | yew = "0.18" 19 | 20 | [dependencies.web-sys] 21 | version = "0.3" 22 | features = [ 23 | "Window", 24 | "DocumentFragment" 25 | ] -------------------------------------------------------------------------------- /examples/yew/build.sh: -------------------------------------------------------------------------------- 1 | wasm-pack build --target web -------------------------------------------------------------------------------- /examples/yew/component_style.css: -------------------------------------------------------------------------------- 1 | p { 2 | color: green; 3 | } -------------------------------------------------------------------------------- /examples/yew/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Custom Elements - Yew 7 | 15 | 19 | 20 | 21 |

This text should be blue, because it's not affected by the encapsulated style.

22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/yew/runserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import BaseHTTPServer 4 | import SimpleHTTPServer 5 | SimpleHTTPServer.SimpleHTTPRequestHandler.extensions_map['.wasm'] = 'application/wasm' 6 | port = 8000 7 | httpd = BaseHTTPServer.HTTPServer(('localhost', 8000), SimpleHTTPServer.SimpleHTTPRequestHandler) 8 | 9 | print "Now serving at http://localhost:8000" 10 | 11 | httpd.serve_forever() 12 | -------------------------------------------------------------------------------- /examples/yew/src/component.rs: -------------------------------------------------------------------------------- 1 | use yew::prelude::*; 2 | 3 | pub enum Msg { 4 | AddOne, 5 | Set(i64), 6 | } 7 | 8 | pub struct Model { 9 | // `ComponentLink` is like a reference to a component. 10 | // It can be used to send messages to the component 11 | link: ComponentLink, 12 | value: i64, 13 | } 14 | 15 | impl Component for Model { 16 | type Message = Msg; 17 | type Properties = (); 18 | 19 | fn create(_props: Self::Properties, link: ComponentLink) -> Self { 20 | Self { link, value: 0 } 21 | } 22 | 23 | fn update(&mut self, msg: Self::Message) -> ShouldRender { 24 | match msg { 25 | Msg::AddOne => { 26 | self.value += 1; 27 | // the value has changed so we need to 28 | // re-render for it to appear on the page 29 | true 30 | } 31 | Msg::Set(value) => { 32 | self.value = value; 33 | true 34 | } 35 | } 36 | } 37 | 38 | fn change(&mut self, _props: Self::Properties) -> ShouldRender { 39 | // Should only return "true" if new properties are different to 40 | // previously received properties. 41 | // This component has no properties so we will always return "false". 42 | false 43 | } 44 | 45 | fn view(&self) -> Html { 46 | html! { 47 |
48 | 49 |

{ self.value }

50 |
51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/yew/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod component; 2 | 3 | use component::Model; 4 | use component::Msg; 5 | use custom_elements::{inject_stylesheet, CustomElement}; 6 | use wasm_bindgen::prelude::*; 7 | use wasm_bindgen::JsCast; 8 | use web_sys::{window, HtmlElement}; 9 | use yew::html::Scope; 10 | use yew::prelude::*; 11 | 12 | struct ComponentWrapper { 13 | scope: Option>, 14 | } 15 | 16 | impl ComponentWrapper { 17 | fn new() -> Self { 18 | Self { scope: None } 19 | } 20 | } 21 | 22 | impl CustomElement for ComponentWrapper { 23 | fn inject_children(&mut self, this: &HtmlElement) { 24 | yew::initialize(); 25 | let app = App::::new(); 26 | let scope = app.mount(this.clone().unchecked_into()); 27 | self.scope = Some(scope); 28 | yew::run_loop(); 29 | 30 | inject_stylesheet(&this, "/component_style.css"); 31 | } 32 | 33 | fn observed_attributes() -> &'static [&'static str] { 34 | &["value"] 35 | } 36 | 37 | fn attribute_changed_callback( 38 | &mut self, 39 | _this: &HtmlElement, 40 | name: String, 41 | _old_value: Option, 42 | new_value: Option, 43 | ) { 44 | match name.as_str() { 45 | "value" => { 46 | if let Some(value) = new_value { 47 | if let Ok(value) = value.parse::() { 48 | if let Some(scope) = &self.scope { 49 | scope.send_message(Msg::Set(value)); 50 | } 51 | } 52 | } 53 | } 54 | _ => (), 55 | }; 56 | } 57 | } 58 | 59 | impl Default for ComponentWrapper { 60 | fn default() -> Self { 61 | Self::new() 62 | } 63 | } 64 | 65 | #[wasm_bindgen] 66 | pub fn run() { 67 | ComponentWrapper::define("ce-yew"); 68 | } 69 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The Web Components standard creates a browser feature that allows you to create reusable components, called Custom Elements. 2 | //! 3 | //! While web_sys exposes the browser’s CustomElementRegistry interface, it can be hard to use. Creating a Custom Element requires calling customElements.define() and passing it an ES2015 class that extends HTMLElement, which is not currently possible to do directly from Rust. 4 | //! 5 | //! This crate provides a [CustomElement][CustomElement] trait that, when implemented, allows you to encapsulate any Rust structure as a reusable web component without writing any JavaScript. In theory it should be usable with any Rust front-end framework. 6 | //! ```rust 7 | //! impl CustomElement for MyWebComponent { 8 | //! fn inject_children(&mut self, this: &HtmlElement) { 9 | //! inject_style(&this, "p { color: green; }"); 10 | //! let node = self.view(); 11 | //! this.append_child(&node).unwrap_throw(); 12 | //! } 13 | //! 14 | //! fn observed_attributes() -> &'static [&'static str] { 15 | //! &["name"] 16 | //! } 17 | //! 18 | //! fn attribute_changed_callback( 19 | //! &mut self, 20 | //! _this: &HtmlElement, 21 | //! name: String, 22 | //! _old_value: Option, 23 | //! new_value: Option, 24 | //! ) { 25 | //! if name == "name" { 26 | //! /* do something... */ 27 | //! } 28 | //! } 29 | //! 30 | //! fn connected_callback(&mut self, _this: &HtmlElement) { 31 | //! log("connected"); 32 | //! } 33 | //! 34 | //! fn disconnected_callback(&mut self, _this: &HtmlElement) { 35 | //! log("disconnected"); 36 | //! } 37 | //! 38 | //! fn adopted_callback(&mut self, _this: &HtmlElement) { 39 | //! log("adopted"); 40 | //! } 41 | //! } 42 | //! 43 | //! #[wasm_bindgen] 44 | //! pub fn define_custom_elements() { 45 | //! MyWebComponent::define("my-component"); 46 | //! } 47 | //! ``` 48 | 49 | use std::sync::{Arc, Mutex}; 50 | 51 | use wasm_bindgen::prelude::*; 52 | use wasm_bindgen::UnwrapThrowExt; 53 | use web_sys::{window, HtmlElement}; 54 | 55 | /// A custom DOM element that can be reused via the Web Components/Custom Elements standard. 56 | /// 57 | /// Note that your component should implement [Default][std::default::Default], which allows the 58 | /// browser to initialize a “default” blank component when a new custom element node is created. 59 | pub trait CustomElement: Default + 'static { 60 | /// Appends children to the root element, either to the shadow root in shadow mode or to the custom element itself. 61 | /// Per the [Web Components spec](https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-conformance), 62 | /// this is deferred to the first invocation of `connectedCallback()`. 63 | /// It will run before [connected_callback](CustomElement::connected_callback). 64 | fn inject_children(&mut self, this: &HtmlElement); 65 | 66 | /// Whether a [Shadow root](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM) 67 | /// should be attached to the element or not. Shadow DOM encapsulates styles, but makes some DOM manipulation more difficult. 68 | /// 69 | /// Defaults to `true`. 70 | fn shadow() -> bool { 71 | true 72 | } 73 | 74 | /// The names of the attributes whose changes should be observed. If an attribute name is in this list, 75 | /// [attribute_changed_callback](CustomElement::attribute_changed_callback) will be invoked when it changes. 76 | /// If it is not, nothing will happen when the DOM attribute changes. 77 | fn observed_attributes() -> &'static [&'static str] { 78 | &[] 79 | } 80 | 81 | /// Invoked when the custom element is instantiated. This can be used to inject any code into the `constructor`, 82 | /// immediately after it calls `super()`. 83 | fn constructor(&mut self, _this: &HtmlElement) {} 84 | 85 | /// Invoked each time the custom element is appended into a document-connected element. 86 | /// This will happen each time the node is moved, and may happen before the element's contents have been fully parsed. 87 | fn connected_callback(&mut self, _this: &HtmlElement) {} 88 | 89 | /// Invoked each time the custom element is disconnected from the document's DOM. 90 | fn disconnected_callback(&mut self, _this: &HtmlElement) {} 91 | 92 | /// Invoked each time the custom element is moved to a new document. 93 | fn adopted_callback(&mut self, _this: &HtmlElement) {} 94 | 95 | /// Invoked each time one of the custom element's attributes is added, removed, or changed. 96 | /// To observe an attribute, include it in [observed_attributes](CustomElement::observed_attributes). 97 | fn attribute_changed_callback( 98 | &mut self, 99 | _this: &HtmlElement, 100 | _name: String, 101 | _old_value: Option, 102 | _new_value: Option, 103 | ) { 104 | } 105 | 106 | /// Specifies the built-in element your element inherits from, if any, by giving its tag name and constructor. 107 | /// This is only relevant to customized built-in elements, not autonomous custom elements. 108 | /// [Browser support is inconsistent](https://caniuse.com/custom-elementsv1). 109 | /// 110 | /// Defaults to the equivalent of `extends HTMLElement`, which makes for an autonomous custom element. 111 | /// 112 | /// To specify your own superclass, import it using `wasm_bindgen`: 113 | /// ``` 114 | /// #[wasm_bindgen] 115 | /// extern "C" { 116 | /// #[wasm_bindgen(js_name = HTMLParagraphElement, js_namespace = window)] 117 | /// pub static HtmlParagraphElementConstructor: js_sys::Function; 118 | /// } 119 | /// impl CustomElement for MyComponent { 120 | /// fn superclass() -> (Option<&'static str>, &'static js_sys::Function) { 121 | /// (Some("p"), &HtmlParagraphElementConstructor) 122 | /// } 123 | /// } 124 | /// ``` 125 | fn superclass() -> (Option<&'static str>, &'static js_sys::Function) { 126 | (None, &HtmlElementConstructor) 127 | } 128 | 129 | /// Must be called somewhere to define the custom element and register it with the DOM Custom Elements Registry. 130 | /// 131 | /// Note that custom element names must contain a hyphen. 132 | /// 133 | /// ```rust 134 | /// impl CustomElement for MyCustomElement { /* ... */ */} 135 | /// #[wasm_bindgen] 136 | /// pub fn define_elements() { 137 | /// MyCustomElement::define("my-component"); 138 | /// } 139 | /// ``` 140 | fn define(tag_name: &'static str) { 141 | // constructor function will be called for each new instance of the component 142 | let constructor = Closure::wrap(Box::new(move |this: HtmlElement| { 143 | let component = Arc::new(Mutex::new(Self::default())); 144 | 145 | // constructor 146 | let cmp = component.clone(); 147 | let constructor = Closure::wrap(Box::new({ 148 | move |el| { 149 | let mut lock = cmp.lock().unwrap_throw(); 150 | lock.constructor(&el); 151 | } 152 | }) as Box); 153 | js_sys::Reflect::set( 154 | &this, 155 | &JsValue::from_str("_constructor"), 156 | &constructor.into_js_value(), 157 | ) 158 | .unwrap_throw(); 159 | 160 | // inject_children 161 | let cmp = component.clone(); 162 | let inject_children = Closure::wrap(Box::new({ 163 | move |el| { 164 | let mut lock = cmp.lock().unwrap_throw(); 165 | lock.inject_children(&el); 166 | } 167 | }) as Box); 168 | js_sys::Reflect::set( 169 | &this, 170 | &JsValue::from_str("_injectChildren"), 171 | &inject_children.into_js_value(), 172 | ) 173 | .unwrap_throw(); 174 | 175 | // connectedCallback 176 | let cmp = component.clone(); 177 | let connected = Closure::wrap(Box::new({ 178 | move |el| { 179 | let mut lock = cmp.lock().unwrap_throw(); 180 | lock.connected_callback(&el); 181 | } 182 | }) as Box); 183 | js_sys::Reflect::set( 184 | &this, 185 | &JsValue::from_str("_connectedCallback"), 186 | &connected.into_js_value(), 187 | ) 188 | .unwrap_throw(); 189 | 190 | // disconnectedCallback 191 | let cmp = component.clone(); 192 | let disconnected = Closure::wrap(Box::new(move |el| { 193 | let mut lock = cmp.lock().unwrap_throw(); 194 | lock.disconnected_callback(&el); 195 | }) as Box); 196 | js_sys::Reflect::set( 197 | &this, 198 | &JsValue::from_str("_disconnectedCallback"), 199 | &disconnected.into_js_value(), 200 | ) 201 | .unwrap_throw(); 202 | 203 | // adoptedCallback 204 | let cmp = component.clone(); 205 | let adopted = Closure::wrap(Box::new(move |el| { 206 | let mut lock = cmp.lock().unwrap_throw(); 207 | lock.adopted_callback(&el); 208 | }) as Box); 209 | js_sys::Reflect::set( 210 | &this, 211 | &JsValue::from_str("_adoptedCallback"), 212 | &adopted.into_js_value(), 213 | ) 214 | .unwrap_throw(); 215 | 216 | // attributeChangedCallback 217 | let cmp = component; 218 | let attribute_changed = Closure::wrap(Box::new(move |el, name, old_value, new_value| { 219 | let mut lock = cmp.lock().unwrap_throw(); 220 | lock.attribute_changed_callback(&el, name, old_value, new_value); 221 | }) 222 | as Box, Option)>); 223 | js_sys::Reflect::set( 224 | &this, 225 | &JsValue::from_str("_attributeChangedCallback"), 226 | &attribute_changed.into_js_value(), 227 | ) 228 | .unwrap_throw(); 229 | }) as Box); 230 | 231 | // observedAttributes is static and needs to be known when the class is defined 232 | let attributes = Self::observed_attributes(); 233 | let observed_attributes = JsValue::from( 234 | attributes 235 | .iter() 236 | .map(|attr| JsValue::from_str(attr)) 237 | .collect::(), 238 | ); 239 | 240 | // call out to JS to define the Custom Element 241 | let (super_tag, super_constructor) = Self::superclass(); 242 | make_custom_element( 243 | super_constructor, 244 | tag_name, 245 | Self::shadow(), 246 | constructor.into_js_value(), 247 | observed_attributes, 248 | super_tag, 249 | ); 250 | } 251 | } 252 | 253 | /// Attaches a `