Webcomponents: Is there a way to retrieve the localName from a custom element constructor?

Created on 14 Sep 2016  Â·  52Comments  Â·  Source: WICG/webcomponents

I've read the spec and I can't find any mention of how to get the localName of an element from the constructor. In v0 (I know, don't rely on old Blink) you could:

const Ctor = document.registerElement('x-test', {
  prototype: Object.create(HTMLElement.prototype)
});

// x-test
console.log(Ctor.name);

In Chrome Canary this behaves as I'd expect:

class Ctor extends HTMLElement {}
window.customElements.define('x-test', Ctor);

// Ctor
console.log(Ctor.name);

I can see why this is but I feel there's use cases for this. For example, when exporting a custom element constructor, consumers may need to know the name that it got registered with. In https://github.com/webcomponents/react-integration, we use the registered name in order to tell React the element it should create in the virtual DOM. Currently we have to construct the component and get the tagName from it.

A proposal for this may be to add a method to CustomElementRegistry which retrieves the name of the element when passed a constructor:

// any getLocalName(Function constructor)
window.customElements.getLocalName(Ctor);

It could return null if not found, or the localName if found.

Thoughts?

custom-elements needs consensus

Most helpful comment

@annevk:

custom elements have a 1:1 mapping, builtins do not. What kind of API do you envision?
how would a global API for both builtin and custom elements work for scoped registries? Or are scoped registries not something Google-at-large is advocating for?

I agree that this isn't straightforward. For scoped registries, since they're scoped to the shadow root, I would think that whatever API we develop for builtins should "just work" there also. But I suppose that depends on the details of the API. Anyway, since we're okay moving ahead without support for builtins, we can punt these questions until later. I should point out that "Google-at-large" is a big place with differing opinions.

@rniwa:

Given there have been no concrete use case presented for retrieving builtin elements' name given their constructors, I don't think supporting builtin elements make sense.

Ok. We are ok moving ahead without builtin support for now, given the strong developer demand for this feature.

All 52 comments

There is no way to do this. As you say, constructing the component and retrieving the .localName from it is the best way currently.

It sounds like the one use case you've provided so far is working around the fact that React cannot use normal element constructors, i.e. it insists on using createElement? Are there any other use cases that aren't working around framework-specific limitations?

Yeah, React needs the local name so it can do it within the patching algorithm.

In Skate we use Incremental DOM and though it leverages the actual DOM, we still have to tell it the localName of the element to create. This information we currently store internally and pass on.

In the same library (Skate), we also offer a way to auto-generate unique tag names. This is useful if you have two versions of the same component arriving on the same page, for whatever reason. It could be someone is slowly upgrading to the new version of your component, Webpack HMR, writing tests without ensuring the component name is unique (because you can't unregister in a teardown step), etc. While this is trivial to do at the library level - and should be done at the library level - it'd be nice for integrations if there was a standard way to retrieve the localName the constructor was registered for, that way component consumer's don't have to rely on the library the component was written in to do so. To them it's just a web component, as opposed to a Skate component.

Piggy-backing off that, a more compelling use-case may be for libraries like iDOM or React to be able to check internally that if a function is passed (React already supports this as stateless functions), to see if it's a custom element constructor (maybe Func.prototype instanceof HTMLElement or something). If so, then it could do customElements.getLocalName(Func) and then do the diffing and patching. Patching could be done using the constructor if a new element is needed, however, you probably wouldn't want to create a new element to get the localName just to diff element types. Furthermore, instanceof probably wouldn't be appropriate here because Func could be a subclass of the element being compared (and this is assuming they even use real DOM nodes - iDOM does, but I don't think React and other implementations do).

There's probably other use-cases, but those are definitely ones close to our domain that we've encountered and some we use on a daily basis.

I also haven't considered customised built-ins here and haven't thought about it much yet.

We could add something like getName on document.customElements that gives you the name of the custom element given the constructor, which you can obtain via new.target.

That'd do the trick!

My proposal would be renaming the existing get to getInterface and add getName to make the semantics clear. We could even call them findInterface and findName as well.

The use cases still aren't clear to me; they seem very speculative, relating to things that maybe future versions of some libraries might choose to do, or to things that can already be done at the library level.

I don't agree that all my use cases were speculative. If we don't have a
standardised way to retrieve a tag name for a constructor, it forces us to
store that information locally. Since part of our API is to have multiple
working versions on the page, we then force internal APIs to never change
else we break that contract. This would be a really nice feature for us to
have.

On Sat, 17 Sep 2016, 02:33 Domenic Denicola [email protected]
wrote:

The use cases still aren't clear to me; they seem very speculative,
relating to things that maybe future versions of some libraries might
choose to do, or to things that can already be done at the library level.

—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/w3c/webcomponents/issues/566#issuecomment-247646843,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAIVbHhRY2qLU-gMgCDlXEdk3gdRt_yeks5qqsTNgaJpZM4J8SND
.

And FWIW I second @rniwa's proposal, though get* might follow existing
conventions more closely

On Sat, 17 Sep 2016, 08:59 Trey Shugart [email protected] wrote:

I don't agree that all my use cases were speculative. If we don't have a
standardised way to retrieve a tag name for a constructor, it forces us to
store that information locally. Since part of our API is to have multiple
working versions on the page, we then force internal APIs to never change
else we break that contract. This would be a really nice feature for us to
have.

On Sat, 17 Sep 2016, 02:33 Domenic Denicola [email protected]
wrote:

The use cases still aren't clear to me; they seem very speculative,
relating to things that maybe future versions of some libraries might
choose to do, or to things that can already be done at the library level.

—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
https://github.com/w3c/webcomponents/issues/566#issuecomment-247646843,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAIVbHhRY2qLU-gMgCDlXEdk3gdRt_yeks5qqsTNgaJpZM4J8SND
.

I echo @domenic's sentiment. I had a chat with @treshugart offline and suggested changing Incremental DOM to allow it to be agnostic to the element name when given a constructor.

(It seems like it should be able to do a elem.constructor === Ctor check when diffing the DOM, rather than relying on some elem.tagName === getName(Ctor) logic.

Yeah, bouncing ideas off @bradleyayers helped. Though, maybe we're missing something with the theoretical implementation. I know with some built-ins this isn't reliable:

// true
document.createElement('blockquote').constructor === document.createElement('q').constructor;

In my mind it _should_ work because you can't reuse the same class for multiple components, though I feel like I'm missing something.

I know with some built-ins this isn't reliable

I don't think we need it to work with built-ins — it only needs to work with custom elements.

That being said, I'm interested in some concrete examples where builtins don't work as expected, and which environments are affected.

I'm doing some experimentation and if you have access to the diff algorithms, this method works. However, at the moment I'm looking for a way to support different virtual DOM libraries - sort of like a custom element / shadow DOM abstraction - and I'd like to be able to tell the virtual element functions to create an element for a given constructor. Unless all the virtual DOM libraries support doing elem.constructor === Ctor this doesn't seem possible without having a standardised way to get the name that a particular constructor was registered with. Any ideas?

One risk here for v1 is that we have customElements.get and if we're adding a new get method that returns the name given a constructor, then we'll have customElements.get and customElement.getName (or some other name) which would be quite inconsistent. Alternatively, we can make get return a name or a constructor based on argument but that seems rather odd.

Alternatively, we can make get return a name or a constructor based on argument but that seems rather odd.

Yeah, that's probably not a good idea. I think the naming of get is sort of ambiguous anyways. What are we "getting"? In the context of customElements it feels like it should return a "custom element", but it returns a "custom element constructor". If define() was set(), it would make more sense in terms of CustomElementRegistry. Maybe a rename of get before finalising v1 is a good thing, even if getName() is never implemented. getConstructor() seems logical to me.

I don't think we should have getName, and we should not disturb the existing method names even if we add it.

I don't think we should have getName, and we should not disturb the existing method names even if we add it.

Perhaps we need to get a tie breaker from Microsoft or Mozilla. @annevk, @travisleithead ?

I think having get for the common case is fine, even if we add getName later. get coupled with its argument is clear.

This relates a bit to the algorithms discussed in https://github.com/whatwg/html/issues/3452. Given that the browser needs them I'm even more convinced that it's reasonable to expose these.

(Although if we want to support what the browser supports it'd have to return a list...)

Again, Apple is supportive of this proposal since it's a very small API surface to add.

So I think the API should be an overload of customElements.get(). If you pass get() a constructor, it'll return the name. I can work on a PR for HTML. Anyone interested in writing tests?

Built-ins:

  • Solving this could be done, but I'm a little worried that with a global API we might constrain scoped custom element registries in some way (e.g., requiring constructors to only ever be in a single registry). It would then also have to return an array.
  • I also think that if we want this, we should also offer the reverse API for built-ins, e.g., going from h1 to HTMLHeadingElement. Perhaps we could do this by introducing a parallel builtinElements.get() that only works with the builtin registry?

I pretty strongly object to an overload. And I don't think we should solve this if we don't also provide the same facility for built-ins.

At the meeting nobody seemed particularly persuaded that this was a real problem for built-ins, but if it turns out to be a problem it seems we can solve it per my suggestion, no?

And what's wrong with using a union here? (Shouldn't have called it an overload I suppose.)

These are fundamentally different operations, and should use different names.

I don't think this is a custom elements specific problem space, and am not comfortable solving it for custom elements only.

Solution for built-ins:

partial interface Window {
  attribute ElementRegistry builtinElements;
};

interface ElementRegistry {
  any get(DOMString name, optional DOMString? namespace = null);
  sequence<ElementName> getNames(any constructor);
};

dictionary ElementName {
  required DOMString name;
  DOMString? namespace = null;
};

(It seems we're inconsistent on "builtin". HTML uses a hyphen at the moment, IDL does not.)

We'd object to adding this functionality for builtins as I've already mentioned during F2F.

One use case i'm currently finding for this is as follows.

When i try to call a constructor new CustomElementCtro the invocation could either fail because of the registry of an error unrelated to the registry that is rooted in the constructor function.

The framework has measures in place to handle thrown errors, so i want to avoid swallowing both of these, but it's hard to know where the error is from with the current TypeError object since between Chrome, FireFox and Safari only Safari mentions that it is related to the registry(albeit it might just be bad design on my part to try to sniff this info from the TypeError).

However, what i am trying to do is branch only when the error is indeed related to the registry.

This allows me to either 1. Display a specific error message or 2. Branch into generating and defining a unique name for the custom element at runtime. I'm sure it's still currently possible to archive this in author space, however the presence of this API would make it a easier to guarantee with either a has or get variant i.e.

if (customElements.has(CustomElementCtro))
    branch1
else
    branch2

Our component library has just hit a use for this, which came up during our work to support both automatic and manual custom element registration. Like @treshugart and others, we don’t view this issue as speculative.

As that post notes, we have a large body of code that generally works with component constructors instead of tag names. Given a constructor, our auto-registration code would like to be able to find out whether the class has already been registered. Like @thysultan, we currently try instantiation and then catch any resulting TypeError, and are similarly worried we’ll inadvertently catch other kinds of exceptions. It would be simpler and more precise if we could inspect whether class B has already been registered. For this purpose, asking for the component’s registered name (and simply checking for the existence of the result) seems like it would address our need and cover others.

It would seem that the discussion has reached an impasse:

  • @rniwa is open to giving devs a way to retrieve the localName of a registered component given its constructor. He seems to prefer overloading customElements.get because a separate entry point would be “odd”. As I read his words, he does not appear wedded to the idea of overloading; i.e., he might accept a separate entry point like getName. He does appear fixed on his position that this API not be extended to support built-in elements.
  • @domenic, in contrast, does _not_ want an overload, but _does_ require that any solution support built-in elements.
  • @annevk appears flexible on the API, with a preference toward an overload and supporting built-in elements.
  • I don’t see any comments from Edge.

I think we forgot to cover this at TPAC, which would have a been a good forum to hash this out. Is there room for negotiation here?

Since the main sticking point appears to be whether this must extend to built-in elements, perhaps we could pin that down first. I don’t see the case for that. @domenic: Could you summarize your thoughts on that?

I'm not comfortable solving this problem only for one class of elements, and making custom elements further special and unique. A lot of the work we're doing in this repository is specifically to ensure custom and built-in elements have the same capabilities, and I'm not OK with breaking that.

I'm not comfortable solving this problem only for one class of elements, and making custom elements further special and unique.

By "I" do you mean yourself or Google in general? If Google, then it seems like we've reached a stalemate because we're objecting to returning builtin elements. Perhaps we need feedback from Mozilla to break the tie.

@annevk, @smaug----: any opinions here?

Nothing beyond https://github.com/w3c/webcomponents/issues/566#issuecomment-371049783 and earlier comments.

Assuming your comment at https://github.com/w3c/webcomponents/issues/566#issuecomment-371034662 still withstands, it seems like we (Apple) and Mozilla's position more or less align.

Given two browser vendors support this feature, it seems okay to proceed with adding this feature to the specification.

Whilst I don't like adding new features with with a clear opposition from one browser vendor (Google), the fact customized builtin has been merged into the HTML5 specification despite of Apple's clear and repeated objection and opposition seems to establish a clear precedent that a feature with two browser vendor support can be added to the specification even when there is a clear opposition from the third.

It's particularly notable that there is no security or privacy implication from this feature, and the opposition is more of a design principle disagreement

If we end up in the same situation as with customized builtins, I rather not. (We should probably gather some telemetry on those.)

If we end up in the same situation as with customized builtins, I rather not. (We should probably gather some telemetry on those.)

Telemetry on what?

Sorry, customized builtins.

I'm not comfortable solving this problem only for one class of elements, and making custom elements further special and unique. A lot of the work we're doing in this repository is specifically to ensure custom and built-in elements have the same capabilities, and I'm not OK with breaking that.

@domenic Thanks for summarizing your position.

@rniwa You wrote

We'd object to adding this functionality for builtins as I've already mentioned during F2F.

I'm sorry, but I can't recall the specifics of that discussion. Can you summarize the objections here?

I ask because I'm still hoping there's room for compromise here, and the more specific the objections are, the higher the chance is that we can find a way to address everyone's concerns.

One strawman compromise: I'm wondering whether we could address this problem at the same time as scoped custom element registries — by representing the built-ins in a new root registry.

The plan for scoped custom element registries is to have a new scoped registry inherit from an optional parent registry. E.g., a call to myRegistry.get looks in myRegistry first, then in parent registries, up to the base customElements registry.

I was wondering if we could extend that, and model built-ins in a new element registry called rootRegistry (or allElements, globalElements, or something along those lines).

  • The existing customElements registry itself would inherit from this new rootRegistry.
  • A call to myRegistry.get('div') or customElements.get('div') would eventually defer to the rootRegistry, and return HTMLDivElement.
  • We could tweak the currently planned constructor for CustomElementRegistry so that it _always_ required a parent param — if someone didn't want to have the registry inherit from the base customElements registry, they would have to pass in the rootRegistry as the parent. Alternatively, we could define the latter as the implied default value for a missing parent param.

The goal here would be keep the built-ins out of customElements (maybe addressing Ryosuke's concerns?), while at the same including those built-ins in a consistent system for mapping tags to classes and vice versa (thereby hopefully addressing Domenic's concerns). By leveraging the planned work for scoped custom element registries, we hopefully keep down costs and end up with a clean model for all elements.

We'd object to adding this functionality for builtins as I've already mentioned during F2F.

I'm sorry, but I can't recall the specifics of that discussion. Can you summarize t

The objection are two folds:

  • CustomElementRegistry and window.customElements should be about custom elements, not builtin elements. None of existing methods do anything with builtin elements.
  • WebKit doesn't have a mapping of builtin element constructor objects to local names, and we don't want to introduce one just for this API.

@rniwa Okay, thanks, that's very helpful.

CustomElementRegistry and window.customElements should be about custom elements, not builtin elements. None of existing methods do anything with builtin elements.

So a solution like the one I proposed above — representing built-ins in a registry that was separate from customElements — would at least meet this particular requirement, correct? That’s not to say that you'd accept that particular solution; I'm just trying to confirm my understanding of your requirement.

WebKit doesn't have a mapping of builtin element constructor objects to local names, and we don't want to introduce one just for this API.

I don't see a way around that, although I guess also I don't see why that would be costly.

In any event, perhaps the above point is the crux of the matter: you don't see a need to invest in something like this, because you're comfortable with a solution that only works with custom elements. @domenic, meanwhile, is only willing to accept something that works with both. If that's true, maybe we are at a real impasse.

The only compromise I could envision would be if Apple and Google could agree in principle to spreading out this work over time. E.g., to use my rootRegistry strawman above just as an example, let's imagine that everyone thought that was a reasonable solution:

  • Since Apple doesn't see a pressing need to tackle this problem, it would leave out support for a rootRegistry that sits above customElements in the hierarchy.
  • Google, however, could implement it right away because they assign a higher priority to treating built-ins and custom elements equally. Perhaps Mozilla follows suit.
  • Let’s assume some form of polyfill is possible.
  • Eventually, if Apple feels developer interest has reached some threshold that would justify the work, they’d make the investment to implement the feature.

Would this approach (not necessarily the specific solution) work? I’m just looking for a way to resolve the impasse.

CustomElementRegistry and window.customElements should be about custom elements, not builtin elements. None of existing methods do anything with builtin elements.

So a solution like the one I proposed above — representing built-ins in a registry that was separate from customElements — would at least meet this particular requirement, correct? That’s not to say that you'd accept that particular solution; I'm just trying to confirm my understanding of your requirement.

Not if customElements inherits from rootRegistry as you proposed. If it's a completely orthogonal feature, then that might work although I'd object that to such an orthogonal feature for the same reason I stated.

I really don't understand why there is suddenly a need for CustomElementRegistry to suddenly care about builtin elements. Literally every other method in CustomElementRegistry doesn't do anything with builtin elements. Why is this feature so different? e.g. exiting get method doesn't return constructors of builtin elements. Why is it different for getting local names out of constructors?

The only compromise I could envision would be if Apple and Google could agree in principle to spreading out this work over time. E.g., to use my rootRegistry strawman above just as an example, let's imagine that everyone thought that was a reasonable solution

Since we don't want to introduce such a mapping in the first place, I don't see why we'd agree to a solution which involves adding the very thing we don't want eventually.

Maybe there's another feature that makes the constructor-to-localName mapping worth doing aside from just this; for example constructable built-in elements, new HTMLParagraphElement().

Maybe there's another feature that makes the constructor-to-localName mapping worth doing aside from just this; for example constructable built-in elements, new HTMLParagraphElement().

That doesn't necessitates a mapping from constructors to local names in WebKit. (I'm gonna avoid going into technical details on why because that's highly engine specific).

At spring virtual f2f, we discussed that:

  • There are many developers asking for this API for custom element constructors.
  • Nobody has raised concrete use cases for builtin elements.
  • Confirmed that Apple still doesn’t want builtin elements to be supported, and Google has historically opposed having this API without any builtin elements; @mfreed7 took an action item to follow up with @domenic about this objections.

FWIW, I no longer like overloading get() as the return values will be different. That's too weird. getName() seems fine. It does seem like that would have to return a dictionary or some such to account for customized builtins (and perhaps the namespace).

FWIW, I no longer like overloading get() as the return values will be different. That's too weird. getName() seems fine. It does seem like that would have to return a dictionary or some such to account for customized builtins (and perhaps the namespace).

Perhaps an alternative design to return whatever arguments being passed to define like getDefinition.

At spring virtual f2f, we discussed that:

  • There are many developers asking for this API for custom element constructors.
  • Nobody has raised concrete use cases for builtin elements.
  • Confirmed that Apple still doesn’t want builtin elements to be supported, and Google has historically opposed having this API without any builtin elements; @mfreed7 took an action item to follow up with @domenic about this objections.

Per my action item, I spoke to @domenic about this issue. He and I do agree that if we only support custom elements with this API, we're increasing the number of differences between custom elements and built-in elements. We have built-in elements, customized built-in elements, and autonomous custom elements. If I'm handed a constructor, it might or might not work with this new API, depending on what it is, and that feels pretty wrong. @rniwa - I'd be curious to hear whether you disagree with this. In the past, I've seen you arguing pretty strongly for platform consistency. But here you're arguing for inconsistency between custom elements and built-in elements. If the argument is just that the proposed API is on CustomElementRegistry, then perhaps we could move it somewhere more generic like Document?

Having said the above, I do see the obvious developer interest in this capability. We generally support this API, and want to move forward with an interoperable solution. So while we'd prefer to support all constructors, it seems that most/all use-cases center around custom element constructors, so we can go along with an API that only supports custom elements for now. Hopefully it would be defined in such a way that allows expansion later to support built-ins?

@mfreed7 custom elements have a 1:1 mapping, builtins do not. What kind of API do you envision?

Per my action item, I spoke to @domenic about this issue. He and I do agree that if we only support custom elements with this API, we're increasing the number of differences between custom elements and built-in elements.

That ship has sailed long ago when Google insisted that lifecycle callback can't be sync and that we support non-sync / upgrade for custom elements. Custom elements behave noting like builtin elements today and probably never will given the Web compat.

We have built-in elements, customized built-in elements, and autonomous custom elements. If I'm handed a constructor, it might or might not work with this new API, depending on what it is, and that feels pretty wrong.

That's exactly what customElements.get does for a custom element name.

@rniwa - I'd be curious to hear whether you disagree with this. In the past, I've seen you arguing pretty strongly for platform consistency. But here you're arguing for inconsistency between custom elements and built-in elements. If the argument is just that the proposed API is on CustomElementRegistry, then perhaps we could move it somewhere more generic like Document?

It would definitely be more consistent to have customElements.getName which returns the name of a custom element for a constructor given we already have customElements.get which implements the inverse function.

To begin with, this will be an API on an interfaced named CustomElementRegistry. Why we need to care about builtin elements at all in API about custom elements? The point of CustomElementRegistry is to query and work with custom elements, not builtin elements.

Having said the above, I do see the obvious developer interest in this capability. We generally support this API, and want to move forward with an _interoperable_ solution. So while we'd prefer to support all constructors, it seems that most/all use-cases center around custom element constructors, so we can go along with an API that only supports custom elements for now.

Given there have been no concrete use case presented for retrieving builtin elements' name given their constructors, I don't think supporting builtin elements make sense.

@mfreed7 how would a global API for both builtin and custom elements work for scoped registries? Or are scoped registries not something Google-at-large is advocating for?

@annevk:

custom elements have a 1:1 mapping, builtins do not. What kind of API do you envision?
how would a global API for both builtin and custom elements work for scoped registries? Or are scoped registries not something Google-at-large is advocating for?

I agree that this isn't straightforward. For scoped registries, since they're scoped to the shadow root, I would think that whatever API we develop for builtins should "just work" there also. But I suppose that depends on the details of the API. Anyway, since we're okay moving ahead without support for builtins, we can punt these questions until later. I should point out that "Google-at-large" is a big place with differing opinions.

@rniwa:

Given there have been no concrete use case presented for retrieving builtin elements' name given their constructors, I don't think supporting builtin elements make sense.

Ok. We are ok moving ahead without builtin support for now, given the strong developer demand for this feature.

I should point out that "Google-at-large" is a big place with differing opinions.

Sure, but in the end what matters is what you're planning to ship in Chrome and figuring that out internally would save us all a lot of time.

I should point out that "Google-at-large" is a big place with differing opinions.

Sure, but in the end what matters is what you're planning to ship in Chrome and figuring that out internally would save us all a lot of time.

I can't guarantee a schedule for getting this change implemented in Chromium, but you can count us as supportive.

Was this page helpful?
0 / 5 - 0 ratings