-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Request for library: slim web component library #162
Comments
https://github.com/rail44/squark |
Neat! Although I don't see any mention of web components, so I think it may be orthogonal to this issue...
Awesome! Do you have any particular questions or anything? Probably best to move this part of the discussion into that issue. |
Oh, sorry 😅 |
Hi, I want to give a try on this but I'm quickly stuck on few things.
Here are my questions:
|
Hi @ctjhoa! Excited to see some movement here :) The design that I think makes sense is to have the actual This approach saves users from having to manage memory and lifetimes of objects themselves. It also side steps some of your questions above. Also, the primary interface between a web-component and the outside world is the custom element's attributes. It would be A+ if we could provide a serde deserializer from attributes to rich rust types that the web component uses (or at minimum does things like parse integers from attribute value strings). Are you available to come to the next WG meeting? If possible, it would be great to have some high-throughput design discussion on this stuff :) #252 |
@fitzgen It's not feasible to use https://codepen.io/Pauan/pen/961b58f8fc23677268ad11f37e3c6cc9 (Open the dev console and see the messages) As you can see, every time a DOM element is inserted/removed, it fires the This happens even when merely moving around the DOM node (without removing it). Unfortunately I don't see a clean way to fix this. You mentioned (during the WG meeting) using So I think we're still stuck with manual memory management, manually calling a |
@Pauan, thanks for making an example and verifying this behavior! I still think a delayed (and cancellable!) destruction is what we want here, because of the superior UX. Custom elements are supposed to "just" be another element, and they are supposed to completely encapsulate themselves without requiring users do anything that "normal" elements require. I think using import { RustCustomElement } from "path/to/rust-custom-element";
class CustomElement extends HTMLElement {
constructor() {
this.inner = null;
this.idleId = null;
if (this.isConnected) {
this.inner = new RustCustomElement(/* ... */);
}
}
connectedCallback() {
if (!this.inner) {
window.cancelIdleCallback(this.idleId);
this.idleId = null;
this.inner = new RustCustomElement(/* ... */);
}
}
disconnectedCallback() {
this.idleId = window.requestIdleCallback(() => {
const inner = this.inner;
this.inner = null;
inner.free();
});
}
} |
The other option would be to require the state to be serializable. That way you can store all the state directly on the component (or in a WeakMap) and you don't actually need to free anything. |
I agree that's a good goal, I'm just not seeing a clean way to accomplish that. If a user removes the custom element from the DOM, then waits for a bit (perhaps using An unusual case, sure, but it definitely can happen, and I can even imagine use-cases for it (e.g. a modal dialog which is only inserted into the DOM while the modal is open, and is otherwise detached from the DOM). So proper support probably requires WeakRef or similar. Having said that, we can totally experiment with custom elements even without WeakRef (just with the above caveat). |
Also, as for encapsulation, custom elements can actually have custom methods, which the consumer can then access: class Foo extends HTMLElement {
myMethod() {
...
}
}
customElements.define("my-foo", Foo);
var foo = document.createElement("my-foo");
foo.myMethod(); So I don't think it's that unusual to call methods on custom elements. I imagine that's probably the preferred way to update the custom element's internal state from the outside. In addition, because JS doesn't have finalizers, I think there will be custom elements (written entirely in JS) which require a So overall I don't think it's that unusual to have a |
The reinsertion will trigger a new Rust object to be created, so this won't result in a bug. See the Yes, there can be multiple Rust objects used across the lifetime of the JS custom element: the idea is that creating and destroying the inner Rust thing on every attach or detach is the baseline, and the delay is an optimization to cut down on thrashing when just moving the element instead of removing it. Unless I am misunderstanding what you are saying? |
I think the usual way is via setting attributes (eg same as value, min, and max for Ultimately, yes we want finalizers, and we can actually polyfill it now. Which is something we need to get published... |
@fitzgen Ah, okay, I had (incorrectly) assumed there would be a 1:1 relationship between the Rust struct and the custom element. If you instead make it N:1 then yeah, it can just dynamically create/destroy the Rust objects on demand. I don't think that'll work for every use case, but it should work for most. I'm a bit concerned about the user's expectations though, I think other users will also expect a 1:1 relationship. |
As for attributes, (at least in HTML) those are primarily used for static pre-initialized things. For dynamic things, users instead use setters/methods like Since custom elements can listen to attribute changes, they can respond to dynamic attribute changes, but using Since getters/setters/methods are so common with regular DOM elements, I expect them to be similarly common with custom elements (I don't have any statistics or experience to back that up, though). |
I've made a proof of concept So what's going on in this project.
|
Seems like any wasm module just needs a JS custom element glue class to call into the module (to trigger the lifecycle hooks). Even if wasm gets ability to reference DOM elements in the future, there will be no way to avoid the JS glue class (to pass into |
Can't you create the "class" from rust creating a function object changing the prototype, etc, etc? |
@olanod That still requires JS glue to create the function object (and change the prototype). Secondly, as far as I know, it's not possible to use ES5 classes for custom elements: function Foo() {
console.log("HI");
}
Foo.prototype = Object.create(HTMLElement.prototype);
customElements.define("my-foo", Foo);
// Errors
var x = document.createElement("my-foo"); This is because ES5 classes cannot inherit from special built-ins like But ES6 classes were specifically designed so that they can inherit from built-ins. So ES6 classes aren't just a bit of syntax sugar, they actually have new behavior. However, even given the above, we only need a single JS function to create all of the classes (this was mentioned by @trusktr ): export function make_custom_element(parent, observedAttributes, connectedCallback, disconnectedCallback, adoptedCallback, attributeChangedCallback) {
return class extends parent {
static get observedAttributes() { return observedAttributes; }
connectedCallback() {
connectedCallback();
}
disconnectedCallback() {
disconnectedCallback();
}
adoptedCallback() {
adoptedCallback();
}
attributeChangedCallback(name, oldValue, newValue) {
attributeChangedCallback(name, oldValue, newValue);
}
};
} Now wasm can just call the In fact, it should be possible to do that right now, no changes needed to wasm-bindgen. |
These might be interesting, if not already known: |
With rustwasm/wasm-bindgen#1737, it's possible to do: #[wasm_bindgen(prototype=web_sys::HtmlElement)]
struct MyCustomElement {}
#[wasm_bindgen]
impl MyCustomElement {
#[wasm_bindgen(constructor)]
fn new() -> WasmType<MyCustomElement> {
instantiate! { MyCustomElement{} }
}
}
// ...
web_sys::window()
.unwrap()
.custom_elements()
.define("my-custom-element", &js_sys::JsFunction::of::<MyCustomElement>())?;
// ... Any thoughts or input into that PR and/or the (early draft) RFC for which it's a prototype would be greatly appreciated! |
@eggyal Oh no! What happened with your draft? What would it take to get this train moving again? I could really use the ability to extend and implement |
It would be super cool if someone wrote a slim library for writing web components and custom elements in rust!
I'm imagining the library would have a trait, and if I (the library user) implement that trait for my type, then the library wires up all the glue to turn that trait implementation into a web component.
A great way to integrate nicely with the JS ecosystem! A website could use these web components without even needing to know that it was implemented in rust and wasm.
A potential web component to implement with this library might be a graphing/charting library that has to churn through a lot of data before displaying it.
The text was updated successfully, but these errors were encountered: