|
| 1 | +--- |
| 2 | +layout: default |
| 3 | +type: guide |
| 4 | +shortname: Articles |
| 5 | +title: "What is shady DOM?" |
| 6 | +subtitle: Why do we need a new kind of DOM? |
| 7 | + |
| 8 | +article: true |
| 9 | +description: Why do we need a new kind of DOM? |
| 10 | +published: 2015-05-28 |
| 11 | +#updated: 2014-10-17 |
| 12 | +author: sjmiles |
| 13 | +polymer_version: "1.0" |
| 14 | + |
| 15 | +tags: |
| 16 | +- shadowdom |
| 17 | +- shadydom |
| 18 | +--- |
| 19 | + |
| 20 | + |
| 21 | +{% include authorship.html %} |
| 22 | + |
| 23 | +{% include toc.html %} |
| 24 | + |
| 25 | +## Why a new kind of DOM? |
| 26 | + |
| 27 | +Encapsulation is at the core of web components. |
| 28 | + |
| 29 | +Web components aim to provide the user a **simple element interface** that is |
| 30 | +rendered with **complexity hidden under the hood.** |
| 31 | + |
| 32 | +Browsers often do this kind of encapsulation internally. Elements like |
| 33 | +`<select>` or `<video>` are rendered as unreachable DOM subtrees, only the |
| 34 | +browser vendors really know what’s in there. |
| 35 | + |
| 36 | +There are libraries today that do this sort of thing via JavaScript. For |
| 37 | +example, JQuery plugins allow you to target an element and imbue it with |
| 38 | +abilities. Often a plugin will generate a bunch of DOM for you; this is how it |
| 39 | +adds value. However, these plugin elements are not quite as nifty as `<select>` |
| 40 | +or `<video>` because the DOM used to render the control ends up just sitting |
| 41 | +there in your document, not hidden away. |
| 42 | + |
| 43 | +The shadow DOM standard is aimed at bridging this gap. On browsers that support |
| 44 | +shadow DOM, it’s possible to have an element that is rendered with complex DOM, |
| 45 | +but have that complexity hidden away as implementation detail. Simple Markup is |
| 46 | +Good |
| 47 | + |
| 48 | +Let’s imagine an `x-fade` element that makes an image fade-in when it loads. You |
| 49 | +use it like this: |
| 50 | + |
| 51 | + <x-fade> |
| 52 | + <img src="cool.png"> |
| 53 | + </x-fade> |
| 54 | + |
| 55 | +Let’s imagine we implement this element as a JQuery plugin, and the user “renders” the element this way: |
| 56 | + |
| 57 | + $('x-fade').makeFade(); |
| 58 | + |
| 59 | +The author is happy, because `x-fade` is doing its thing now. |
| 60 | + |
| 61 | +This is the virtue we want from Web Components: the author has used some simple |
| 62 | +markup to acquire fancy functionality. But Web Components makes this experience |
| 63 | +even better. The plugin version has problems with its DOM; problems that are |
| 64 | +solved by shadow DOM. |
| 65 | + |
| 66 | +## DOM Pollution |
| 67 | + |
| 68 | +After the `makeFade` call, let’s say the DOM looks something like this: |
| 69 | + |
| 70 | + <x-fade> |
| 71 | + <div> |
| 72 | + <img src="cool.png"> |
| 73 | + </div> |
| 74 | + <canvas></canvas> |
| 75 | + </x-fade> |
| 76 | + |
| 77 | +`x-fade needs to add some DOM elements to implement its behavior. Sadly, these |
| 78 | +elements are now exposed to the world. Exposing these nodes is problematic: |
| 79 | + |
| 80 | + |
| 81 | +- Details of the implementation are leaking. |
| 82 | +- Queries over the document now include the `<canvas>` and the `<div>`. |
| 83 | +- The new nodes may be affected by stylesheets, because the document author wasn’t expecting them. |
| 84 | +- The `<img>` node may lose styling, because it’s in a different part of the DOM tree now. |
| 85 | +- Can the developer add a new `<img>` or replace the old one? How can she do that, if the node is not where she left it? |
| 86 | + |
| 87 | + |
| 88 | +## Tree Scoping |
| 89 | + |
| 90 | +This is where shadow DOM comes in, or more generally speaking, there is where |
| 91 | +_tree-scoping_ comes in. Tree-scoping is the ability to take a DOM subtree and |
| 92 | +hide it from the main document scope. This is where the shadow nomenclature |
| 93 | +comes from, as if we are hiding some DOM in the shadows (which is awesome, but |
| 94 | +unfortunately sounds a bit nefarious). |
| 95 | + |
| 96 | +If `x-fade` is implemented with shadow DOM, then after calling `makeFade`, the DOM tree looks like this: |
| 97 | + |
| 98 | + <x-fade> |
| 99 | + <img src="cool.png"> |
| 100 | + </x-fade> |
| 101 | + |
| 102 | +That is, exactly how it was before makeFade, which is the point. |
| 103 | + |
| 104 | +The rendering is just as fancy, but from the developer’s perspective, it’s just |
| 105 | +one node with one child, just as she wrote it. |
| 106 | + |
| 107 | +So tree scoping with shadow DOM has solved our problems with the previous |
| 108 | +implementation. Specifically: |
| 109 | + |
| 110 | +- Details of the implementation are hidden. |
| 111 | +- Queries over the document will not see the `<canvas>` or the `<div>`. |
| 112 | +- The new nodes are not affected by stylesheets, because they are not in the document scope. |
| 113 | +- The `<img>` node will not lose styling, because it never moves. |
| 114 | +- The developer can add a new `<img>` or replace the old one, it’s just a regular child of `x-fade`. |
| 115 | + |
| 116 | +## Shadow DOM Encapsulation |
| 117 | + |
| 118 | +If we draw a picture of the DOM that includes the shadow root, we can see where the extra goodies are: |
| 119 | + |
| 120 | + <x-fade> |
| 121 | + <img src="cool.png"> |
| 122 | + #shadow-root |
| 123 | + <div> |
| 124 | + <content select="img"> |
| 125 | + </div> |
| 126 | + <canvas></canvas> |
| 127 | + </x-fade> |
| 128 | + |
| 129 | +OK, there’s our `<canvas>` and our `<div>` (and one other new thing: the `<content>` |
| 130 | +node, this is the bit that tells the browser where and how to combine the |
| 131 | +element's shadow tree with its regular children). |
| 132 | + |
| 133 | +At render time, the element's children and shadow tree are _composed_ into a |
| 134 | +single tree for rendering. In this case, the _composed tree_ looks just like the |
| 135 | +classic jQuery version shown before: |
| 136 | + |
| 137 | + <x-fade> |
| 138 | + <div> |
| 139 | + <img src="cool.png"> |
| 140 | + </div> |
| 141 | + <canvas></canvas> |
| 142 | + </x-fade> |
| 143 | + |
| 144 | +## Shadow DOM is awesome, why is there a shady DOM? |
| 145 | + |
| 146 | +Shadow DOM works by hiding the scoped DOM trees from the traditional tree |
| 147 | +walking functions and accessors (`childNodes`, `children`, `firstChild` and so |
| 148 | +on). These accessors return only the elements in your scope. |
| 149 | + |
| 150 | +To polyfill shadow DOM turns out to be hard. The polyfill must ensure native DOM |
| 151 | +always reflects the composed tree so the platform renders the right thing, while |
| 152 | +only ever revealing the logical tree to the developer. |
| 153 | + |
| 154 | +This means that all the tree-walking accessors (`childNodes`, `children`, |
| 155 | +`firstChild`, and so on) have to be modified so they return custom information. |
| 156 | +To present the logical tree to the developer, one has to wrap the entire DOM API |
| 157 | +surface and indirect the responses through custom data structures. |
| 158 | + |
| 159 | +The Shadow DOM Polyfill actually attempts this task, but there are costs: |
| 160 | + |
| 161 | +- It’s a lot of code. |
| 162 | +- It’s slow to indirect all the DOM API. |
| 163 | +- Structures like `NodeList` can simply not be emulated. |
| 164 | +- There are certain accessors that cannot be overwritten (for example, `window.document`, `window.document.body`). |
| 165 | +- The polyfill returns objects that are not actually Nodes, but Node proxies, which can be very confusing. |
| 166 | + |
| 167 | +Many projects simply cannot afford the Shadow DOM Polyfill, for the reasons |
| 168 | +given above. In particular, the performance costs on platforms like mobile |
| 169 | +Safari are nearly intolerable. |
| 170 | + |
| 171 | +## Shady DOM is Born |
| 172 | + |
| 173 | +Roughly speaking, shady DOM provides a shadow DOM compatible form of tree |
| 174 | +scoping. To go back to our `x-fade` example, the `x-fade` DOM rendered with |
| 175 | +shady DOM looks just like the classic JQuery version: |
| 176 | + |
| 177 | + <x-fade> |
| 178 | + <div> |
| 179 | + <img src="cool.png"> |
| 180 | + </div> |
| 181 | + <canvas></canvas> |
| 182 | + </x-fade> |
| 183 | + |
| 184 | +In other words, strictly speaking it has the same deficiencies. It’s leaking |
| 185 | +details, confusing CSS, and all the rest. |
| 186 | + |
| 187 | +The saving grace is that if you opt-in to looking at the tree with the shady DOM |
| 188 | +API, you can restore the value of shadow DOM. For example, if instead of walking |
| 189 | +`<x-fade>`’s subtree using the traditional API, if you examine it using the shady |
| 190 | +DOM API, you see this: |
| 191 | + |
| 192 | + <x-fade> |
| 193 | + <img src="cool.png"> |
| 194 | + </x-fade> |
| 195 | + |
| 196 | +“Examine it using the shady DOM API” means something like this: |
| 197 | + |
| 198 | + var arrayOfNodes = Polymer.dom(x-fade).children; |
| 199 | + |
| 200 | +The upshot is that shady DOM provides enough tree-scoping for Polymer to act as |
| 201 | +if shadow DOM is available on all platforms, without compromising performance. |
| 202 | + |
| 203 | +It’s important to note that we've made shady DOM and shadow DOM compatible. This |
| 204 | +means that the shady DOM API employs real shadow DOM where it's available. |
| 205 | +Therefore, you can write one code base that works on all platforms, but you |
| 206 | +enjoy improved performance and robustness on platforms that implement Shadow |
| 207 | +DOM. |
| 208 | + |
| 209 | +## Summary |
| 210 | + |
| 211 | +- Web components require tree-scoping for proper encapsulation. |
| 212 | +- Shadow DOM is the standard that implements tree-scoping, but it’s not yet universally implemented. |
| 213 | +- Polyfilling shadow DOM is hard, the robust polyfill is invasive and slow. |
| 214 | +- Shady DOM is a super-fast shim for shadow DOM that provides tree-scoping, but has drawbacks. Most importantly, one must use the shady DOM APIs to work with scoped trees. |
| 215 | +- Shady DOM makes web components palatable for a much wider swath of projects, growing mindshare, and driving adoption of all web components standards. |
| 216 | +- The annoying bits of shady DOM are exactly the reasons why shadow DOM needs to be native across platforms. |
0 commit comments