Нужны ли нам веб-компоненты?

Введение

Около 10 лет назад была написана первая черновая версия спецификации Web Components (веб-компонентов) с изложением ее основных концепций. Первоначально она была написана Google, а затем доработана WHATWG и опубликована как версия v1. Технология привнесла в цифровое поле новые стандарты и возможности.

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

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

  • Настраиваемые элементы. Многократно используемые элементы с пользовательским поведением, применяемые для создания пользовательского интерфейса. Они могут расширять текущие элементы HTML или определяться как совершенно новые пользовательские элементы.
  • Shadow DOM (теневая модель документа). Инкапсулированная часть дерева компонентов, отображаемая отдельно от DOM основного документа и имеющая свои стили и поведение. Это позволяет скрыть внутреннюю часть компонента от остальной части DOM, обеспечивая при этом ту же функциональность, для которой он был изначально разработан.
  • HTML-шаблоны. Способы вставки фрагментов HTML, содержащихся внутри элемента <template>, который не отображается на странице, но может быть использован в качестве основы для структуры пользовательского элемента. Элементы также могут иметь заполнители, определенные с помощью элементов <slot>, которые впоследствии могут быть заполнены вашей разметкой.

Эти технологии изначально доступны в современных браузерах и могут поддерживаться в старых браузерах с помощью полифиллов. Веб-компоненты также можно использовать в сочетании с библиотеками и фреймворками, такими как React, Vue и другими. Это, а также тот факт, что GitHub, Google и другие компании, используют их в своих флагманских продуктах, делает их интересными с точки зрения вариантов применения.

Краткий курс по веб-компонентам

Прежде чем ответить на вопрос, нужны ли нам веб-компоненты, попытаемся лучше понять, как они используются и работают. Итак, рассмотрим несколько примеров. Начнем с определения класса, который будет содержать логику пользовательского элемента (такие элементы называются автономными пользовательскими элементами). Затем определим шаблон и слоты, чтобы выяснить, как будет отображаться элемент. И наконец, чтобы этот класс можно было использовать, зарегистрируем его в CustomElementRegistry, который доступен в виде глобальной переменной под именем customElements.

Конечный результат будет выглядеть примерно так:

Пример веб-компонента

Для этого примера создадим два файла: main.js, который содержит пользовательский класс и регистрацию пользовательского элемента, и index.html, содержащий шаблон и ссылку на скрипт main.js.

Содержимое файла main.js:

customElements.define('person-details', // customElements - это глобальная переменная, представляющая CustomElementRegistry. В этом шаге мы непосредственно регистрируем пользовательский элемент с именем person-details
class extends HTMLElement {
constructor() {
super();

const template = document.getElementById('person-template'); // использование шаблона, определенного в index.html
const templateContent = template.content;

const shadowRoot = this.attachShadow({mode: 'open'}); // добавление корня тени

const style = document.createElement('style'); // добавление стилей
style.textContent = `
div { padding: 10px; border: 1px solid gray; width: 200px; margin: 10px; }
h2 { margin: 0 0 10px; }
ul { margin: 0; }
p { margin: 10px 0; }
::slotted(*) { color: gray; font-family: sans-serif; }
`; // slotted - это псевдоэлемент CSS, который представляет любой элемент, помещенный в слот

shadowRoot.appendChild(style); // добавление стилей к корню тени
shadowRoot.appendChild(templateContent.cloneNode(true)); // добавление
}
});

Содержимое файла index.html:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>::slotted example</title>
<script src="main.js" defer></script>
</head>
<body>

<template id="person-template">
<div>
<h2>Personal ID Card</h2>
<!-- элементы слота обозначают разделы, которые впоследствии заполняются в пользовательском определении элемента -->
<slot name="person-name">NAME MISSING</slot>
<ul>
<li>
<slot name="person-age">AGE MISSING</slot>
</li>
<li>
<slot name="person-occupation">OCCUPATION MISSING</slot>
</li>
</ul>
</div>
</template>

<person-details>
<!-- элементы с атрибутом slot будут заполнять вышеуказанные элементы slot -->
<p slot="person-name">Morgan Stanley</p>
<span slot="person-age">36</span>
<span slot="person-occupation">Accountant</span>
</person-details>

</body>
</html>

Существующие элементы HTML также могут быть расширены посредством пользовательской функциональности (такие элементы называются пользовательскими встроенными элементами). При этом регистрация и определение элемента выглядят несколько иначе, чем при создании нового элемента. Основное отличие заключается в том, что расширяемый элемент должен быть явно определен. Рассмотрим способ расширения нативной кнопки HTML. 

В этом примере расширим нативную кнопку с помощью диалогового окна подтверждения, в котором у пользователя спрашивается, хочет ли он продолжить текущее действие.

Содержимое файла main.js:

class ConfirmButton extends HTMLButtonElement { // обратите внимание: это расширяет HTMLButtonElement
constructor() {
super();

this.addEventListener('click', e => {
const result = confirm(this.confirmText);

if (!result) {
e.preventDefault();
}
});
}

get confirmText() {
return this.getAttribute("confirm-text");
}

}

// обратите внимание: это указывает на расширение элемента кнопки
customElements.define('confirm-button', ConfirmButton, {extends: "button"});

Содержимое файла index.html:

<!-- обратите внимание на разницу: это все еще элемент кнопки, но с атрибутом "is", установленным на пользовательский элемент -->
<button is="confirm-button" confirm-text="Do you want to proceed?">
Click to confirm
</button>

С помощью веб-компонентов очень легко создавать прототипы новых элементов, поскольку API изначально поддерживается браузером. Чтобы просмотреть отрендерированные компоненты, достаточно открыть их в браузере. Для разработки компонентов, готовых к производству, необходимо по крайней мере минифицировать их или использовать Typescript или что-то подобное, что требует наличия инструментов сборки, предоставляющих подобные услуги. Но возможность сразу увидеть результат  —  это большое преимущество при простом создании прототипов и их тестировании.

Веб-компоненты имеют свой жизненный цикл, представленный методами класса callback, вызываемыми в различные моменты жизненного цикла элемента.

  • connectedCallback: вызывается при добавлении пользовательского элемента в DOM (также вызывается при каждом перемещении узла). Аналогичен componentDidMount в React и ngOnInit в Angular.
  • disconnectedCallback: вызывается, когда пользовательский элемент удаляется из DOM. Аналогичен componentWillUnmount в React и ngOnDestroy в Angular.
  • attributeChangedCallback: вызывается, когда атрибут пользовательского компонента изменяется в DOM, но только для атрибутов, которые определяются в свойстве observedAttributes. Аналогичен componentDidUpdate в React и ngOnChanges в Angular.
  • acceptedCallback: вызывается, когда пользовательский элемент перемещается в новый документ.

Как видим, колбэки, доступные в современных библиотеках/фреймворках для веб-разработки, доступны и в веб-компонентах. Большинство сценариев использования можно перевести напрямую, например, componentDidMount в connectedCallback, componentWillUnmount в disconnectedCallback и т. д. Простой пример сравнения веб-компонентов и колбэков жизненного цикла React будет выглядеть примерно так:

class LifecycleMethods extends HTMLElement {
// Указание наблюдаемых атрибутов, необходимых
// для работы attributeChangedCallback
static get observedAttributes() {
return ['value', 'disabled'];
}

constructor() {
// В конструкторе всегда вызывайте super-элемент первым
super();

console.log('Custom element constructor called.');
}

connectedCallback() {
console.log('Custom element added to page.');
}

disconnectedCallback() {
console.log('Custom element removed from page.');
}

adoptedCallback() {
console.log('Custom element moved to new page.');
}

attributeChangedCallback(name, oldValue, newValue) {
console.log('Custom element attributes changed.');
}
}

customElements.define('lifecycle-methods', LifecycleMethods);

Удобство использования и возможность повторного применения веб-компонентов

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

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

Большинство современных популярных библиотек и фреймворков в той или иной степени поддерживают использование веб-компонентов. В React задокументированы некоторые примеры использования веб-компонентов внутри приложения React и использования React внутри приложения с веб-компонентами (ссылка на документацию). Нечто подобное можно сделать и в Angular с помощью пакета @angular/elements, который позволяет упаковывать компоненты Angular как пользовательские элементы, а Vue предоставляет возможность создавать веб-компоненты с помощью API defineCustomElement.

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

Состояние веб-компонентов

Почти все современные браузеры поддерживают веб-компоненты:

  • API пользовательских элементов  —  96% браузеров по всему миру (18%  —  частичная поддержка);
  • шаблоны HTML  —  примерно 97% браузеров;
  • Shadow DOM API  —  примерно 96% браузеров.

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

Другой интересной статистикой является процент загруженных страниц, на которых зарегистрирован хотя бы один веб-компонент (хотя это число взято только из браузеров Chrome). На приведенном ниже графике видно, что использование этого API удвоилось за последний год:

Крупные технологические компании уже начали развертывать собственные веб-компоненты, и GitHub является отличным примером этому. За последние несколько лет платформа перешла от jQuery к веб-компонентам и создала множество многоразовых элементов, которые публикует для всеобщего использования. Эти компоненты доступны здесь, там же представлен ряд компонентов от других участников сообщества.

Можно выделить две главные причины успешного перехода GitHub на веб-компоненты:

  • определение набора шаблонов и методов разработки компонентов под названием Catalyst (это простая библиотека, предоставляющая функции, которые помогают в разработке за счет сокращения шаблонного кода);
  • создание линтера, который используется наряду с общим линтером Javascript.

Другие крупные технологические компании, в том числе Apple, Amazon, Google, NASA и Salesforce, также начали использовать веб-компоненты (весь список можно посмотреть здесь). Эти компании применяют не только чистые веб-компоненты, но и библиотеки на их основе. Например, Apple использует Stencil.js для разделов Apple Music. Есть множество библиотек и фреймворков на основе веб-компонентов, среди которых одним из самых популярных является Lit от Google. К числу других относятся: FASTElement, snuggsi, X-Tag, Slim.js, Smart, Stencil, hyperHTML-Element, DataFormsJS и Custom-Element-Builder.

Библиотеки компонентов представляют особый интерес для представителей тех областей разработки, где веб-компоненты были бы очень полезны. Дело в том, что они содержат простые компоненты пользовательского интерфейса, которые можно легко реализовать с помощью веб-компонентов. Большим преимуществом является то, что эти библиотеки реализуются только один раз в качестве веб-компонентов и могут использоваться вместе с различными технологиями, такими как React, Vue, Next и другими.

Например, Material UI как система дизайна реализована в нескольких технологиях, чтобы разработчики могли включать их в свои приложения. Так, приложение React обычно включает React-реализацию Material UI для рендеринга этих компонентов. Веб-компоненты могут объединить эти виды библиотек в одну, что значительно облегчит их поддержку и решение проблем, а также предоставит новые возможности.

Заключение

Несмотря на то что веб-компоненты демонстрируют большой потенциал для будущего, они все еще находятся на ранних стадиях развития. И хотя некоторые реализации пока легче выполнить, используя другие технологии (например, React и Vue), особенность веб-компонентов состоит в том, что они сочетаются практически с любой из существующих библиотек, что значительно упрощает задачи разработчиков.

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

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Bernard Lekaj: Do we need Web Components?

Предыдущая статьяЯзык R: прокачайте свои навыки до следующего уровня
Следующая статьяФреймворк Google Wire: автоматическое внедрение зависимостей в Go