How should we load web components?

Within the last 6 months, it felt like a good time to get on board properly with Web Components so I've been toying around with bits and pieces. I've been thinking about the ecosystem as a whole and I've also recently been creating a few elements.

One thing that is really unclear to me is that there is no defined best practice for how to include styles and templates (HTML) with your custom element which means as a consumer of Custom elements you are at the mercy of what the component developer thinks is best.

Looking at early guidance, there are two ways:

  1. Use <link rel="import"> that is only supported by Chrome but allows you to bundle CSS, <template>s and other assets needed for element.
  2. Go it alone and figure something out.

When I was making the <air-horner> element I did ship a <link rel=import> file because it seemed like that was the only way to get it working with webcomponents.org but all it does is load the single JS file that encapsulates everything. Instead, I chose to have a single JS file (<script src="air-horner.js"></script>) that you include in your page that defines and registers the custom element. The script file encapsulates the element logic, definition and styling.

I chose this route because I made one decision early on: By including my component the consumer of the custom element should not have un-controlled blocking requests emanate from my element. If something will block the render, then the consumer has decided to do it. This means I don't have any external style sheets and I don't have any external JS either. I don't include a <link rel=stylesheet> in the template and I also don't dynamically fetch a remote file.

This constraint means that I have to think of a way to embed both the template used in my shadowDOM and they styles too without polluting the global scope. I chose to expose a template() function in my custom element that will create and cache a dynamically created <template> element. This template element contains a <style> element and a root <div> that the contains the inline HTML of the element structure.

get template() {
  if(this._template) return this._template;
  else {
    this._template = document.createElement('template');
    let styles = document.createElement('style');
    styles.innerHTML = `:host {}
    /* Lots of CSS*/`;
    let body = document.createElement('div');
    body.id = 'airhorn';
    body.innerHTML = `
      <div class='horn'>
        <div class='inner'>
          <div class='center'></div>
        </div>
      </div>`;
    this._template.content.appendChild(styles);
    this._template.content.appendChild(body);
    
    return this._template;
  }
}

When the element is instantiated I stamp out the shadowDOM and then go to work on attaching functionality to the element DOM.

const root = this.attachShadow({mode:'open'});
root.appendChild(this.template.content.cloneNode(true));

// Now attach handlers...

This works well for the very first version of the element, but it is not entirely extensible. To let the user style the element I have to figure out a way to allow them to inject their own styles and maybe even their own custom HTML, or I can expose extension points via CSS variables. The latter method is quite easy but it pollutes CSS variable namespace and makes the "API" complex to document and hard to discover for developers, the former method, I have no good idea about how to do that in a consistent way.

I really don't want to see an ecosystem where we have to have complex bundling and deployment scripts just so I can drop some fancy elements on my page but I would like to see some commonality about how we include elements in our sites and apps.

I don't have answers at the moment, I only have questions:

I would love to get your thoughts.

Update: Ali Afshar asked why I am using a template element when it is not in the DOM. It's a good question, I don't believe I needed to, but it was a nice way to group multiple elements in something that wasn't a div.

I lead the Chrome Developer Relations team at Google.

We want people to have the best experience possible on the web without having to install a native app or produce content in a walled garden.

Our team tries to make it easier for developers to build on the web by supporting every Chrome release, creating great content to support developers on web.dev, contributing to MDN, helping to improve browser compatibility, and some of the best developer tools like Lighthouse, Workbox, Squoosh to name just a few.

I love to learn about what you are building, and how I can help with Chrome or Web development in general, so if you want to chat with me directly, please feel free to book a consultation.

I'm trialing a newsletter, you can subscribe below (thank you!)