The custom-element registry is immutable per tag name — you cannot redefine a
tag. The trick this runtime uses: per spec, the UA captures an element's
lifecycle callbacks (connectedCallback etc.) and observedAttributes from
the class at definition time. So we register a stable shell class once,
and that shell delegates each lifecycle reaction to the current
implementation. On a hot update the module re-runs customElements.define,
which we intercept to (a) record the new implementation and (b) re-point every
live instance's prototype at it and re-run its lifecycle — no full reload.
Known limitations (documented; full reload is always the fallback):
observedAttributes is fixed at the first definition (the UA reads it once).
The constructor runs once (the first implementation's), so shadow-root setup
persists across swaps; new implementations should (re)populate it in
connectedCallback/render, which is the common pattern anyway.
installHmr is written to be both callable directly (jsdom tests) and
serializable via Function.prototype.toString() for browser injection — so it
must not reference any module-scope binding.
Client-side HMR runtime for custom elements (#8).
The custom-element registry is immutable per tag name — you cannot redefine a tag. The trick this runtime uses: per spec, the UA captures an element's lifecycle callbacks (
connectedCallbacketc.) andobservedAttributesfrom the class at definition time. So we register a stable shell class once, and that shell delegates each lifecycle reaction to the current implementation. On a hot update the module re-runscustomElements.define, which we intercept to (a) record the new implementation and (b) re-point every live instance's prototype at it and re-run its lifecycle — no full reload.Known limitations (documented; full reload is always the fallback):
observedAttributesis fixed at the first definition (the UA reads it once).connectedCallback/render, which is the common pattern anyway.installHmris written to be both callable directly (jsdom tests) and serializable viaFunction.prototype.toString()for browser injection — so it must not reference any module-scope binding.