I had a chat with Dmitry Lomov (V8 team/TC39, helped shape the new ES6 classes 
design, CC'ed). His perspective was helpful. He suggested a way of evolving the 
current createdCallback design that I think makes it more palatable, and allows 
us to avoid all of the teeth-gnashing we've been doing in this thread.

- Define a default constructor for HTMLElement that includes something like:

  ```js
  const createdCallback = this.createdCallback;
  if (typeof createdCallback === "function") {
    createdCallback();
  }
  ```

- Detect whether the constructor passed to document.registerElement is the 
default ES class constructor or not. (By default, classes generate a 
constructor like `constructor(...args) { super(...args); }`.) If it is not the 
default constructor, i.e. if an author tried to supply one, throw an error. 
This functionality doesn't currently exist in ES, but it exists in V8 and seems 
like a useful addition to ES (e.g. as `Reflect.isDefaultConstructor`).

- Define the HTMLElement constructor to be smart enough to work without any 
arguments. It could do this by e.g. looking up `new.target` in the registry. I 
can prototype this in https://github.com/domenic/element-constructors.

With these tweaks, the syntax for registering an element becomes:

```js
class MyEl extends HTMLElement {
  createdCallback() {
    // initialization code goes here
  }
}

document.registerElement("my-el", MyEl);
```

Note how we don't need to save the return value of `document.registerElement`. 
`new MyEl()` still works, since it just calls the default `HTMLElement` 
constructor. (It can get its tag name by looking up `new.target === MyEl` in 
the custom element registry.) And, `new MyEl()` is equivalent to the 
parse-then-upgrade dance, since parsing corresponds to the main body of the 
HTMLElement constructor, and upgrading corresponds to proto-munging plus 
calling `this.createdCallback()`.

Compare this to the "ideal" syntax that we've been searching for a way to make 
work throughout this thread:

```js
class MyEl extends HTMLElement {
  constructor() {
    super();
    // initialization code goes here
  }
}

document.registerElement("my-el", MyEl);
```

It's arguable just as good. You can't use the normal constructor mechanism, 
which feels sad. But let's talk about that.

Whenever you're working within a framework, be it Rails or ASP.NET or Polymer 
or "the DOM", sometimes the extension mechanism for the framework is to allow 
you to derive from a given base class. When you do so, there's a contract of 
what methods you implement, what methods you *don't* override, and so on. When 
doing this kind of base-class extension, you're implementing a specific 
protocol that the framework tells you to, and you don't have complete freedom. 
So, it seems pretty reasonable if you're working within a framework that says, 
"you must not override the constructor; that's my domain. Instead, I've 
provided a protocol for how you can do initialization." Especially for a 
framework as complicated and full of initialization issues as the DOM.

This design seems pretty nice to me. It means we can get upgrading (which, a 
few people have emphasized, is key in an ES6 modules world), we don't need to 
generate new constructors, and we can still use class syntax without it 
becoming just an indirect way of generating a `__proto__` to munge later. Via 
the isDefaultConstructor detection, we can protect people from the footgun of 
overriding the constructor.

On a final note, we could further bikeshed the name of createdCallback(), e.g. 
to either use a symbol or to be named something short and appealing like 
initialize() or create(), if that would make it even more appealing :)

Reply via email to