Fetching web component definitions

Donnie D'Amato April 12, 2022
Source
I love web components. Being able to just plop a custom element onto a page with all the functionality baked in without needing a framework or bundling to make it work is just liberating. I know there’s a lot of overhead that comes with it that libraries and frameworks aim to abstract but I just enjoy having the low-level ability to do whatever you want. The trickiest part of web components for me has been how to identify when a web components, which is written as HTML, needs to be defined through JavaScript. From a performance point-of-view, we don’t want to send JavaScript to the page for components that aren’t there. On the flip side, we don’t want to miss components that apppear later in the lifecycle of the page. The technique I’m about to explain is something I was working on at Compass but was never completed. Since then I’ve also enhanced the approach to be a bit easier to setup. Registrar The core of the approach uses an immediately invoked function expression (IIFE) which does a few things: Listens for new custom elements appearing on the page. Fetches definitions for elements that are not defined. Listens within new shadow roots for additional elements. Let’s describe how it does each of these. Listening for new custom elements The technique for detecting when an element appears in the DOM is not new. It was first discovered by Daniel Buchner as early as 2012. This was the basis behind identifying when a custom element appears on the page with a few changes. First, I use as the trigger for the animation. The reason for this is because it is not commonly used for animations like would be. This avoids possible conflicts where these elements would be animated more traditionally. So the declaration block that triggers the animation would begin to look like this: The missing piece is the selector to target these custom elements. In my original implementation, I would generate the list of elements to find here however, there is a better way. Because what we are looking for is custom elements that are undefined, there’s a CSS selector that can target all of these. Yep, that’s it. Now the animation will trigger for all custom elements that are not yet defined. Greedy registration I’ve noticed that custom elements that are on the page from extensions or other libraries will also attempt to be requested and ultimately fail since they aren’t part of the library. This would be a benefit for having an explicit list of elements to listen for but the failures can be caught and ignored in this implementation or you could filter by prefix (i.e.; but not ). So we will write this CSS to the of the page within the registrar and begin listening for the animation. Fetching the definition This requires a bit of infrastructure. In my custom element library, each component is bundled into its own IIFE, available at in relation to the registrar. We can then determine the location of the components when the registrar is invoked with the following script. The code above determines the url where the current script is located and then removes the file name. I haven’t found a cleaner way to do this without the ugly regex and without requiring to know the file name here. I wish browsers had the module from Node. Then we can determine the location of the components by building a url with this variable. From there, it’s easy to load the definition at this location. Here’s what the registrar looks like with this included. The additional parts added are to ensure we don’t fetch definitions for things we are currently loading or have already loaded and to clean up the scripts being added to the page after they have completed. Handling shadow roots So we’ve solved for when custom elements appear on the page but not when they appear within other custom elements. There’s a little more work to do here. When a custom element is identified, we’ll want to listen inside of its shadow root for undefined elements. We can do that in the event trigger. This will also require us to change the function since shadow roots do not have a . This is what the final registrar function looks like. Missing pieces One edge case that this approach might not catch is when custom elements are dynamically added to defined elements. In other words, if appears on the page, we define it and immediately add the listener to only that shadow root. While it does determine if any custom elements within its lifecycle need definitions, later components added to the page will already be defined and therefore not trigger dynamically added custom elements within those later components (because the triggering resources aren’t added to subsquent shadow roots). To have true coverage, the triggering resources would need to be added to all shadow roots. Luckily, this could be solved by importing the node detection CSS separately within each component which will trigger the registrar to fetch the definition; something that could be part of the library building script. Listening for defined You may have considered another approach to use node detection to just look for defined elements. From there we could just add the target resources to each element; skipping the check since the CSS animation trigger should catch it instead. There’s one catch, the selector will trigger for all elements (, , , etc.) and for every single one of them found on the page. While you could certainly filter for specific custom elements with open shadow roots, it’s still a lot of callbacks firing. Another reason why having a list of elements to update or pre-updating with the triggering resources seems to be better approaches. All together now Once you have the files bundled and deployed, you can just add the registrar to each page and it’ll begin fetching definitions. I’ll add so it doesn’t block the page from loading. And that’s it, we’re automagically getting the definitions for web components on-demand!

Discussion in the ATmosphere

Loading comments...