GrabDuck

Теневой DOM (Shady DOM)

:

На Google I/O нам был представлен Polymer 1.0. Это новый релиз инструмента, который включает ряд особенностей и нововведении. Пожалуй начать стоит именно с Shady DOM.

Зачем нам еще один DOM?


Инкапсуляция является основой веб-компонентов.

Целью веб-компонентов является предоставление пользователю простого интерфейса для отображения сложных элементов, реализация которых скрыта.

Браузеры часто используют инкапсуляцию. Например, элементы <select> и <video>, отображаются используя не доступный для нас DOM, о котором знает только браузер.

Имеется множество библиотек которые пытаются следовать похожему поведению. Например, плагин jQuery, превращающий выбранный вами элемент в слайдер. Как правило, плагин генерирует кучу DOM'а вокруг элемента, пытаясь наделить его типичными для слайдера свойствами и возможностями. Данный подход является отличной практикой, но весь DOM сгенерированный для нужд слайдера не скрыт и находится на странице. Это выглядит далеко не так изящно как использование <select> или <video>.

Shadow DOM нацелен на решение данной проблемы. Браузеры которые поддерживают shadow DOM могут отображать сложные элементы скрывая реализацию (DOM, CSS, JS).

Простая разметка — это хорошо!

Давайте представим элемент x-fade, суть которого заключается в красивом появлении изображения при его загрузке.
<x-fade>
  <img src="cool.png">
</x-fade>

И, допустим, мы реализовали плагин для него:
$('x-fade').makeFade();

Автор будет очень доволен, так как он добьется необходимого ему поведения.

По факту, это все, что нам надо от веб-компонентов — простая разметка для достижения необходимого поведения. Но подход основанный на плагине имеет ряд недостатков, который решает shadow DOM.

Загрязнение DOM


Допустим после вызова makefade, мы имеем следующий DOM:
<x-fade>
  <div>
    <img src="cool.png">
  </div>
  <canvas></canvas>
</x-fade>

Плагину x-fade необходим некоторый DOM, для достижения своей цели. Элементы которые он добавил — открыты, что ведет к следующим проблемам:
  • Детали реализации раскрыты.
  • Селекторы которые проходят по дереву документа будут включать <canvas> и <div>.
  • Так как автор не ожидал появление этих элементов, они могут унаследовать ненужные стили.
  • Элемент <img>, напротив может потерять свои стили, так как он уже не является частью прежнего DOM.
  • Может ли автор добавить новый элемент? Изменить или удалить? Как поступить если элементы уже не там где были изначально.

Tree Scoping


Область видимости дерева, позволяет нам скрыть часть дерева DOM, от основного документа.
Если мы реализуем x-fade используя shadow DOM, то после вызова makeFade, наше дерево будет выглядеть так:
<x-fade>
  <img src="cool.png">
</x-fade>

То есть, абсолютно точно так же как и до инициализации.
Отображение в браузере отличается от того как оно представлено в коде. Для разработчика это по-прежнему элемент, в котором всего один <img>.
Благодаря данной возможности мы решили все вышеперечисленные проблемы. А именно:
  • Детали реализации сокрыты.
  • Выборки проходящие по документу не будут включать в себя canvas и <div>.
  • Новые элементы не будут наследовать стили
  • <img> не потеряет своих стилей, так как он никуда не переместился.
  • Разработчики с легкостью может добавить новое изображение или изменить текущее.

Инкапсуляция Shadow DOM


Если мы решим взглянуть на полную картину того, что же мы получили, то мы увидим следующее:
<x-fade>
  <img src="cool.png">
  #shadow-root
    <div>
      <content select="img">
    </div>
    <canvas></canvas>
</x-fade>

Ага, вот и наши <canvas> и <div>. Так же вы могли отметить новый элемент <content>. Это пример композиции shadow DOM с так называемым light DOM — тот, что мы можем передать элементу.
В момент рендеринга эти два DOM'а объединяются и выглядят как результат работы jQuery (для браузера разумеется, мы же этого не видим):
<x-fade>
  <div>
    <img src="cool.png">
  </div>
  <canvas></canvas>
</x-fade>

Shadow DOM так крут, так зачем нам еще один shady DOM?!


Shadow DOM скрывает дерево DOM, от всего документа. Cелекторы которые мы будем делать по документу ( childNodes, children, firstChild итд) не будут включать в результате скрытые элементы.

Сделать полифил для такого поведения ОЧЕНЬ сложно. Нам необходимо добиться такого же композиционного отображения DOM дерева, при этом скрыть его от логического кода.
Это означает, что нам необходимо модифицировать все доступные методы по работе с элементами, что бы возвращать кастомную информацию.

Мы реализовали такой полифил, но цена:

  • Очень много кода.
  • Переопределение методов, замедляет работу с элементами.
  • Такие структуры как NodeList, нам не подвластны.
  • Аксессоры (например window.document, window.document.body), не могут быть переопределены.
  • Полифил возвращает проксированные объекты, что может запутать.

Большинство проектов попросту не могут быть реализованы из-за перечисленных выше недостатков, а в Safari мы имеем ужасную производительность.

Shady DOM


А-ля франкенштейн, который Гугл всячески пытается похвалить. Жаль, но другого выхода нет

Грубо говоря Shady DOM, предоставляет нам совместимую с shadow DOM модель области видимости дерева. Результат работы мы получим абсолютно точно такой же DOM как и при работе с jQuery плагином.
<x-fade>
  <div>
    <img src="cool.png">
  </div>
  <canvas></canvas>
</x-fade>

А другими словами господа, все те самые недостатки которые мы якобы побороли — открытая реализация, проблемы со стилями и остальные.

Все, что смог от части сохранить Гугл, так это то, как представлено дерево в коде. Но для этого нам ОБЯЗАТЕЛЬНО, необходимо использовать новое API по работе с DOM и только тогда мы будем работать с элементами будто нечего и не произошло и видеть его так:

<x-fade>
  <img src="cool.png">
</x-fade>

На самом деле в пределах элемента, это выглядит вполне себе достойно:
var arrayOfNodes = Polymer.dom(x-fade).children;

Таким образом мы можем работать как и с внутренним DOM так и со светлым DOM.

Из полимера не была вытиснута полностью модель shadow DOM'а. Совместимость shady с shadow позволяет нам писать в одном стиле. Если хотите, вы можете сделать так, что бы полимер решал, где он может использовать shadow DOM нативно, а где включать в работу shady.

Выводы


  • Веб-компонентам необходима инкапсуляция, ага...
  • Shadow DOM имплементирует инкапсуляцию, но нативно его только Гугл и поддерживает.
  • Пытаться сделать полифил для shadow DOM затея сложная и медленная в перспективе.
  • Shady DOM представляет нам супер-быстрый аналог полифила shadow DOM'а, но с некоторыми самой большой кучей недостатков, но у нас есть новое API для вас.
  • Shady DOM дает нам возможность расширить аудиторию приложений которые мы может разрабатывать используя данную модель.
  • Все эти неудобства доказывают, что все платформы должны поддерживать shadow DOM нативно.

На самом деле я очень доволен самим полимером. Как было сказано на конференции, компоненты реакта работают только в реакте, компоненты ангуляра только с ангуляром, а компоненты, написанные с использование полимера — работают везде. Они занимают уровень между веб-платформой и фреймворками. Вы можете использовать их с любым фреймворком или же написать приложение используя только компоненты.

У меня был опыт скрещивания Backbone с React компонентами, но это не так круто, как может показаться. А вот компоненты полимера + Backbone прям конфетка.