This pull requests adds support for Custom Elements v1, the official standardized spec recognized by all major platforms. v1 has already landed in Chrome, Safari, and Opera, and development is underway in Firefox and Microsoft Edge.
Custom Elements v1 and v0 are conceptually identical, although they don't share much in common API-wise. For better or worse, those API differences make v1 more difficult to adopt (unless you're only targeting modern browsers and delivering ES2015+ JavaScript code).
v1 gotchas…
Caveat: an unpolyfillable upgrade
There are things that current V1 makes impossible to reproduce via plain JS and the most obvious is the JS instance upgrade. In older engines, extending any DOM native class is not enough to have a proper DOM instance.
— https://www.webreflection.co.uk/blog/2016/08/21/custom-elements-v1#caveat-an-unpolyfillable-upgrade
ES5 vs ES2015
The custom elements v1 spec is not compatible with ES5 style classes. This means ES2015 code compiled to ES5 will not work with a native implementation of Custom Elements.[0] While it's possible to force the custom elements polyfill to be used to workaround this issue (by setting (customElements.forcePolyfill = true;
before loading the polyfill), you will not be using the UA's native implementation in that case.
Since this is not ideal, we've provided an alternative: native-shim.js. Loading this shim minimally augments the native implementation to be compatible with ES5 code. We are also working on some future refinements to this approach that will improve the implementation and automatically detect if it's needed.
[0] The spec requires that an element call the HTMLElement
constructor. Typically an ES5 style class would do something like HTMLElement.call(this)
to emulate super()
. However, HTMLElement
must be called as a constructor and not as a plain function, i.e. with Reflect.construct(HTMLElement, [], MyCEConstructor)
, or it will throw.
— https://github.com/webcomponents/custom-elements/blob/master/README.md#es5-vs-es2015
In my testing, none of the v1 polyfills worked reliably in older versions of Safari or on older Android devices. They're also far more invasive, attempting to patch every possible means of content creation / insertion. Miss one (like Element#insertAdjacentHTML
) and custom elements slip through without upgrading.
v0 on the other hand, even though it's only officially supported in Chrome, has far better support thanks to several battle hardened, MutationObserver
-based polyfills. And so, this pull requests preserves support for v0 too.
To smooth over the v1/v0 API differences, this change introduces its own distinct naming convention for lifecycle callbacks: initialize()
, connect()
, disconnect()
(thanks, Stimulus!), and automatically maps them to the supported custom element API.
v1:
initialize() → connectedCallback() (once)
connect() → connectedCallback()
disconnect() → disconnectedCallback()
v0 (polyfilled if necessary):
initialize() → createdCallback()
connect() → attachedCallback()
disconnect() → detachedCallback()
Fixes #495