Skip to content

Instantly share code, notes, and snippets.

@dglazkov
Last active June 26, 2017 17:02
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dglazkov/e8402f9ab18ad7ef2e7d to your computer and use it in GitHub Desktop.
Save dglazkov/e8402f9ab18ad7ef2e7d to your computer and use it in GitHub Desktop.
Why Do We Need Distributions?

#Why Do We Need Distributions?

Suppose you have a custom element <my-editing-bar>, that represents an editing bar. You built it to have one insertion point to add various commands, like B, I, U, and it always appends text color and background buttons itself. So this:

<my-editing-bar>
    <i>I</i>
    <b>B</b>
    <u>U</u>
</my-editing-bar>

Ends up looking like this:

Or live here: http://jsbin.com/somon/1/edit (need "experimental Web Platform features" flag turned on in Chrome)

The <my-editing-bar> itself is built using <my-zebra> custom element, which colors everything you put into its insertion point in alternate colors and makes them look pretty. So this:

<my-zebra>
    <div>Z</div>
    <div>E</div>
    <div>B</div>
    <div>R</div>
    <div>A</div>
</my-zebra>

Ends up looking like this:

Or live here: http://jsbin.com/fedef/1/edit (need "experimental Web Platform features" flag turned on in Chrome)

<my-zebra> may do other cool scriptey stuff, but that doesn't matter in our example.

What matters though is the interaction between <my-editing-bar> and <my-zebra>: the former feeds the elements from its insertion point to the latter. We express this by making <my-editing-bar>'s insertion point a child of <my-zebra>, like so:

<!-- my-editing-bar's shadow tree -->
<my-zebra>
    <content id="a"></content>
    <a href="">text color</a>
    <a href="">background color</a>
</my-zebra>

<!-- my-zebra's shadow tree->
<div>
    <content id="b"></content>
</div>

This is important. The first key observation is that to function as expected, <my-zebra> needs to be aware of insertion points as its children. Otherwise, the interaction between <my-zebra> and its user will go like this:

User: "Hey, custom element, you seem nice. Here's a bag of goodies, look at all the pretty elements in it".

Custom element: "What is this weird bag thing, go away".

User: "But... it's an insertion point, it's replaced by the elements in it, so you should treat them as separate elements, too!"

Custom element: "I don't care".

User: "I think I should replace you with <my-pony>."

Custom element: "Drats."

Mechanically-speaking, <my-zebra> needs to look for <content> elements as their children, crack them open, and treat them as part of the pool of the elements to add to its own insertion points.

Now, a puzzle. Which insertion point is <b>B</b> distributed into? If we assume it resides in content#a, then how can <my-zebra> reason about which shade to color it? If it's in content#b, then how can <my-editing-bar> reason about what it has in a given insertion point? Henceforth, a second key observation: is possible for an element to be in multiple insertion points. If left unchecked, this notion could lead to pretty crazy/scary things, like attempting to rendering the same element in more than one place. We need some constraints over how this whole being-in-many-places-at-once works.

Also, what happens when the user of <my-editing-bar> appends a child to it? Clearly, <my-editing-bar> needs to add this new child to its insertion point. But what about <my-zebra>? Here's the third key observation: to fulfill the "this is working reasonably" expectation, <my-zebra> needs to not only react to changes to its children, but also, if these children are insertion points themselves, to the changes in theirs contents.

In a general case, the elements that belong to any given insertion point are affected by both insertion points in the nesting shadow trees (stuff that uses your custom element) and nested shadow trees (custom element that you use).

The act of understanding these effects and ensuring that all the necessary constraints are satisfied is called distribution, and it's a major part of the Shadow DOM specification.

With distributions, <my-zebra> does not need to have any idea about what came down to its insertion point. It could have real elements, or it could have elements that came from another insertion point, like in our example here. The developer of <my-zebra> does not need to know anything about that.

Conversely, the developer of <my-editing-bar> who uses <my-zebra> does not need to know anything about how <my-zebra> was designed. Will it accept a <content> as its child? Who cares? With distributions, everything just works.

Ultimately, Shadow DOM is primarily about composition. Encapsulation is just a means to an end. The main goal is to help the component's author reason about external effects (things that are happening outside of the component or inside of the things that the component uses) in terms of local effects (what items are in my insertion points). Distribution is the contract that defines how external effects are translated into local effects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment