-
Notifications
You must be signed in to change notification settings - Fork 371
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
[idea] Make use of observedAttributes
optional
#565
Comments
observedAttributes
optional
Isn't the reason for observedAttributes performance? |
While many things changed from Custom Elements v0 to v1, in upgrading elements to v1, I've been surprised that that dealing with As @matthewp indicates, the motivation behind
Each of those @trusktr's proposal above would help, but I think it'd fall down in the case, e.g., where a mixin/subclass wants to observe an attribute. If a mixin/subclass defined Counter-proposal: all attributes except standard attributes always trigger
@domenic If I recall, you were the driving force behind the introduction of |
No, I don't think it's possible to change this at this stage. |
Drat. Okay, thanks. |
On the upside, Will be playing around with it and seeing how it goes... |
Given how observedAttributes was designed, and how the consensus was explicitly to make it mandatory, I'll close this issue. Happy to continue discussing in the closed thread if people are unable to find the meeting notes and GitHub issues documenting that decision on their own. |
I agree, changing the semantics of Defining the list of standard attributes is hard because the list is not finalized. As more attributes are added to the standard, components which relied that those attributes to be not standard will fail to observe the attribute value changes even if the attributes only had a meaning in some builtin elements. As such, this approach poses a serious forward compatibility concern. |
I think having If the attribute name matches the RegExp then the method is triggered. The current Array could be also represented as RegExp: ["my-attr", "my-other"]
/^(?:my-attr|my-other)$/ For performance concerns, if any faster, developers can still use the Array and let browsers optimise it as possible. My 2 cents |
I don't think we want to be evaluating regular expressions against attribute names whenever its value changes. That would be unacceptably expensive. |
Agreed, and I wasn't suggesting that. Implementors can whitelist attributes by name once, instead of each time, so that you'd evaluate only first time it matches the RegExp and from that time on use the list. In better words: the RegExp filters once the list of attributes. On specs level it should probably be simpler, but AFAIK specs don't care much about optimisation details. |
I'm not sure what you mean by whitelist. Basically, anytime an attribute is added, we have to evaluate the regular expression. The alternative is to cache the list of attributes we've matched in the past, which can increase the memory footprint. I don't think either approach is acceptable. |
Only the first time. Meta-speaking
After the first time, you'll do that also only if the name is unknown/different which is usually not common. Of course performance would be penalised compared to just first check but like I've said, we could keep the current Mine was just an idea, it's surely more work for vendors and specs author, but it'd probably make developers happy giving them the ability to intercept whatever they want if/when they want. I personally wouldn't mind keeping as it is, but I understand other developers needs. |
As a web developer, @WebReflection's idea seems like a pretty neat and useful way to split the difference between performance and flexibility here. I definitely appreciate that using an expression of some sort (whether a regex or something else) certainly has a little overhead, but am curious as to whether the memory cost of caching the results is really that high—since the list is specific to the custom element type, not the instance, it doesn’t seem like that’d be a really huge cost. And wouldn’t the cache effectively be the same as just prepending a matched attribute name to the list anyway? Would it be any more expensive (memory-wise) than an author providing a list of 10 strings rather than a list of two expressions? (I do get than this means using mutable, non-fixed length structures, but I have to imagine there are lots of opportunities for smart optimizations, too.) And if we can expect people to use a similar set of attributes on most instances of an element (again, this seems like a reasonable assumption to me), wouldn’t we be likely to arrive at a relatively fixed list of strings that pretty much always matches before falling back to one of the provided expressions pretty quickly? |
With caching, I think this could be useful, and adding it to spec later would remain backwards-compatible. It would also be opt-in, so if an array is provided then no extra memory footprint for the cache list. Although I like the idea of using RegExp for observedAttributes, extending a super class's observedAttributes then becomes tricky (imagine super observedAttributes is a list, and the child class wants to have a RegExp).
The phrase "list of two expressions" makes me think: maybe observedAttributes can be a list as now, but can also contain RegExps as well (with caching as mentioned). In this case, extending super isn't so bad.
From a design-pattern perspective, would it be more web-manifesto-friendly for "global" attributes to be observedAttributes on a base class that all elements extend from, like HTMLElement? That would be a breaking change after v1, but I feel like it makes sense in order for things to be explainable rather than just magic. The downside is that calling super would be required for all custom elements or the custom element would no longer observe things like class MyElement extends HTMLElement {
static get observedAttributes() {
return super.observedAttributes().concat(...)
}
} |
After reading your responses on the original topic, I am okay with current behavior. I should also be less nit picky. :} |
if really needed, there are ways. class MyElement extends HTMLElement {
static get observedAttributes() {
return new RegExp("^my-el-|" + super.observedAttributes().source);
}
} |
@WebReflection But in that example we're assuming that |
So I think requiring class MyElement extends HTMLElement {
static get observedAttributes() {
return super.observedAttributes().concat(/* your new stuff here */)
}
} |
like I've said, I'm OK with current status and extendability through In few words, we are making up inexistent problems because reality would be like the following:
Since people will abuse these things and it's easy to get it wrong, I guess we'll stick with current status and that's it, it's like good old |
FWIW, I don’t feel particularly concerned about the extendability issue—I think the status quo for extension is fine. That said, I think making
I think supporting some kind of expression would also be good in so far as it “explains the platform” for |
I think if we wanted to maximise potential, making it a callback that returns |
We definitely don't want to turn this into a callback. The whole point of this property is to avoid invoking JS whenever possible in the engine code. |
For my case, I ended up with building my own instead of using
What I do is to simply call this from
|
@rniwa, your previous thought of avoiding invoking JS is not really solving performance. Instead of the end user's conditional statements running logic for certain attributes, we've merely moved that logic into the HTML engine, so now the HTML engine has to conditionally check attributeChangedCallback (a, o, n) {
if (a == 'foo') { ... }
else if (a == 'bar') { ... }
else if (a == 'baz') { ... }
// etc...
} So the only thing being saved is some extra no-op function calls that will do nothing because most people will already be checking conditionally for certain attributes. Is there really a significant gain from observedAttributes that outweighs the burden of having to remember to manually list all observed attributes? Are there benchmarks somewhere? |
Yes there is. Going from C++ to JavaScript is expensive. |
Yeah, calling out to JS at all is extremely expensive. The fact we have to remember the old attribute value and queue things up when multiple attributes are modified, etc... all add up to something like 3x overhead the last time I measured. |
What if someone wants to observe any attributes without knowing what they are in advance? Would you prefer for them to reimplement attribute observation like @allenhwkim had to above? |
What are use cases in which you want to observe changes to arbitrary set of attributes? |
@rniwa Common use cases are building an element that wraps an visual object; map, chart, drawing, etc. Assuming your are building a It's not exactly related to this issue, but I have tried to write a google map on this article. https://medium.com/allenhwkim/back-to-element-c4aecf3c6b64 |
That doesn't involve observing all content attributes. What you need is simply a dynamic filtering of a pre-determined set of content attributes. You can do that using |
@rniwa Is there a way to use |
No, you should list those attributes. That would be still much cheaper than having to serialize the style attribute whenever the inline style is changed. |
I would like to add an example for a situation where I do not know the attributes my custom elements will expose in advance. I am writing a library called three-elements that, at import time, walks through the exports of Three.js and dynamically defines a new custom element for every one that can be instantiated. When it encounters an attribute that also exists as a property on the wrapped Three.js object, setting this attribute will also set that object's property. Unless I'm missing something terribly obvious, there is no way to implement this using I've solved this by using the MutationObserver trick described above, and it generally works well, with two caveats:
I understand why things are the way there are and wouldn't even want to go anywhere near suggesting a change -- I merely wanted to present an (admittedly unusual) use case in the hopes that maybe new patterns will emerge in the future. (For example, it would rock to be able to imperatively update the (Or, alternatively, letting me tell the mechanism to yes, please run the attributeChangeCallback for any attribute change. :b) Thanks & stay healthy! |
Note that @hmans case isn't that unusual. A-Frame hit the same issue with their Entity-Component-System, and it kept them on the v0 specs for a long time. I think they might solve this by patching I remember in the very early group meetings that Elliott Sprehn proposed some kind of wildcard with a sigil that would exclude btw, @hmans a solution that works with |
Yeah, I've been thinking (a lot) about approaches like that, but with the Three.js, it's going to prove difficult. The nicest thing I can think of right now is to process its .d.ts files and generate something from them, but that would bind my library to a specific version of Three.js, and that's something I've been wanting to avoid. There's another crazy idea where I have every element (eg. Someone's gonna come up with something. :b Thanks for getting back! |
This is something I have been looking for for quite some time already. When developing Web Components, there are plenty of use cases where you want to update the list of observed attributes in your Component. By providing a simple API, something along the lines of customElements.updateObervableAttributes('my-component') or built into the HTMLElement class, // inside HTMLElement
this.updateObservableAttributes(); The developer could manage the updating of the attributes themselves. |
@Matsuuu the registry is public, meaning every script could update your observed attributes ... in a world first-come, first-serve that idea is as bad as allowing prototype changes after Custom Elements definition. In these cases, using a MutationObserver for attributes doesn't seem like bad at all, so the primitive is there, we just need to use it when appropriate. |
I'm about to do this in three-elements, too, and it feels like a pretty solid solution, especially since, eventually, I can make this smart enough to make What were the obvious problems you were referring to here? |
I've found one drawback: while the overloaded Are there any others? |
weird use case though, but once again, MutationObserver has been created to solve exactly this issue. |
Absolutely, and certainly not critically important in any way. At the moment I'm enjoying that this is something that works because it helps me sell the library ("check it out, you can just change values in the DOM!") - but it is something that I would be happy to give up if (if) the |
A quick update from my side on how I've been tackling this with three-elements: After using MutationObserver for a while, I eventually removed it, and instead went with overloading I've had at least one user of the library tell me that this is a feature they really liked and wanted to see return, so I implemented an opt-in MutationObserver mode that also prints a console warning about its performance impact. All in all, things are plenty good now, but of course if there is a future where I can whitelist all attributes without working around things, I want in. :-) The biggest caveat right now is that there are so many nice web component authoring libraries out there that I would theoretically like to implement in my library, but only few of them allow me to hook into their underlying classes so I can apply my setAttribute trickery. |
Why did you remove it and went for the setAttribute hack? |
For context, it is important to understand that three-elements is, at its core, a game engine. Not a terribly good one, but it does the typical things a game engine does: provide structure, a solid ticker, and have a focus on performance. In games, doing everything within 16.7ms is critically important; in games on the web, where you're often dealing with lower-powered devices and/or want to conserve their batteries, even more so. MutationObserver was posing two problems for me:
The first issue is of course much more critical than the second. Applying the Last but not least, I should add that mutating my elements' attributes isn't even the recommended way to work with the library; I typically advise users of my library to interact with the underlying Three.js object instances directly where possible, much like the react-three-fiber team advises their users to prevent React re-renders, but not only do I think it's great that you can also set attributes without incurring a significant performance cost; ironically it is me who is using three-elements in a way where I have to change elements' attributes and can't work with the wrapped objects directly (I'm working with an experimental stack that involves server-sent and -diffed HTML.) I hope all of this sort of makes sense. Please let me know if you have questions, and even more so please let me know if you believe I had simply been using MutationObserver (or anything else, for that matter) incorrectly. Thanks! |
21132 |
I think it might be nice that if the
observedAttributes
static property is not present on the constructor, then forattributeChangedCallback
to behave as in v0, where it is called for all attribute changes.Unless I misread the spec, there is only a single algorithm that uses
observedAttributes
in the spec, and that algorithm doesn't specify what happens when it is undefined, it only specifies what happens when it is "not undefined".Furthermore, Section 2.6 says
noting the keyword "any" in that sentence.
Based on the above two facts, then there is nothing that states what the behavior should be if
attributeChangedCallback
is defined whileobservedAttributes
is not. In my opinion, it'd be nice for behavior to remain the same as in v0 whereattributeChangedCallback
is called for every attribute that is changed, so that migration from v0 to v1 is easy. Plus, it's easy to opt in to the new behavior anyways by simply defining theobservedAttributes
property.Related (reason why I opened this issue): WebReflection/document-register-element#76
The text was updated successfully, but these errors were encountered: