Веб-разработка стремительно развивается и появляются новые виды различных инструментов разработки: библиотеки и фреймворки. Их появление обусловлено необходимостью быстрой и качественной разработки веб-приложений и получения результата, которого javascript в чистом виде не всегда способен предоставить. Но и реализация новых методов, которые могут сделать разработку эффективней, продолжает происходить. Благодаря этому, хоть и не такому быстрому развитию, как хотелось бы, появились специальные веб-компоненты, которые обещают помогать делать обычными средствами javascript и браузеров то, что раньше можно было сделать только подключив определенную библиотеку или использовав фреймворк.

Web Components (Веб-компоненты) - это технология позволяющая создавать компоненты в веб-документах и веб-приложениях для многократного использования.

Веб-компоненты частично поддерживаются в браузерах Chrome, Firefox, Opera и Safari и работают напрямую без необходимости подключать какие-либо библиотеки. В остальных, где нет поддержки веб-компонентов, используется полифилы.

Полифилы - это код, реализующий функционал, которые не поддерживается по умолчанию в некоторых браузерах. Полифил является специальной библиотекой, которая подключается для поддержки стандартов HTML 5.

Веб-компоненты включают 4 технологии:

  • Custom Elements — API для создания собственных HTML элементов.
  • HTML Templates — тег template для реализации изолированные DOM-элементов
  • Shadow DOM — изолирует DOM и стили в разных элементах.
  • HTML Imports — импорт HTML документов.

Спецификация веб-компонентов определяет следующие пункты:

  • Новые элементы HTML: template
  • Связанные интерфейсы API для новых элементов: HTMLTemplateElement, HTMLContentElement (удален из спецификации) и HTMLShadowElement (удален из спецификации)
  • Расширения для интерфейса HTMLLinkElement и <link>
  • API для регистрации встроенных элементов, Document.registerElement(), и модификации Document.createElement() и Document.createElementNS()
  • Новый "жизненный цикл колбеков" может быть добавлен в прототип, на основе которого создается встроенный элемент
  • Новый CSS псевдо-класс для задания стилей по умолчанию, :unresolved.
  • Теневой DOM: ShadowRoot и Element.createShadowRoot(), Element.getDestinationInsertionPoints(), Element.shadowRoot
  • Расширение для интерфейса Event, Event.path
  • Расширение для интерфейса Document
  • Для задания стилей Веб-компонент:
    • Новые псевдо-классы: :host, :host(), :host-context()
    • Новые псевдо-элементы: ::shadow и ::content
    • Новый комбинатор, /deep/.

Custom Elements

Custom Elements (Пользовательские элементы) - эта технология позволяет создавать свои типы элементов, описывать для них свойства, методы и т.п.

Для регистрации нового элемента используется конструкция:

document.registerElement(name, { prototype: name_prototype }).

Где:

  • name - имя нового элемента (тега). В названии обязательно должен быть дефис. Это сделано, чтобы предотвратить конфликт со стандартными HTML-элементами. Например, нальзя создать элемент footertop или footerTop, а название должно быть footer-top
  • prototype - объект-прототип нового элемента, который должен наследовать от HTMLElement, чтобы у нового элемента были стандартные методы и свойства.

Возможно, непонятно, почему была создана новая возможность создания пользовательских элементов, поскольку уже можно создать имя тега, например, <mytag> и стилизовать его с помощью CSS, а затем использовать скрипты для прикрепления к нему поведения.

Преимущество, которое имеют Custom Elements, - это их жизненные реакции , которые позволяют привязывать поведение к различным частям «жизненного цикла» нового элемента. Например, можно установить определенное поведение, когда элемент вставлен в DOM («подключен»), и другое поведение, когда оно удаляется из DOM («отключено») или когда его атрибуты меняются.

Методы Custom Elements

Пользовательские элементы имеют следующие методы, которые определяют, как они себя ведут:

  • constructor() - вызывается, когда элемент создается или обновляется
  • connectedCallback() - вызывается, когда элемент вставлен в документ, в том числе в теневое дерево
  • disconnectedCallback() - вызывается, когда элемент удаляется из документа
  • attributeChangedCallback(attributeName, oldValue, newValue, namespace) - вызывается, когда атрибут изменяется, добавляется, удаляется или заменяется элементом.
  • adoptedCallback(oldDocument, newDocument) - вызывается, когда элемент принят в новый документ

Пример Custom Elements

<x-product data-name="Ruby" data-img="https://s3-us-west-2.amazonaws.com/s.cdpn.io/4621/ruby.png" data-url="http://example.com/1"></x-product>
<x-product data-name="JavaScript" data-img="https://s3-us-west-2.amazonaws.com/s.cdpn.io/4621/javascript.png" data-url="http://example.com/2"></x-product>
<x-product data-name="Python" data-img="https://s3-us-west-2.amazonaws.com/s.cdpn.io/4621/python.png" data-url="http://example.com/3"></x-product>

 

// Create a class for the element
class XProduct extends HTMLElement {
  constructor() {
    // Always call super first in constructor
    super();

    // Create a shadow root
    var shadow = this.attachShadow({mode: 'open'});

    // Create a standard img element and set its attributes.
    var img = document.createElement('img');
    img.alt = this.getAttribute('data-name');
    img.src = this.getAttribute('data-img');
    img.width = '150';
    img.height = '150';
    img.className = 'product-img';

    // Add the image to the shadow root.
    shadow.appendChild(img);

    // Add an event listener to the image.
    img.addEventListener('click', () => {
      window.location = this.getAttribute('data-url');
    });

    // Create a link to the product.
    var link = document.createElement('a');
    link.innerText = this.getAttribute('data-name');
    link.href = this.getAttribute('data-url');
    link.className = 'product-name';

    // Add the link to the shadow root.
    shadow.appendChild(link);
  }
}

// Define the new element
customElements.define('x-product', XProduct);
body {
  background: #F7F7F7;
}

x-product {
  display: inline-block;
  float: left;
  margin: 0.5em;
  border-radius: 3px;
  background: #FFF;
  box-shadow: 0 1px 3px rgba(0,0,0,0.25);
  font-family: Helvetica, arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

x-product::slotted(.product-img) {
  cursor: pointer;
  background: #FFF;
  margin: 0.5em;
}

x-product::slotted(.product-name) {
  display: block;
  text-align: center;
  text-decoration: none;
  color: #08C;
  border-top: 1px solid #EEE;
  font-weight: bold;
  padding: 0.75em 0;
}

HTML Templates

HTML Templates - это отложенный рендер клиентского контента, который не отображается во время загрузки, но может быть инициализирован при помощи JavaScript. Парсер обрабатывает содержимое элемента template во время загрузки страницы, только лишь чтобы убедиться в валидности содержимого, не отображая при этом само содержимое.

У элемента template есть доступный только для чтения аттрибут content, предоставляющий доступ к содержимому шаблона. Проверив наличие этого атрибута помогает узнать, поддерживает ли браузер элемент emplate.

Пример HTML Templates

<table id="producttable">
 <thead>
   <tr>
     <td>UPC_Code</td>
     <td>Product_Name</td>
   </tr>
 </thead>
 <tbody>
   <!-- existing data could optionally be included here -->
 </tbody>
</table>
<template id="productrow">
 <tr>
   <td class="record"></td>
   <td></td>
 </tr>
</template>
// Проверяем поддерживает ли браузер тег <template>
// проверив наличие аттрибута content у элемента template
if ('content' in document.createElement('template')) {

  // Instantiate the table with the existing HTML tbody and the row with the template
  var t = document.querySelector('#productrow'),
  td = t.content.querySelectorAll("td");
  td[0].textContent = "1235646565";
  td[1].textContent = "Stuff";

  // клонируем новую строку и вставляем её в таблицу
  var tb = document.getElementsByTagName("tbody");
  var clone = document.importNode(t.content, true);
  tb[0].appendChild(clone);
  
  // создаём новую строку
  td[0].textContent = "0384928528";
  td[1].textContent = "Acme Kidney Beans";

 // клонируем новую строку и вставляем её в таблицу
  var clone2 = document.importNode(t.content, true);
  tb[0].appendChild(clone2);

} else {
  // необходимо найти другой способ добавить строку в таблицу т.к.
  // тег <template> не поддерживатся браузером
}

Shadow DOM

Shadow DOM - это инкапсуляция для DOM и CSS в веб-компоненте и делает так, чтобы они оставались отдельно от DOM основного документа.
Суть инкапсуляции в том, чтобы сохранить код отдельно от остальной части страницы, когда, например, на большом сайте,  не очень хорошо организован CSS и стили могут применяться в основной области содержимого, где этого делать не требуется, и наоборот.

Shadow DOM всегда должен быть привязан к существующему элементу: латеральному элементу в файле HTML, элементу созданному в DOM через js, либо пользовательским элементом.

Теневая DOM добавляется с помощью Element.attachShadow().

Пример Shadow DOM

<html>
 <head></head>
 <body>
   <p id="hostElement"></p>
   <script>
     // create shadow DOM on the <p> element above
     var shadow = document.querySelector('#hostElement')
       .attachShadow({mode: 'open'});
   </script>
 </body>
</html>
shadow.innerHTML = '<span>Here is some new text</span>';
<script>
 // Create shadow DOM
 var shadow = document.querySelector('#hostElement')
   .attachShadow({mode: 'open'});
 // Add some text to shadow DOM
 shadow.innerHTML = '<span>Here is some new text</span>';
 // Add some CSS to make the text red
 shadow.innerHTML += '<style>span { color: red; }</style>';
</script>

Shadow DOM состоит из следующих частей:

  • Element.attachShadow()
  • Element.getDestinationInsertionPoints()
  • Element.shadowRoot
  • элемент <template>
  • Элемент <slot>
  • Связанные интерфейсы API: ShadowRoot, HTMLTemplateElementиHTMLSlotElement
  • Селекторы CSS:
    • Псевдо-классы: :host, :host(),:host-context()
    • Псевдо-элементы: ::slotted()
    • Комбинатор:  >>>*

Интерфейсы Shadow DOM

  • ShadowRoot - корневой узел поддерева DOM, который отображается отдельно от основного дерева DOM документа.
  • HTMLTemplateElement - включает доступ к содержимому HTML- <template>элемента.
  • HTMLSlotElement - включает доступ к имени и назначенным узлам <slot>элемента HTML  .
  • DocumentOrShadowRoot - предоставляет API, которые совместно используются документами и теневыми корнями.

HTML Imports

HTML Imports (HTML импорт) - это механизм упаковки для веб-компонентов, но его также можно использовать сам по себе.

Импорт производится с помощью тега link: 

<link rel="import" href="myfile.html">

Полноценные веб-компоненты будут не скоро. Кроме этого, как и все остальное в javascript, в чистом виде редко используется и применяются различные фреймворки и библиотеки, являющиеся обертками вокруг этих чистых методов для более простого использования.

Так что, можно ожидать, что веб-компоненты не заменят фреймворки и библиотеки, а войдут в их текущий функционал и будут сосуществовать наравне с ними. По крайней мере, чтобы заменить фреймворки им придется потягаться с ними. Да и, в общем-то, сами стандарты веб-компонентов, еще не до конца определены.