javascriptcustom-elementproxy-pattern

Proxy a WebComponent's constructor that extends HTMLElement


So, in a library that I'm creating that uses custom elements, you obviously need to define the class in the CustomElementsRegistry before you may instantiate it.

As of right now, this is being solved with a decorator:

class Component extends HTMLElement {

    static register (componentName) {
        return component => {
            window.customElements.define(componentName, component);
            return component;
        }
    }
}

@Component.register('my-element')
class MyElement extends Component { }

document.body.appendChild(new MyElement());

This works, however, I would like to automatically register the custom element upon instantiation of the class (so that the author does not have to add the decorator to every single component that they write). This could maybe be accomplished via a Proxy.


My problem, though, is that when I attempt to use a Proxy on the constructor, and attempt to return an instance of the target, I still get Illegal Constructor, as if the element has never been defined in the registry.

This obviously has to do with the way I am instantiating the class inside of the proxy, but I'm unsure of how to do it otherwise. My code is as follows:

Please run in latest Chrome:

class Component extends HTMLElement {

    static get componentName () {
        return this.name.replace(/[A-Z]/g, char => `-${ char.toLowerCase() }`).substring(1);
    }
}

const ProxiedComponent = new Proxy(Component, {

    construct (target, args, extender) {
        const { componentName } = extender;
  	
        if (!window.customElements.get(componentName)) {
            window.customElements.define(componentName, extender);
        }
    
        return new target(); // culprit
    }
});

class MyElement extends ProxiedComponent { }

document.body.appendChild(new MyElement());

How can I continue the chain of inheritance inside of the proxy without losing the context of the fact that I'm instantiating the MyElement class so that it doesn't throw the Illegal Constructor exception?


Solution

  • There were 2 problems:

    The only way i found is to use Reflect API to create right instance of object.

    class LibElement extends HTMLElement {
        static get componentName () {
            return this.name.replace(/[A-Z]/g, char => `-${ char.toLowerCase() }`).substring(1);
        }
    }
    
    const LibElementProxy = new Proxy(LibElement, {
        construct (base, args, extended) {
            if (!customElements.get(extended.componentName)) {
                customElements.define(extended.componentName, extended);
            }
        
            return Reflect.construct(base, args, extended);
        }
    });
    
    class MyCustomComponent extends LibElementProxy {}
    class MyCustomComponentExtended extends MyCustomComponent {}
    
    document.body.appendChild(new MyCustomComponent());
    document.body.appendChild(new MyCustomComponentExtended());

    And I really liked this idea of proxied constructor for auto-registration of custom elements )