[Custom Elements] Not requiring hyphens in names


#1

I started an email thread about the idea here: http://lists.w3.org/Archives/Public/public-webapps/2016AprJun/0045.html

TLDR: I think it’d be nice to be able to name Custom Elements anything without requiring a hyphen, <box> for example. It’d be nice to be able to register elements with the same name as native elements (div, input, p, span, etc) in order to be able to override those elements. Furthermore, browser vendors could continue to add new native elements, but the proposed feature would allow users to override the elements if they wanted to.

Here’s the example from the email thread, which builds on my idea of being able to register elements on a per-shadow-root basis, and shows a pattern similar to React components where the component is a Custom Element, and the shadowRoot is to the Custom Element as what JSX/render() is to a React Component:

//  --------------- SomeElement.js
import MyElement from './MyElement'

export default
class SomeElement extends HTMLElement {
    constructor() {
        this.root = this.createShadowRoot()
        this.root.registerElement('MyElement', MyElement) // <myelement> or <MyElement>

        const frag = document.createDocumentFragment()
        frag.innerHTML = `
            <div>
              <MyElement>
                  ...
              </MyElement>
            </div>
        `
        this.root.appendChild(frag)
    }

    static get observedAttributes() { return [ ... ] }
    connectedCallback() { ... }
    disconnectedCallback() { ... }
    attributeChangedCallback() { ... }
}
//  --------------- app.js
import SomeElement from './SomeElement'
import $ from 'jquery'

// elements registered on the document won't cross into shadow roots
document.registerElement('SomeElement', SomeElement)
$('<someelement>blah blah</someelement>').appendTo(document.body)

#2

I responded in the WebApps thread:

This would prevent us from ever adding any new elements to the language, or at least require us to do real-world usage checks and avoid names that would break too many pages if we took it over. Requiring a dash is a minimal cost to element authors, and permanently avoids any clashes.

This is similar to CSS requiring custom properties to start with a double-dash, like --foo.


#3

No it wouldn’t, because what I suggested is that native elements be overridable. Therefore, if some web app has

shadowRoot.registerElement('xxx', SomeClass)

and then browser vendors introduce an actual xxx element, the web app won’t break because that shadowRoot.registerElement call would continue to override the native xxx element.

Then, if the end user really really wants to use the new native xxx element, that end user can rename the registered element so that it doesn’t override xxx anymore.

This will work just fine, apps won’t break, and browsers can introduce any elements they wish. No problem.


#5

And I further responded about why that’s problematic, too:

That means we lose the lingua franca that HTML provides; two independent libraries can’t ever depend on the core HTML elements, because the other library might have overridden some of them.

Having a well-known common API is worthwhile. (JS technically has this problem, but replacing built-ins, when it’s done, is typically just to expand them. And once modules finally ship, we’ll have a built-in module with pristine versions of all the built-ins, too.)


#6

If a library overrides an element, and another library uses the replaced version, isnt that the desired behavior? It would match the behavior of JS objects.


#7

That’s why I mentioned the idea for allowing registration in ShadowDOM roots: it allows developers to specify what elements are in their component scopes (similarly to JavaScript in React, but in the HTML scope of a Custom Element’s shadow root). The shadow root of an entirely different Custom Element might register a different set of Custom Elements all together.

This would allow more freedom of expression for someone writing HTML components.


#8

FWIW, there was my proposal (2011-09-03) about custom elements that allowed reusing tag names of standard elements in a forward-compatible way and used CSS-like syntax to declare such elements.


#9

@MT Wow, I was a total newb back then. xD It says in the last comment

For example, search engines wouldn’t be able to determine how to process the files that used these new features.

I guess this is true, but is it really that important? Suppose someone, using the suggestion here coupled with the ShdowDOM idea I linked to in the opening post, defines an element

shadowRoot.registerElement('span', CustomClass)

Arguably, at this point it doesn’t even matter because the <span> tag won’t even be part of the page’s initial markup for spiders to consider. But, even if it was, then, why not leave it at the developer’s discretion to determine the meaning of the elements in his app? It’s like variables in JavaScript: developers have freedom to name them conveniently.

If someone goes and turns a SPAN element into some video player at the top level,

document.registerElement('span', VideoPlayerElement)

this changes the meaning os the span tag, but, so? Can’t we tell developers “Hey, we recommend you use native tags for their semantic meaning so engines know what the information in your site means” and just leave it at that? It’s like in JavaScript: “Hey, name your variables with meaning” but some people like cryptic single-letter names, and sometimes the single-letter names are better (f.e. nodes.map(n => console.log(n) is still easy to understand).

Someone might really really like to use <div> tags all the time. The following is a single-letter-variable-like-in-javascript example, possibly making someone’s programming experience more enjoyable:

shadowRoot.registerElement('d', HTMLDivElement)
<d>
  I love Divs and use them all the time!
</d>

Someone might hate single letter elements, so

shadowRoot.registerElement('anchor', HTMLAnchorElement)
shadowRoot.registerElement('paragraph', HTMLParagraphElement)
<paragraph>
  <anchor>This is a link.</anchor>
</paragraph>

A simple component might use a 3D library (disclaimer, I’m working on it) that has MotorHTMLScene and MotorHTMLNode element classes, and it could then be convenient to use them like this:

myComponentShadowRoot.registerElement('s', MotorHTMLScene)
myComponentShadowRoot.registerElement('n', MotorHTMLNode)
myComponentShadowRoot.registerElement('mesh', MotorHTMLMesh) // this one doesn't exist yet
<div>
  <h1>A 3D scene:</h1>

  <s style="width: 600px; height: 400px;">
    <n rotation="0, 30, 0" onUpdate="rotation[1]++">

      <span>This text rotates and has some decorative mesh behind it!</span>

      <n translation="0 0 -200">
        <mesh model="/path/to/file.json">
        </mesh>
      </n>

    </n>
  <s>
</div>

I dislike having to do things like <x-box> just because I had to add a hyphen to the name of an element who’s purpose can be described using a single word word.

Let’s let authors enjoy their programming experience by letting them have more flexibility and customizability!


#10

I absolutley get the reason why I’m not allowed to do this. Although I’m the page author, I don’t own the page entirely. There are user plugins/extensions working with the DOM, there are third party scripts I don’t own, but are included from a different domain and can change update at any time.

But I actually don’t understand why attributes and properties aren’t also name spaced. I know that in an older version of polymer there was the advice to not (re-)use existing attributes and properties. But how should I know any future property/attribute?

What does custom elements say about problems here?

I know, that there where some problems in the past with HTML5, for example some people used required="false" for their JS - form validation hooks.


#11

I don’t understand how this problem is different than global variables in any programming language.

If I make a global variable foo, then my code can rely on it, and if the environment defines a new foo global variable in a future release, it doesn’t matter, it won’t break my code because my global variable will continue to reign.

People in other programming languages know how to work with globals. It can easily be like this in HTML.

For example, if I define a custom element <foo>, then if browsers introduce a new element foo, then the app can still work, because <foo> will still be defined by the app’s code. It just would mean that the app can not take advantage of the new element… but that is okay because the app was never taking advantage of the element in the first place.

On the same principal, defining a custom element <div> should override the global one, just like overriding variables in JavaScript. I don’t see why this can’t work.

Honestly have to put a hyphen in all my custom element names is a complete annoyance.

Plus, currently, there’s nothing stopping libraries like jQuery or framework like Angular from working with non-hyphen names, so introducing new elements that conflict with names used in a jQuery app or Angular app will have much much higher chance of causing problems than global-variable-overriding would. In the case of jQuery or Angular (or any tools that can rely on element names) their functionality doesn’t and never will override built-in behavior, so they can very easily be broken. I would be much more concerned about that.

There’s honestly nothing to worry about with overriding built ins using a native API like custom elements, because the risk of a problem is very low.

Just imagine JavaScript requiring all globals to be prefixed with _, or something. It is not going to happen, and HTML doesn’t need to do it either.


#12

The problem with this is that there’s no scoping to protect other libraries from each other’s edits. Say the following happens:

  1. LibraryA defines a <foo> custom element, and adds them to your page.
  2. <foo> gets added to HTML. LibraryA continues to work fine, as it “overrides” the definition of <foo>.
  3. You add LibraryB to your page, which uses native <foo> elements. It creates one and puts it into your page, and… whoops! LibraryA activates, takes over the element, and suddenly everything’s confused and you have, at best, a badly displaying element, and at worst, script errors.

The same argument is why custom properties in CSS are syntactically distinct from native properties.

JS gets away with custom and built-in things living together in the same namespace because most of the names you write in JS aren’t global, they’re arguments or methods or local variables. We do run into occasional problems adding new things to JS due to collision with global names (function names, class names), but most of the time we are either using a fairly verbose name that probably wont’ collide with anything (CSSMathValue, for example), or are attaching it as a property of an existing global that’s lived for a long time, so authors already know not to override it. (This is why CSS added the CSS namespace object, so we could put useful short names somewhere without risk of colliding with author-defined globals.)


#13

Exactly, you just described the issues with relying on globals.

I think some way to scope names would be great (for the same reason we scope variables in JS).


#14

We could use a colon!

Er, wait…


#15

I like the idea of not having to specify hyphen names in custom elements. However, I can’t get on board with the idea of overriding logic already defined in core level HTML elements. But maybe we can find a middle ground?

As an example, perhaps browsers can throw an error when attempting to override core-level methods/properties on an HTMLElement? But new methods on the element would still be allowed? Another third-party library would never need to know about this custom logic on the element, so no problem there.

EDIT: edited for clarity


#16

How would we achieve this? f.e., If we do this:

class Foo extends HTMLElement {}
customElements.define('foo', Foo)

and then later the browsers come out with a new builtin element called foo and an HTMLFooElement class, the above definition will not have the HTMLFooElement methods and properties, as if they were overridden to be deleted.

What should happen in this case? It seems like the expected behavior would be for the element not to behave like a the builtin foo, so that the app continues to work exactly as it did before. People can optionally rename their elements if they wish to be able to use the new foo element.


#17

Ok, so we allow developer overriding of elements. Then a developer includes a 3rd party component that is expecting the native element not their custom one on top. What should happen? This then brings 3rd party component developers into needing to do even more extensive feature detection on everything. Or developers needing to use a fine tooth comb over every little aspect of their application, even in updates that would otherwise be mundane.

Also, browser vendors could be dissuaded from choosing a solid element name simply because an element already exists with the same name in a large number of sites. Just like new JS methods and properties. So allowing global names muddies the waters for future implementations. And having “the new element simply not take effect or be overridden” isn’t a good option for browser vendors. Everyone should have consistent access to the default elements.

Allowing users to override native element names in the global space isn’t a widely beneficial thing to have. It will only bring in more problems long-term for users. Namespacing them and requiring a dash is a perfectly reasonable way to have a namespace enforced as to not collide with any future native development.


#18

Does it help at all that a lot of folks in standards, including browser vedors are discussing how we do mixins that would allow effectively what you seem to be describing?


#19

I’ve thought a bit about this, and your thoughts are exactly in line with what the TAG was getting at in section 3.3 https://www.w3.org/2001/tag/doc/polyfills/#don-t-squat-on-proposed-names-in-speculative-polyfills .

And I agree with that.

But the customized built-in mechanism exactly addresses this concern, allowing authors to ‘ponyfill’ instead of polyfill, polluting the global namespace.https://github.com/sindresorhus/ponyfill

Finally, why not allow the custom built-in mechanism to be used for HTMLUnknown elements? This would have allowed, for instance, a element to be developed without it having to have been a ‘speculative polyfill’ which now has to be removed because its in the way of the global namespace? https://twitter.com/yoavweiss/status/925760243178622978


#20

Not really. If we’re discussing it, let’s have that discussion here, not behind closed doors.


#21

@trusktr if we have class Foo that extends HTMLElement, and later come out with a builtin Foo, any third party libraries that require the builtin Foo element should announce in their release that they require the builtin Foo element in order for their library to behave as intended. I don’t see why we’re so worried about future-proofing element names that do not yet exist. Because:

  1. the same argument can be said about names with hyphens. It seems short-sighted to always assume a single word for builtin element names in the future when multiple words (separated by a hyphen will be more intuitive).
  2. Some developers were already using Set or a Map as a global variables before they became native classes and we didn’t seem to be concerned, so why are we with these custom element classes?

@Garbee

Ok, so we allow developer overriding of elements. Then a developer includes a 3rd party component that is expecting the native element not their custom one on top. What should happen? This then brings 3rd party component developers into needing to do even more extensive feature detection on everything.

Nah. What the 3rd party component developers should do is announce in their release that their library may not be fully compatible if the builtin element is not used. 3rd party developers have always had the burden of notifying their users of a potential breaking change and making sure they stick to semver to prevent breaking previous usage. I don’t see why this would be any different.