31 мая 2015 в 12:20

Теневой 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 прям конфетка.
Ольховой Дмитрий @auine
карма
–9,0
рейтинг 0,0
Master of Computer Science
Самое читаемое Разработка

Комментарии (23)

  • 0
    А вот компоненты полимера + Backbone прям конфетка.

    Что по поводу серверного рендеринга? Если все состоит из web-компонентов (а не просто используются как часть разметки), я так понимаю решения нет?
    • +5
      Я никогда не поддерживал серверный рендеринг. Между фронт и сервером должны летать только данные. Но есть и множество кейсов где рендерится на серваке. Вы можете сфетчить компоненты и проинициализировать их на фронте, почему нет? Но вы теряете офлайн поддержку.
      • –4
        Между фронт и сервером должны летать только данные.

        Каким образом должна происходить первая загрузка страница? А если остаются только приложения, то зачем вообще нужны браузеры и сопутствующие им проблемы и решения?
        Вы можете сфетчить компоненты и проинициализировать их на фронте, почему нет?

        Ну вот как быть с web-компонентами у которых все в shadow-dom? Сделать его не shadow? Повторно сформировать DOM уже на клиенте?
        • +2
          Боюсь мы друг друга не понимаем. Почитайте на тему стандарта веб-компонентов и одностраничных приложений.
          Приложение не означает, что это, что-то в не браузера))
          • –3
            Боюсь мы друг друга не понимаем. Почитайте на тему стандарта веб-компонентов и одностраничных приложений.
            Приложение не означает, что это, что-то в не браузера))

            Вопрос простой — каким образом должна происходить загрузка первой страницы приложения. В случае нативных приложений операционных систем — пользователь явным образом скачивает его. В случае браузера пользователь ожидает открытия страницы и чем быстрее, тем лучше.
            Если у нас все сайты становятся приложениями, не способными быстро отобразить первую страницу, то теряется смысл браузера — куча ограничений, проблем с безопасностью, скоростью и т.д., в то время как те же приложения поддерживают все основные ОС.
            • –3
              Идеальный вариант — использовать для транспорта данных JSON-LD. Выглядит так:

              <my-element>
                  <script type="application/ld+json">
                      JSON-LD data structure
                  </script>
              </my-element>
              

              При первой загрузке данные можно вставить напрямую, в ходе работы приложения грузить из API и напрямую вставлять таким же образом. JSON-LD понимают и поисковые машины, и в коде распарсить JSON-LD быстро и удобно, и формат данных один на все случаи жизни. А используя алиасы даже структуру БД менять не нужно.
        • 0
          Ну вот как быть с web-компонентами у которых все в shadow-dom? Сделать его не shadow? Повторно сформировать DOM уже на клиенте?

          В Shadow DOM реализация, данные остаются в обычном DOM, в том числе упрощая чтение и разбор DOM поисковым машинам, ибо в коде нет интерфейсного мусора, только семантически размеченные данные.
          • 0
            В Shadow DOM реализация, данные остаются в обычном DOM, в том числе упрощая чтение и разбор DOM поисковым машинам, ибо в коде нет интерфейсного мусора, только семантически размеченные данные.

            Я понимаю, когда компоненты вставляются в обычную верстку, как расширение стандартных элементов, но в случае с Polymer все оборачивается в компоненты, в Body вставляется только <my-app />. А все компоненты хранятся в импортированных HTML.
            • 0
              Нет, совершенно не обязательно. Вы, конечно, можете до такой степени всё обернуть, то здесь ведь тоже есть здравый смысл, верно?
              Не рекламы ради, посмотрите инспектором в Firefox, Chrome: ecois.me/en
              Там семантически размеченные блоки, которые в итоге с помощью веб-компонентов (Polymer 0.5.x) стилизируются, при чём по разному и даже в разном порядке для настольной и мобильной версии. При этом верстка не содержит интерфейсных элементов, только контент. Это простой вариант, некоторые другие показать не могу, но если интересно — пишите в личку.
              • –1
                Понятно, что не обязательно, но судя по примерам самого Polymer и их готовым компонентам, а также примерами многих других — Polymer предполагается как основной инструмент для разметки. И на самом деле, мне нравится этот подход. Но в отличии от ReactJS (ну или другого компонентного фреймворка), web-компоненты нельзя использовать изоморфно.
                • 0
                  Их примеры показывают, что с «правильными», назовем их так, компонентами, большинство вещей можно будет делать декларативно, например, вложить метку в карту, и оно отрендерится ожидаемым образом, хотя у вас заняло 3 строчки HTML и 0 JS.
                  Чем проще ваша верстка, тем проще достичь изоморфности. Вам больше не нужны множественные вложенные шаблоны на сервере и аналогичные на клиенте, вы просто ложите кусочек JSON-LD (полученный прямиком из API) и оборачиваете его в один тэг,. Остальное уже чистый фронтенд и сервера не касается.
                  • 0
                    Дело не в декларативности, чистый HTML и CSS тоже декларативны. Дело в декомпозиции, инкапсуляции, в повторном использовании. Но прикол в том, что чем больше мы реализуем в виде компонентов, тем дольше будет загружаться страница, ведь нативные элементы в бразуере человек скачивает вместе с браузером, а web-компоненты при загрузке страницы.
                    Конечно просто написать изоморфное приложение из 1 тега <my-app />. Но цена…
                    • 0
                      Во-первых на счёт декларативности я упоминал JavaScript, вы не подключите вот так просто с помощью декларативного HTML и CSS карту с меткой на ней, придется писать JavaScript, с веб-компонентами же вы делаете всё декларативно.

                      Во-вторых мы страница загружаться дольше не будет, поскольку если вы не используете веб-компоненты — вы ту же логику и стили закладываете в обычные CSS и JavaScript файлы, то есть браузеру принципиальной разницы нет что выполнять, вот только организацию компонентов вам придется делать самому, что совсем не обязательно будет быстрее нативных веб-компонентов и всего что они предлагают (вроде наследования и тому подобного). О полифиллах сейчас речь не шла.
                      • 0
                        Согласна с тем, что в итоге мы получим одно и то же, и вроде бы страница не должна дольше загружаться, но пока слишком много нюансов. Т.е. когда-нибудь ShadowDOM будет везде и быстро работать, когда-нибудь HTTP2 будет везде работать и можно будет не париться с множественным количеством файлов.
                        Сейчас есть хорошая практика JS выполнять после рендеринга страницы, опять же вопрос, с web-компонентами уже не так красиво получается в разных местах генерировать код?
                        Ну и по поводу привязки данных и навешивания событий, я так понимаю, единого мнения тоже пока нет.
            • 0
              Почему используя один элемент вы выкачиваете все приложение сразу? ;) Главный элемент приложения может подгружать данные по необходимости.
              Так же вы можете организовать структуру приложения иным способом, не говоря уже о том, что просто брать от полимера только те элементы, что вам действительно необходимы, а всю логику писать используя совершенно другой инструмент.
              • 0
                Не могли бы вы написать пример для polymer как сделать lazy подгрузку?
                Просто сколько не смотрю примеров, везде грузится все и сразу.
      • +19
        image
  • –2
    Shadow DOM имплементирует инкапсуляцию, но нативно его только Гугл и поддерживает.

    Уже есть давно под флагом в Firefox, Apple тоже планируют имплементировать, но у них возникли некоторые комментарии/пожелания, сейчас ищут компромисс. По своему опыту скажу что скорости полифиллов уже достаточно, просто в перспективе будет ещё быстрее.
    • +2
      Понимаете, то, что под флагом не допустимо для продакшена. В данном случае мы рассматривали полифил стандарта веб-компонентов, который как вы поняли если даже и оптимизируете, покрыть все потребности вы не сможете.
      • 0
        Конечно, понимаю. Но под поддержкой я имею ввиду что это не вещь, которую эксклюзивно разрабатывает Google и больше никто. Все основные браузеры работают в этом направлении, например, в Firefox уже можно регистрировать пользовательские элементы, теневой DOM под флагом. Не всё сразу)
  • +1
    Довольно красивые контролы, но тест производительности угнетает. Делал добавление 5000 элементов, после чего браузер можно только килять. Что будет на смартфоне я даже представить боюсь.
  • +1
    То есть они хотят делать вид что делают Shadow DOM сделав свой jQuery, который игнорирует типа-шедоу-дом?
    • 0
      Конечно же не свой jQuery, вы используя API можете работать с элементами с помощью jQuery, не вопрос. А по факту, да — разделив композицю на два DOM — темный и светлый ;) Через API вы можете выбирать, в какой из них закинуть элемент или по которому из них сделать селект итд.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.