-
Notifications
You must be signed in to change notification settings - Fork 176
Web Components: Shadow Dom + Template Element #295
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
Conversation
Approach 1
I don't think so, at least not if you want to define custom methods. However, I think the
You just have to implement impl TryFrom<Value> for MyElement {
type Error = ConversionError;
fn try_from(value: Value) -> Result<Self, Self::Error> {
...
}
} This can easily be automated by the
I'm not sure, but I don't think it matters. Since the class is being generated by stdweb, I don't think we need an Approach 2
Is this true? I would imagine something like this would work: CustomElementBuilder::new("my-element")
.extends(TextAreaElement)
.define() And then in the if let Some(ref extends) = self.extends {
js!(
return class extends @{extends} {
...
};
).try_into().unwrap()
} else {
js!(
return class {
...
};
).try_into().unwrap()
} It's not pretty, but I think it would work. |
Oh, I see the issue with approach 2: stdweb doesn't currently expose the actual JS classes (only the instances of the classes). So you'd need to do something like this: CustomElementBuilder::new("my-element")
.extends(js!( return HTMLTextAreaElement; ))
.define() A bit ugly, but doable. And maybe we could figure out a cleaner solution, something like this: CustomElementBuilder::new("my-element")
.extends(TextAreaElement::class)
.define() |
By the way, there is a third approach: using macros. custom_element! {
#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
#[reference(subclass_of(EventTarget, Node, Element, HtmlElement, TextAreaElement))]
pub struct MyElement extends HTMLTextAreaElement;
fn inited(&self) {
// JavaScript Constructor Called
}
fn connected(&self) {
// HTMLElement.connectedCallback
}
fn attr_changed(&self, attr_name: String, before: String, after: String) {
// HTMLElement.attributeChangedCallback
}
fn adopted(&self) {
// HTMLElement.adoptedCallback
}
fn disconnected(&self) {
// HTMLElement.disconnectedCallback
}
fn observed_attrs(&self) -> Vec<String> {
// vec!["name".to_owned(), "class".to_owned()]
}
} Since it needs to use the |
Hmm... I still think approach 1 is the way to go. I proposed the second approach just because it's much easier to implement. Though there are ways to workaround the subclass issue for approach 2, it is still kind of difficult to use. You still have to downcast the reference if you want to use any methods from As for approach 3, what's the advantage of using When I originally thought about the API, I specifically avoided using macros with custom syntax for a couple of reasons:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR!
Initial review done. I'll probably still have more comments, but it's already late and I need to go to sleep. (:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Continuing the review....
src/webapi/element.rs
Outdated
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow) | ||
// https://dom.spec.whatwg.org/#ref-for-dom-element-attachshadow | ||
fn attach_shadow( &self, mode: ShadowRootMode ) -> Result<ShadowRoot, AttachShadowError> { | ||
js_try!( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Broken indentation (:
src/webapi/html_elements/slot.rs
Outdated
/// present them together. | ||
/// | ||
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot) | ||
// https://html.spec.whatwg.org/multipage/scripting.html#the-slot-element |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A link to the IDL would be better:
https://html.spec.whatwg.org/multipage/scripting.html#htmlslotelement
src/webapi/html_elements/slot.rs
Outdated
|
||
/// Setter of name. | ||
#[inline] | ||
pub fn set_name<S: AsRef<str>>( &self, new_name: S ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any particular reason why you made it S: AsRef<str>
? In general we usually take &str
and have the user convert it themselves.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No particular reason, I just feel it would be easier to use.
src/webapi/html_elements/slot.rs
Outdated
/// | ||
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/assignedNodes) | ||
// https://html.spec.whatwg.org/multipage/scripting.html#the-slot-element:dom-slot-assignednodes | ||
pub fn assigned_nodes( &self, flatten: bool ) -> Vec<Node> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than take a bool
parameter I'd probably split this into two methods, say, assigned_nodes
and flattened_assigned_nodes
? (The second name is a little long though; if you can think of a better one feel free to suggest it.) Ditto for assigned_elements
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about a enum (like webapi::node::CloneKind
)?
enum ChildrenArrangement { // I cannot come up with a better name.
Flattened,
Nested,
}
pub fn assigned_nodes( &self, a: ChildrenArrangement ) -> ... {
Also, should the return type be NodeList
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, should the return type be
NodeList
?
In the specs it says that it's a sequence<Node>
, which is different than NodeList
, so I don't think so.
How about a enum (like
webapi::node::CloneKind
)?
Well, that would work too, but that's another extra type which you have to import and remember.
I don't have a strong opinion either way though.
src/webapi/shadow_root.rs
Outdated
/// The mode property of the `ShadowRoot` specifies its mode. | ||
/// | ||
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/mode) | ||
// https://dom.spec.whatwg.org/#dom-shadowroot-mode |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Link to IDL: https://dom.spec.whatwg.org/#ref-for-dom-shadowroot-mode
src/webapi/shadow_root.rs
Outdated
/// the ShadowRoot is attached to. | ||
/// | ||
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/host) | ||
// https://dom.spec.whatwg.org/#dom-shadowroot-host |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://dom.spec.whatwg.org/#ref-for-dom-shadowroot-host
src/webapi/slotable.rs
Outdated
use webapi::html_elements::SlotElement; | ||
|
||
/// The Slotable mixin defines features that allow nodes to become the contents of | ||
/// a <slot> element — the following features are included in both Element and Text. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Escape all of the HTML tags in the doc comments, or they'll screw up the docs.
<slot> -> `<slot>`
Regarding the custom elements - personally I'd prefer if they were defined using normal Rust syntax with a derive macro and/or a procedural macro generating the glue. (So option 1 I guess.)
As long as the
Currently there are two ways to emit JS code:
Technically it would also be possible to introduce a third mechanism (which wouldn't necessarily have to be public) which would allow us to emit JS code in a more declarative way in the outermost namespace "before" |
Option 1 also have another problem. The user have no control on when the element is defined. They may want to have it defined after We may want to add something like |
Ah, never mind. It seems like So we need a Or we need to use the "third mechanism" that @koute mentioned. |
It seems like travis became one commit behind after I pushed a commit during the time when GitHub was unstable earlier this week. Can someone with write access trigger a manual rebuild to see if this can be fixed? |
src/webapi/html_elements/slot.rs
Outdated
pub fn assigned_nodes( &self, kind: SlotContentKind ) -> Vec<Node> { | ||
let is_flatten = match kind { | ||
SlotContentKind::Default => false, | ||
SlotContentKind::Fallback => true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the member in the specs is called flatten
can you explain why are the enums named Default
and Fallback
instead of, I don't know, NonFlattened
and Flattened
? (I don't mind the discrepancy as long as it's well motivated and done for good reasons; I'm not super familiar with these APIs, so that's why I'm asking.)
Also, a related question - do you actually know which variant is used more often in practice? (Since, e.g., if one variant is used 90% of the time and the other is used only 10% of the time then it makes more sense to have two methods to have better ergonomics for the most common case, however if it's more like 50% each then an enum
like this might not be a bad idea.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, according to MDN, what { flatten: true }
actually means is that it will return the fallback content of the slot
defined in the <slot></slot>
tag if nothing's been assigned using slot=
attribute. And the default behaviour for slot.assignedNode()
is { flatten: false }
which means the browser will only return content assigned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also,
slot.assigned_node(SlotContentKind::Assigned);
looks kind of weird.
So I changed it to
slot.assigned_node(SlotContentKind::Default);
.
I've restarted the stable tests on Travis and they still fail. (It looks like you might be missing an FYI, you can always retrigger them yourself with some git trickery. (: Just run |
Forgive me for that messy commit history... Well, it seems like that And it turned out to be an issue with So all tests run without any problem in my vm now, but travis is still complaining about It also seems like that only Chrome supports |
The biggest benefit I see is that it allows you to not specify every method. That's actually quite important: most of the time users won't need or want to implement every method. There might be performance differences as well: if the browser notices that the class doesn't have a method, it might use a fast path which completely avoids that method call. But since approach 1 requires the user to specify all methods, the browser now has to always invoke those methods, even if the methods don't really do anything.
Assuming that the user always specifies all the methods, yes the end result will be the same. But that's not the case if the user wishes to not specify some methods.
I think that's a great idea! Being able to create JS classes might be useful in situations other than custom elements. I guess it would look something like this: js_class! {
#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
#[reference(subclass_of(EventTarget, Node, Element, HtmlElement, TextAreaElement))]
pub struct Foo(TextAreaElement) extends HTMLTextAreaElement {
pub fn inited(&self) {
...
}
}
} It would generate this Rust code: #[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
#[reference(subclass_of(EventTarget, Node, Element, HtmlElement, TextAreaElement))]
struct Foo(TextAreaElement);
impl Foo {
pub fn inited(&self) {
...
}
}
lazy_static! {
pub static ref Foo: Foo = js!(
return class extends HTMLTextAreaElement {
inited() {
@{Foo::inited}(this);
}
};
).try_into().unwrap();
}
impl Foo {
pub fn new() -> Self {
js!( return new @{&*Foo}(); ).try_into().unwrap()
}
} Now you can define a custom element like this: custom_elements().define_extends("foo", &*Foo, "textarea")
I wonder how true that is. Especially if it's written as a proc macro, I think we can provide good error messages.
Sublime Text works fine, as long as you're using Rust syntax inside of the macro. Macros are quite common in Rust (e.g. Even the Markdown syntax highlighting here on GitHub works with macros (see the
I'm not sure that's true, since they both have to do similar things (and since attributes internally call into proc macros anyways).
It would probably be built using proc macros, not |
I think most users will not use custom elements on its own directly. They will use a framework that glues custom elements, shadow dom, and template together with optional data binding. And the framework, of course, is going to define all methods. js_class! {
// Why `#[derive(...)]` cannot be automated by `js_class!`?
// Why is `pub` needed? Aren't we going to export it to JavaScript-side regardlessly?
#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
#[reference(subclass_of(EventTarget, Node, Element, HtmlElement, TextAreaElement))]
pub struct Foo(TextAreaElement) extends HTMLTextAreaElement {
// Why this cannot be read from #[reference(instance_of = "HTMLTextAreaElement")]
// What if there's a method called `inited` in `HTMLTextAreaElement `?
// How do we call `super()`?
pub fn inited(&self) {
...
}
// Do we need to use camel case for methods?
// `rustc` would complain about that. I would complain either.
pub fn attributeChangedCallback(&self, name: &str, before: &str, after: &str) {
// ...
}
}
} Though I said "use macro to define a JavaScript class". I am not a big fan of the way
I am not talking about the syntax error on custom syntax. I am talking about the error on code inside the macro. For example, when I was implementing /// Setter of name.
#[inline]
pub fn set_name<S: AsRef<str>>( &self, new_name: S ) {
js! ( @(no_return)
@{self}.name = @{new_name};
);
} And error[E0277]: the trait bound `webcore::newtype::Newtype<_, S>: webcore::serialization::JsSerializeOwned` is not satisfied
--> src/webcore/macros.rs:306:21
|
306 | let $name = $crate::private::JsSerializeOwned::into_js_owned( &mut $name );
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `webcore::serialization::JsSerializeOwned` is not implemented for `webcore::newtype::Newtype<_, S>`
|
::: src/webapi/html_elements/slot.rs:60:9
|
60 | / js! ( @(no_return)
61 | | @{self}.name = @{new_name};
62 | | );
| |__________- in this macro invocation
|
= help: the following implementations were found:
<webcore::newtype::Newtype<(webcore::serialization::NonFunctionTag, ()), T> as webcore::serialization::JsSerializeOwned>
<webcore::newtype::Newtype<(webcore::serialization::FunctionTag, (A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12)), F> as webcore::serialization::JsSerializeOwned>
<webcore::newtype::Newtype<(webcore::serialization::FunctionTag, (A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12)), std::option::Option<F>> as webcore::serialization::JsSerializeOwned>
<webcore::newtype::Newtype<(webcore::serialization::FunctionTag, (A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12)), webcore::mutfn::Mut<F>> as webcore::serialization::JsSerializeOwned>
and 81 others
note: required by `webcore::serialization::JsSerializeOwned::into_js_owned`
--> src/webcore/serialization.rs:59:5
|
59 | fn into_js_owned< 'a >( value: &'a mut Option< Self > ) -> SerializedValue< 'a >;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error I do not think this error message is useful compare to this one. error[E0277]: the trait bound `webcore::value::Value: std::convert::From<S>` is not satisfied
--> src/webapi/html_elements/slot.rs:60:33
|
60 | let n: Value = new_name.into();
| ^^^^ the trait `std::convert::From<S>` is not implemented for `webcore::value::Value`
|
= help: consider adding a `where webcore::value::Value: std::convert::From<S>` bound
= note: required because of the requirements on the impl of `std::convert::Into<webcore::value::Value>` for `S` Luckily this macro is short and I can find my error pretty quickly (I forgot to
I wonder why my editor won't automatically highlight keyword Markdown didn't highlight
Do you think that it would be easier to implement js_class! {
#[reference(subclass_of(EventTarget, Node, Element, HtmlElement))]
class A extends HtmlElement { // Why `class` and `extends` are not highlighted?
// Can we do `class extends HTMLElement` and ask `js_class!` to return the anonymous class as `Value`?
constructor(&this) {
js_super_constructor!(this);
// Do we still need `js!` to access js methods of `this`?
// ...
}
// ...
}
} than #[custom_element("my-element")] // Extends HtmlElement by default.
#[custom_element(name = "my-element", extends = TextareaElement)]
// ...
fn main() {
// ...
MyElement::define().unwrap();
// ...
} using procedural macro? Anyways, I think there're some common work to do regardless which method is chosen. I guess I will start with modifying |
Could you explain more? I don't see why a framework would want to always define all the methods (especially if it has some performance impact). In any case, we are not creating that framework, we are creating DOM bindings. And the DOM allows you to omit methods (and this omition is important), so we need to provide the ability to omit methods.
I don't mind either way, both options are good.
Okay, since you're not interested, we can leave that for a different PR.
That has nothing to do with macros. The error you're getting is correct and precise, it is not obfuscated by macros. The error is hard to understand because it's leaking internal details about the traits used by the As proof that error messages work with macros, consider the lazy_static! {
static ref Foo: u32 = "foo";
} I get this error:
The error is exactly as it should be. I admit the errors with
All of the Rust syntax is highlighted correctly, and the Rust syntax is 99% of the The
I never said a macro would be easier. The attribute is still a proc macro, it still has to do a lot of the same work. The only thing that might be harder with Also, I'm not sure why you put in a superfluous A fair comparison (with all superfluous details stripped) would look like this: js_class! {
struct Foo(InputElement) extends HTMLInputElement {
// ...
}
}
custom_elements().define_extends("foo", &*Foo, "input"); #[custom_element(name = "foo", extends = "input", inherits = "HTMLInputElement")]
struct Foo(InputElement);
impl ICustomElement for Foo {
// ...
} Except that the first version can omit methods, so in practice it will almost always be much shorter.
That sounds reasonable to me. Though I'm not sure how useful it would be.
If accessing a method which is defined in the class body, it shouldn't require use of the
Custom elements do not inherit from HTMLElement by default (and they should not inherit from HTMLElement by default). |
Web Components is not designed to be a performance-oriented standard. It's designed to help programmer with better boilerplate. You do not need web components to write web pages and the result will be the same. The performance impact of Shadow DOM is way worse than an extra function call. And people are still using it. Why? Because the benefit of avoiding the hassle of tracking css styles and making sure they do not apply to elements that they shouldn't outweighs the performance impact. This is known as opportunity cost. The opportunity cost for me to avoid some extra function calls is very low. I am not willing to write an extra piece of code to accomplish just that nor do those framework creators when they are already way fed up by hundreds of (if not thousands of) open issues. Also, when I am writing a framework, I do not know if the user needs a feature. I have no choice but to implement them all. When the element is constructed, I do not know if the user needs mutation observation at a later point(it may depend on the content assigned to the element by Shadow Dom), but I have to define
This is not true. Normally, I must satisfy the function contract before I pass my variable to another function. This means that my error can never go somewhere else. This is one of the advantages of using a static typed language. However, macros are able to write arbitrary code with no contract check. This pretty much like in Python or JavaScript you can generate code as string and then fn print_it<S: AsRef<str>>(s: S) {
println!("{}", s.as_ref());
}
macro_rules! print_it {
($a:expr) => (println!("{}", $a.as_ref()));
}
fn main() {
print_it("test");
print_it(Ok("test"));
print_it!("test");
print_it!(
//500 lines long
//500 lines long
//500 lines long
//500 lines long
//500 lines long
Ok("test")
//500 lines long
//500 lines long
//500 lines long
//500 lines long
//500 lines long
);
}
error[E0277]: the trait bound `std::result::Result<&str, _>: std::convert::AsRef<str>` is not satisfied
--> src/main.rs:11:5
|
11 | print_it(Ok("test"));
| ^^^^^^^^ the trait `std::convert::AsRef<str>` is not implemented for `std::result::Result<&str, _>`
|
note: required by `print_it`
--> src/main.rs:1:1
|
1 | fn print_it<S: AsRef<str>>(s: S) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E0277]: `std::result::Result<&&str, &_>` doesn't implement `std::fmt::Display`
--> src/main.rs:6:34
|
6 | ($a:expr) => (println!("{}", $a.as_ref()));
| ^^^^^^^^^^^ `std::result::Result<&&str, &_>` cannot be formatted with the default formatter
...
14 | / print_it!(
15 | | //500 lines long
16 | | //500 lines long
17 | | //500 lines long
... |
25 | | //500 lines long
26 | | );
| |______- in this macro invocation
|
= help: the trait `std::fmt::Display` is not implemented for `std::result::Result<&&str, &_>`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: required by `std::fmt::Display::fmt`
error: aborting due to 2 previous errors I do not think we can provide better error message under current macro system.
99% of code you wrote is correct, it's the 1% that's driving
Because this is the reserved name for JavaScript constructor. Other names can be used for other purposes.
I am talking about the complexity to implement
I did put
Because it does make sense to separate methods from values in JavaScript. You can do Also, class definition is can fail(throws an exception) if your class name conflicts with another class, I am not sure how you recover from it if
How about: window().custom_elements().define("my-element", js_class!(class extends HtmlElement {...}));
Can you explain this to me? I think most of the time |
Perhaps a better alternative would be a hybrid of those two, e.g.: (the exact syntax could probably be tweaked further) js_class! {
#[custom_element(name = "foo", extends = "input", inherits = "HTMLInputElement")]
struct Foo(InputElement);
impl ICustomElement for Foo {
// ...
}
} Why? The first snippet requires some finicky custom parsing logic (as
With the
Do we need to recover from that? That sounds to me like a programmer error, in which case a |
I'm not really sure what you're saying here. Stdweb provides low-level DOM bindings, the ability to omit methods is a part of custom elements, thus we must provide that functionality. It doesn't have anything to do with you, or frameworks, or anything else. It's just the way that stdweb does things. If you don't want to write the code, that's fine. I already said we can handle that in a different PR.
Your framework can provide the ability for users to specify what features they want. Just like how stdweb lets you specify that. There's many ways to write frameworks, you're only describing one specific way. If your framework wants to always define all the methods, that's fine. There's nothing wrong with that. But stdweb shouldn't require all methods to be defined. Stdweb needs to be flexible enough to be used for many different use cases, including people who want to write custom elements by hand (without a framework), or people who want to create a framework which doesn't require all methods to be defined. Requiring all methods to be defined will affect everybody, which isn't good.
Yes of course you can do dynamic typing with macros... but that's not relevant to the
Once again... that's not an issue with the macro system, it's an issue with With the But with the If you give type annotations within the macro, then it gives the same error messages with both the function and the macro. Those issues do not apply to the Please stop taking an example of one macro which has bad behavior and then claiming that all macros inherently have that bad behavior. Especially because
I'm not sure what you're talking about, we're discussing syntax highlighters, not In any case, I have no problem with using an And it has nothing to do with
What I mean is, that the
I am talking about the user facing API, and also the complexity to implement it. Both things have to be considered, not just the implementation complexity. When we provide APIs in stdweb, we have to consider many things: the cost to implement it, the cost to maintain it, the cost to use it, the performance, how flexible it is, how much overlap it has with other features, how close it is to the JS API, how "Rust-y" it is, etc. I've tried very hard to keep this conversation neutral and fact-based, without any emotion. I'm not sure why you seem to keep pushing so hard for one approach, and ignoring the pros and cons of other approaches. None of the approaches are perfect, there are complex and difficult trade-offs that we need to make. If the issue is that you don't want to implement it, that's fine, I already said you don't need to implement it. But in that case just say that you don't want to implement it. There's no need to keep arguing based solely upon how easy it is to implement it.
You need to run them through TokenStream for attributes as well, because attributes are proc macros.
I'm sorry, I don't understand this at all. We're discussing the user-facing syntax inside of the
The
That is equivalent to this: {
js_class! { struct Foo(HtmlElement) extends HTMLElement { ... } }
window().custom_elements().define("my-element", &*Foo);
} Except that named classes are a lot easier to implement. Trying to support both named and unnamed classes is tricky and will require some careful thought.
That is just how custom elements work: https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements
It is possible to create custom elements which don't inherit from any HTML elements. In fact that is the default, and you have to go out of your way to inherit from an HTML element. |
I think that's a good idea. I also very much agree that being able to parse it with However, I don't like the js_class! {
#[custom_element(name = "foo", extends = "input")]
#[reference(extends = "HTMLInputElement")]
struct Foo(InputElement);
impl Foo {
fn inited(&self) {
// ...
}
// ...
}
} It will probably require a lot of tweaking and discussion (and prototype implementations) before we can figure out the best syntax. But I think this is a good start.
Thankfully |
I think it would be useful if someone wants to implement a polyfill. |
Well, Opportunity cost is an important factor involved in decision making. It helps decision maker to make better decisions. When people choose between manually check style classes/ids and shadow dom, each option involves a cost. Manually checking style classes/ids is faster in terms of perforamance, but it can become very painful as the application becoming more complex. Shadow Dom can solve this problem with little hassle, but it impacts the performance. Does this mean people who choose to use Shadow Dom does not care about performance? No. It is because the benefit of using shadow dom outweighs the performance gained by tracking styles manually. This also applies to choosing which approach to use for custom elements.
It sounds like when making a web standard, W3C has nothing to do with browser vendors. I do not think this is how things works.
What's the effect of defining all methods? 3~4 extra function calls.
Did I? Or, why you seem to keep pushing so hard for your approach? Will you say "I think that's a good idea." on something that you think is in fact horrible?
Please try harder. This is how the Internet works. I am the one that really does not understand why are you so paranoid about the performance impact on function calls.
For me
Well... I guess you didn't read that page all the way thought.
Also, Shadow Dom says it can be attached to Why I ditch You should be able to learn what's the JavaScript Name from the struct since #[reference(tag_name = "textarea")]
// ...
pub struct TextareaElement; . Why I ditch I think we need some customization for custom elements standard to make it more polished and leverage the advantage of using a compiled, type-safe language. Not just simply make a Javascript class and toss them to |
Well, though I do not think that several extra function calls matters, I do think that omitting them improves readability. How about something like #[custom_element(...)]
impl ICustomElement for MyElement {} ? So We move |
This also provides a macro-free version if somebody doesn't like macros. They can implement |
@futursolo You are acting very unreasonable, so I refuse to comment anymore with you. Goodbye. |
I only post "unreasonable" response to "very unreasonable" questions. To make me "very unreasonable", the questioner has to be "extremely unreasonable" theirselves. I also initially refused to waste my time to honour concerns about "extra function calls" that would effect the overall design. I also said I am not interested in Don't forget to click "mute the thread" or "unsubscribe" to make sure you do not receive more "unreasonable" comments. |
Both of you, please, let's chill, okay? (: It's fine to have disagreements, but successively escalating the arguments is never productive as it usually turns into a shouting match. @Pauan Thanks for all of your input so far. @futursolo Thanks for your work on this PR. It almost looks good to me; I'd like to request two last changes:
Then we can merge this PR as-is and continue discussing the details on how to actually make the web components happen. |
No problem. While I don't regret on what I did, I legitimately feel sorry to everybody else who spent their time to read these non-sense. I truly hope this kinds of drama never happen again. |
Thanks! |
As far as adding support for actually defining web components goes, it would be nice to have something that is minimal, extensible, fully type checked and as declarative as possible. The best way to approach this is, I think, with a procedural macro. E.g. it would be great to get something like this working: web_component! {
#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
#[reference(subclass_of(EventTarget, Node, Element, HtmlElement))]
struct MyElement(HtmlElement);
impl MyElement {
fn constructor(&self) {
// ...
}
fn connected(&self) {
// ...
}
}
}
fn define_custom_elements() {
// ...
window().custom_elements().define("my-element", MyElement);
// ...
} Since you have to call Notice that in the snippet I've posted I didn't explicitly write trait TypeConstructor {
fn type_constructor() -> Value;
} and then making the normal #[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
#[reference(instance_of = "HTMLElement")]
#[reference(subclass_of(EventTarget, Node, Element))]
pub struct HtmlElement( Reference ); emit something like this: impl TypeConstructor for HtmlElement {
fn type_constructor() -> Value {
return js!( return HTMLElement; );
}
} And since when defining classes with trait CustomElement {
fn define() -> Value;
}
impl CustomElement for MyElement {
fn define() -> Value {
return js!(
return class MyElement extends @{HtmlElement::type_constructor()} {
// ...
};
);
}
} The @futursolo Does this sound good to you? |
Since this pull request is merged, can we move the discussion about the syntax for custom elements to an issue? |
Sure! |
Rationale
With the landing of Web Components in Firefox 63, all major browsers will have web components support on by default in a couple of weeks(when Firefox 63 becomes stable). Web Components can provide better performance especially on mobile platforms comparing to other JavaScript-based solutions. I think now it is a good time to implement Web Components support.
Outcome
Web Components consists of Template Element, Shadow DOM, Custom Element and HTML Imports. This pull request aims at implementing all of them except HTML Imports. There are concerns about the disconnection between ES6 Modules and HTML Imports and thus Safari and Firefox refuse to implement it. Frameworks like Polymer used to use HTML Imports has moved away from it. Chrome also deprecated HTML Imports. It may need major revamp before it gains any adoption from browser vendors. Hence, I've decided to not include HTML imports in this pull request.
Steps
TemplateElement
Document.import_node
SlotElement
ISlotable
SlotChangeEvent
ShadowRootMode
ShadowRoot
IElement.attach_shadow
IElement.shadow_root
Custom Elements(Withdrawn due to unexpected circumstances)(I didn't write tests because I don't have Chrome on my computer. I'll make them up eventually.)
Unsolved Question
How should custom elements be constructed?
Approach 1:
Pros:
HTMLTextareaElement
).Cons:
js!
macro?[reference(instance_of = "MyElement")]
work if the class is not defined at the time wasm file is initiated?Approach 2:
Pros:
js!
all javascript code insidedefine()
.Cons:
HTMLTextareaElement
.See also: #32