Search code examples
hyperhtml

Is there a way/workaround to have the slot principle in hyperHTML without using Shadow DOM?


I like the simplicity of hyperHtml and lit-html that use 'Tagged Template Literals' to only update the 'variable parts' of the template. Simple javascript and no need for virtual DOM code and the recommended immutable state.

I would like to try using custom elements with hyperHtml as simple as possible with support of the <slot/> principle in the templates, but without Shadow DOM. If I understand it right, slots are only possible with Shadow DOM?

Is there a way or workaround to have the <slot/> principle in hyperHTML without using Shadow DOM?

    <my-popup>
      <h1>Title</h1>
      <my-button>Close<my-button>
    </my-popup>

Although there are benefits, some reasons I prefer not to use Shadow DOM:

  • I want to see if I can convert my existing SPA: all required CSS styling lives now in SASS files and is compiled to 1 CSS file. Using global CSS inside Shadow DOM components is not easily possible and I prefer not to unravel the SASS (now)
  • Shadow DOM has some performance cost
  • I don't want the large Shadow DOM polyfill to have slots (webcomponents-lite.js: 84KB - unminified)

Solution

  • Let me start describing what are slots and what problem these solve.

    Just Parked Data

    Having slots in your layout is the HTML attempt to let you park some data within the layout, and address it later on through JavaScript.

    You don't even need Shadow DOM to use slots, you just need a template with named slots that will put values in place.

        <user-data>
          <img  src="..." slot="avatar">
          <span slot="nick-name">...</span>
          <span slot="full-name">...</span>
        </user-data>
    

    Can you spot the difference between that component and the following JavaScript ?

        const userData = {
          avatar: '...',
          nickName: '...',
          fullName: '...'
        };
    

    In other words, with a function like the following one we can already convert slots into useful data addressed by properties.

        function slotsAsData(parent) {
          const data = {};
          parent.querySelectorAll('[slot]').forEach(el => {
            // convert 'nick-name' into 'nickName' for easy JS access
            // set the *DOM node* as data property value
            data[el.getAttribute('slot').replace(
              /-(\w)/g,
              ($0, $1) => $1.toUpperCase())
            ] = el; // <- this is a DOM node, not a string ;-)
          });
          return data;
        }
    

    Slots as hyperHTML interpolations

    Now that we have a way to address slots, all we need is a way to place these inside our layout.

    Theoretically, we don't need Custom Elements to make it possible.

        document.querySelectorAll('user-data').forEach(el => {
          // retrieve slots as data
          const data = slotsAsData(el);
          // place data within a more complex template
          hyperHTML.bind(el)`
            <div class="user">
              <div class="avatar">
                ${data.avatar}
              </div>
              ${data.nickName}
              ${data.fullName}
            </div>`;
        });
    

    However, if we'd like to use Shadow DOM to keep styles and node safe from undesired page / 3rd parts pollution, we can do it as shown in this Code Pen example based on Custom Elements.

    As you can see, the only needed API is the attachShadow one and there is a super lightweight polyfill for just that that weights 1.6K min-zipped.

    Last, but not least, you could use slots inside hyperHTML template literals and let the browser do the transformation, but that would need heavier polyfills and I would not recommend it in production, specially when there are better and lighter alternatives as shown in here.

    I hope this answer helped you.