Веб-разработка стремительно развивается и появляются новые виды различных инструментов разработки: библиотеки и фреймворки. Их появление обусловлено необходимостью быстрой и качественной разработки веб-приложений и получения результата, которого 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 документов.

Спецификация Web Components  располагаются в GitHub репозитории webcomponents.

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

  • Новые элементы 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, предоставляющий доступ к содержимому шаблона. Проверив наличие этого атрибута помогает узнать, поддерживает ли браузет элемент <template>.

Пример 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, в чистом виде редко используется и применяются различные фреймворки и библиотеки, являющиеся обертками вокруг этих чистых методов для более простого использования.

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

Как вы думаете, заменят ли Web Components фреймворки?