Aurelia

Aurelia Shadow DOM v1 Slots Prerelease

Introduction

Aurelia

Aurelia


Aurelia Shadow DOM v1 Slots Prerelease

Posted by Aurelia on .
Featured

Aurelia Shadow DOM v1 Slots Prerelease

Posted by Aurelia on .

The open web and web standards have always been an important part of Aurelia. We don't want to abstract the web away from you, but rather enable you to use cutting edge web technology in a practical way while building applications. Our commitment to these principles brings the Aurelia community the first implementation of Shadow DOM v1 Slots. It's an awesome piece of technology and we look forward to seeing how you leverage it to build amazing apps.

What is Shadow DOM v1 Slots?

The slot mechanism of the Shadow DOM v1 spec is used to declare how two separate DOM trees can be merged together to produce a single visual representation. Why is this important? Well, when creating custom elements, your custom element often has a set of DOM nodes that represent the view of the element. Then, when a developer uses the custom element, that developer may also place HTML inside the content of the element. This results in two DOM trees that must be merged together to produce the final visual.

To illustrate this, imagine an Aurelia name-tag HTML component implemented like this:

<template bindable="color" css="background: ${color}">  
  <require from="./name-tag.css"></require>

  <div class="header">
    <h3>HELLO</h3>
    <h4>my name is</h4>
  </div>

  <div class="body">
    <slot></slot>
  </div>

  <div class="footer"></div>
</template>  

Now, in another view, you want to use this name-tag, providing different names as content:

<template>  
  <require from="./name-tag.html"></require>

  <name-tag color="red">John</name-tag>
  <name-tag color="blue">Bob</name-tag>
</template>  

Look back up at the name-tag view. Do you see the slot element? This tells the renderer where to "project" the content that is placed inside of the name-tag. So, in the first case, the text "John" will be rendered at the location of the slot. At runtime, the composed DOM tree will look something like this:

<name-tag>  
  <div class="header">
    <h3>HELLO</h3>
    <h4>my name is</h4>
  </div>

  <div class="body">
    John
  </div>

  <div class="footer"></div>
</name-tag>  

What Else Can It Do?

The scenario above uses a "default" slot because the slot has no name attribute. In Shadow DOM, you can create as many slots as you want, provided that you give them different names. Then, the content that gets projected into the Shadow DOM must specify which slot it wants using a slot attribute. If it indicates no particular slot (or is plain text) it will get projected into the default slot. Here's an example of named slots:

Shadow DOM Tree for a named-slot Element

<template>  
  <div>
    The first slot:
    <div>
      <slot name="slot1"></slot>
    </div>
    The second slot:
    <div>
      <slot name="slot2"></slot>
    </div>
  </div>
</template>  

Using the named-slot Element

<named-slot>  
  <div slot="slot1">
    This Goes in Slot 1
  </div>

  <div slot="slot2">
    This Goes in Slot 2
  </div>
</named-slot>  

The Composed Visual Tree

<named-slot>  
  <div>
    The first slot:
    <div>
      <div slot="slot1">
        This Goes in Slot 1
      </div>
    </div>
    The second slot:
    <div>
      <div slot="slot2">
        This Goes in Slot 2
      </div>
    </div>
  </div>
</named-slot>  

A nice feature of slots is that they can have fallback content. If nothing gets projected into the slot, the slot will render its fallback content:

Shadow DOM Tree for a fallback-content Element

<template>  
  <div>
    The first slot:
    <div>
      <slot name="slot1"></slot>
    </div>
    The second slot:
    <div>
      <slot name="slot2">This is some fallback content for slot 2...</slot>
    </div>
  </div>
</template>  

Using the fallback-content Element

<fallback-content>  
  <div slot="slot1">
    This Goes in Slot 1
  </div>
</fallback-content>  

The Composed Visual Tree

<named-slot>  
  <div>
    The first slot:
    <div>
      <div slot="slot1">
        This Goes in Slot 1
      </div>
    </div>
    The second slot:
    <div>
      This is some fallback content for slot 2...
    </div>
  </div>
</named-slot>  

Ok, now it's time to get crazy. What if the fallback content generates more slots!? Those fallback slots can be targeted by the content. Here's an example based on a post from the WebKit team:

Shadow DOM Tree for a contact-card Element

<template>  
  <b>Name</b>:
  <slot name="fullName">
    <slot name="firstName"></slot>
    <slot name="lastName"></slot>
  </slot><br>

  <b>Email</b>:
  <slot name="email">Unknown</slot><br>

  <b>Address</b>:
  <slot name="address">Unknown</slot>
</template>  

Using the contact-card Element

<contact-card>  
  <span slot="fullName">John Doe</span>
  <span slot="address">123 Main Street</span>
</contact-card>

<contact-card>  
  <span slot="firstName">Billy</span>
  <span slot="lastName">Bob</span>
  <span slot="email">billy@bob.com</span>
</contact-card>  

The Composed Visual Tree

<contact-card>  
  <b>Name</b>:
  <span slot="fullName">John Doe</span><br>

  <b>Email</b>:
  Unknown<br>

  <b>Address</b>:
  <span slot="address">123 Main Street</span>
</contact-card>

<contact-card>  
  <b>Name</b>:
  <span slot="firstName">Billy</span>
  <span slot="lastName">Bob</span><br>

  <b>Email</b>:
  <span slot="email">billy@bob.com</span><br>

  <b>Address</b>:
  Unknown
</contact-card>  

That was fun! Ok, what about slots, that target other slots with fallback content that generates slots...

Shadow DOM Tree for mixed-slot Element

<template>  
  <div>
    The first slot:
    <div>
      <slot name="slot1">
        Default content for Slot 1
      </slot>
    </div>
    The default slot:
    <div>
      <slot>
        Default Content for the Default Slot
      </slot>
    </div>
    The second slot:
    <div>
      <slot name="slot2">
        The first fallback slot:
        <div>
          <slot name="fallbackSlot1">
            Default Content for Fallback Slot 1
          </slot>
        </div>
        The second fallback slot:
        <div>
          <slot name="fallbackSlot2"></slot>
        </div>
      </slot>
    </div>
  </div>
</template>  

Shadow DOM Tree for slot-to-mixed-slot Element

<template>  
  <require from="./mixed-slot"></require>

  <div>
    <mixed-slot>
      <slot name="slot1" slot="slot1">Fallback Content for Projected Slot 1</slot>
      <slot name="slot2" slot="fallbackSlot2">Fallback Content for Projected Slot 2</slot>
    </mixed-slot>
  </div>
</template>  

Using the slot-to-mixed-slot Element

<slot-to-mixed-slot>  
  <div slot="slot2">This is user content for slot 2. (should appear in fallbackSlot2)</div>
</slot-to-mixed-slot>  

The Composed Visual Tree

<slot-to-mixed-slot>  
  <div>
    The first slot:
    <div>
      Fallback Content for Projected Slot 1
    </div>
    The default slot:
    <div>
      Default Content for the Default Slot
    </div>
    The second slot:
    <div>
      <slot name="slot2">
        The first fallback slot:
        <div>
          Default Content for Fallback Slot 1
        </div>
        The second fallback slot:
        <div>
          <div slot="slot2">This is user content for slot 2. (appearing in fallbackSlot2)</div>
        </div>
      </slot>
    </div>
  </div>
</slot-to-mixed-slot>  

Implementation Limitations

All the above is implemented with Aurelia. It also works with template controllers such as if and repeat which can dynamically generate content. We've fixed up our @child and @children decorators to understand the new model as well.

The only known limitations of our implementation are as follows:

  • You cannot data-bind the slot's name attribute.
  • You cannot data-bind the slot attribute.

We will investigate lifting these restrictions in the future, but they aren't something you'd want to do in most cases as changes in these attributes would result in a full Shadow DOM re-projection, which wouldn't be the most "slick" thing for your users.

One final note about our implementation: We haven't attempted to create a "generic" polyfill designed to be used outside of Aurelia. Our slots implementation is baked into Aurelia's templating compiler and renderer so that it can provide maximum performance and meet the needs of our community. We haven't attempted to implement all the APIs of the spec, but rather to emulate the declarative rendering capabilities of slots. By programming against Aurelia in this way, you don't need to worry about whether or not your browser does or does not support slots natively. Aurelia will take care of it for you.

Performance

The good news is that Shadow DOM v1 no longer relies on CSS selectors for projection. The simple named slot mechanism allows us to do fast key/value lookup, enabling the new implementation to perform better than our previous v0 implementation. Additionally, in order to implement some of the Shadow DOM features, we did some reworking to how custom element content was compiled, resulting in a more light-weight process. This means that performance of custom element rendering is improved across the board.

The performance of default and named slots, as well as fallback content is very good. In general, performance is going to be directly tied to the complexities of the projections involved. Because fallback content can generate more slots, which can generate more slots which can pass-through to other slots...it's quite possible to create a complex performance nightmare for yourself. You know what you are doing though, so we didn't want to limit you, especially since the spec really is this flexible. Just keep in mind that you'll want to consider carefully how you leverage these features, not only to maintain good performance, but also for your own sanity.

Other Improvements

While we were working on Shadow DOM, we took time to reduce duplication of code in several places, improve the techniques used for custom element content in general and cleanup the DOM structures we were using to track views. This all results in a faster and more memory efficient runtime.

How Do I Try It Out?

We're releasing the new Shadow DOM v1 Slot support as an out-of-band release. If you want to start using it now, you will need to install directly from the Github releases. You need two libraries:

We'd like to encourage you to try updating your existing projects early if you can. It will help us to track down any bugs our tests didn't account for and get you moving ahead earlier towards v1 compatibility. We'll release this update through our main release channel as part of the upcoming Release Candidate.

Summary

Today we're excited to announce a pre-release version of our templating engine which includes a full working version of Web Components Shadow DOM v1 Slots. This is the first full implementation in any framework or library and it's our last major milestone on the journey towards Release Candidate.

View Comments...