Effortlessly Registering Custom Elements with Hyphen: A JavaScript Adventure

November 26, 2023 · View on GitHub

Automatically detecting subclass declaration in JavaScript

Diving into the ever-evolving world of JavaScript can sometimes feel like embarking on a coding odyssey. This rings especially true in our latest venture with Hyphen, a spry and capable JavaScript library geared for web development enthusiasts. Our quest? To simplify the registration of custom elements in JavaScript, a challenge we embraced with both hands. This blog post is a chronicle of that adventure, highlighting how Hyphen now adeptly manages both direct and indirect subclass registration, all while keeping the coding experience light, fun, and intuitive.

The Challenge at Hand

Picture this: you're crafting a new subclass in JavaScript, and voila – it automatically registers itself as a custom element. That's the kind of streamlined workflow we envisioned for Hyphen. Our target was two-fold: accommodate both direct subclasses (class MyElement extends $ {...}) and indirect subclasses (class AnotherElement extends MyElement.$ {...}). The aim was to keep the process native to JavaScript's syntax and free from the laborious task of manual registration.

The Route We Explored

Our journey with Hyphen started on familiar grounds, but soon, we found ourselves charting new territories.

1. Manual Registration:

We initially toyed with the idea of explicit registration methods. It was straightforward, sure, but it kind of felt like using a flip phone in the age of smartphones – functional, but not quite there. Calling customElements.define after every class is just so tiresome. And there's no call for a build tool with a single file drop in base class.

2. Proxies Meet Error Stack Traces:

Next we flirted with bizarre approaches that aimed to achieve our multiple goals of minimal syntax noise, minimal keystrokes, and native JS syntax. We explored whether custom properties, like:

class XYZ {
  get [myCustomProp()]() {
     return 'gotcha'
  }
}

were evaluated on declaration. Indeed, they were, however, they were no re-evaluated on subclass declaration, and we wanted something that developrers would not have manually put inside every subclass in order to trigger the detection. That would gain nothing.

3. A Proxy Revolution:

The real game-changer was when we decided to try a more novel approach, using JavaScript Proxies and parsing error stack traces. This was our "aha!" moment, allowing us to gracefully manage both direct and indirect subclassing. By trapping the prototype getter we could reliably detect subclassing across browser platforms.

Meet subclassDetector – Hyphen's New Ace

After much tinkering, we unveiled subclassDetector, a sleek addition to Hyphen's toolbox. This nifty function uses a Proxy to smartly handle subclass detection and registration.

Under the Hood

subclassDetector works its magic by intercepting property accesses and function calls:

function subclassDetector(superclass, onSubclassed) {
  // Clever Proxy logic resides here
  const subclassProxy = new Proxy(superclass, {
    get(target, prop, receiver) {
      if (prop === 'prototype') {
        try {
          onSubclassed(target);
        } catch(e) {
          console.warn(`Error during prototype getter intercept: exception occurred during onSubclassed handler`, e, onSubclassed);
        }
      }
      return Reflect.get(target, prop, receiver);
    },
    apply(target, thisArg, argumentsList) {
      // Assume the first argument is the class to be subclassed
      if (argumentsList.length > 0 && typeof argumentsList[0] === 'function') {
        const subclass = argumentsList[0];
        return subclassDetector(subclass, subclass.onSubclassed);
      }
      throw new Error('Invalid usage of subclassDetector: Expected a class as argument');
    }
  });

  return subclassProxy;
}

The Hyphen base class (that you inherit from when creating all your Custom Elements) implements onSubclassed, which uses Error stack traces to pin down the call location and regex to extract the classname being declared.

The Hyphen Way

With subclassDetector in play, Hyphen offers two smooth patterns for defining custom elements:

  1. Direct Subclassing: class MyElement extends $ {...}
  2. Indirect Subclassing: class AnotherElement extends MyElement.$ {...}

This not only aligns with Hyphen's ethos of keeping things simple and efficient but also adds a touch of elegance to the developer's workflow.

Technical Nuggets and Creative Solutions

  • Dynamic Proxy Detection: Using Proxies allowed us to dynamically detect subclassing actions.
  • Minified Code, No Problem: We took extra care to ensure our approach holds up well, even with minified code.
  • Iterative Innovation: The path we took with Hyphen was filled with incremental improvements, addressing unique challenges and steadily pushing JavaScript to do more for us.

Embracing Hyphen's Core Philosophy

This exploration wasn't just a technical endeavor; it was about staying true to what Hyphen stands for – making web development an enjoyable, creative, and hassle-free experience. With this new feature, developers can let their imagination run wild, leaving the nitty-gritty of custom element registration to Hyphen.

In Conclusion

Our adventure to automate custom element registration in JavaScript, as chronicled in our GitHub repository, stands as a narrative of innovation, technical exploration, and unwavering commitment to enhancing the developer experience. Hyphen has evolved, emerging as a more potent tool in the arsenal of web development.

What's more, this method of subclass detection does not only work client-side with the web platform, it also works in Node.JS. By utilizing a corresponding onSubclassed function for Node you can get this magic JavaScript reflection there, too.

So, fellow developers, we invite you to dive into the world of Hyphen, explore its features, and join us on this continuous journey of reshaping web development into a smoother, more delightful craft.