Алексей Назаренко, MinskJS #12, 2025.
Web Components: Just in the Nick of Time
Introduction to Web Components, W3C Working Draft 22 May 2012
Tom Occhino and Jordan Walke: JS Apps at Facebook
JavaScript | 2024 | The Web Almanac by HTTP Archive
Chrome Platform Status: CustomElementRegistryDefine
Выпуск 443 — Веб-стандарты
Веб-компоненты — набор API веб-платформы, с помощью которых можно создавать собственные повторно используемые DOM-элементы с изолированной разметкой, стилями и поведением.
Веб-компоненты — набор API веб-платформы, с помощью которых можно создавать собственные повторно используемые DOM-элементы с изолированной разметкой, стилями и поведением.
class QuantityInput extends HTMLElement {
	constructor() {} // Элемент создан
	connectedCallback() {} // Элемент добавлен в DOM
	disconnectedCallback() {} // Элемент удалён из DOM
	adoptedCallback() {} // Элемент перенесён из другого документа
	static observedAttributes = [] // Наблюдаемые атрибуты
	attributeChangedCallback(name, oldVal, newVal) {} // Изменился наблюдаемый атрибут
}
customElements.define('quantity-input', QuantityInput); // регистрация
class QuantityInput extends HTMLElement {
	static formAssociated = true; // Элемент должен ассоциироваться с формой
	connectedCallback() {
		// Возможность работы с формами, валидацией, пользовательскими состояниями и ARIA
		this._internals = this.attachInternals();
	}
	formAssociatedCallback(form) {} // Элемент ассоциирован с формой
	formDisabledCallback(disabled) {} // Форма отключена
	formResetCallback() {} // Форма сброшена
	formStateRestoreCallback(state, mode) // Форма восстановлена или сработал autocomplete
}
customElements.define('quantity-input', QuantityInput); // регистрация
<template id="quantity-input">
	<div class="base">
		<label for="input"></label>
		<button type="button" id="inc">
			+
		</button>
		<button type="button" id="dec">
			-
		</button>
		<input type="number" id="input">
	</div>
</template>
const t = document.createElement('template');
t.innerHTML = `
	<div class="base">
		<label for="input"></label>
		<button type="button" id="inc">
			+
		</button>
		<button type="button" id="dec">
			-
		</button>
		<input type="number" id="input">
	</div>
`;
class QuantityInput extends HTMLElement {
	connectedCallback() {
		// Находим template
		const t = document.querySelector('#quantity-input');
		// Клонируем шаблон
		this.innerHTML = t.content.cloneNode('true');
	}
}
customElements.define('quantity-input', QuantityInput);
class QuantityInput extends HTMLElement {
	connectedCallback() {
		// Находим template
		const t = document.querySelector('#quantity-input');
		// Добавляем Shadow DOM
		this.attachShadow({ mode: 'open' });
		// Клонируем шаблон
		this.shadowRoot.innerHTML = t.content.cloneNode('true');
	}
}
customElements.define('quantity-input', QuantityInput);
Веб-компоненты — набор API веб-платформы, с помощью которых можно создавать собственные повторно используемые DOM-элементы с изолированной разметкой, стилями и поведением.
class QuantityInput extends HTMLElement { /* ... */ }
customElements.define('quantity-input', QuantityInput);
<head>
	<!-- ... -->
	<script src="quantity-input.js"></script>
</head>
<body>
	<quantity-input label="Количество" value="1"></quantity-input>
	<!-- ... -->
	<quantity-input label="Количество" value="5"></quantity-input>
</body>
<quantity-input value="1"></quantity-input>
<calendar-date>
	<calendar-month></calendar-month>
</calendar-date>
<sl-select label="Тема">
	<sl-option value="dark">Тёмная</sl-option>
	<sl-option value="light">Светлая</sl-option>
	<sl-option value="system">Системная</sl-option>
</sl-select>
Веб-компоненты — набор API веб-платформы, с помощью которых можно создавать собственные повторно используемые DOM-элементы с изолированной разметкой, стилями и поведением.
<quantity-input label="Количество" value="1"></quantity-input>
<quantity-input label="Количество" value="1">
	#shadow-root (open)
		<div class="base">
			<label for="input">Количество</label>
			<button type="button" id="inc">+</button>
			<button type="button" id="dec">-</button>
			<input type="number" id="input" value="1">
		</div>
</quantity-input>
What We Do In The Shadow (DOM)
An Attempted Taxonomy of Web Components
Я *должен* добавить, что это замечательно, что люди дали имена полезным подкомбинациям этих примитивов. Мы не стали их называть, но предвидели, что некоторые пользователи будут игнорировать, например, <template> или CE [Custom Elements] или SD [Shadow DOM] и просто будут использовать другие части. С моей точки зрения, это прекрасно и работает как задумано.
Веб-компоненты
👇
Набор API: Custom Elements,
Templates, Shadow DOM, ...
Веб-компоненты
👇
<app-button></app-button>
<app-input></app-input>
<app-drawer></app-drawer>
| Веб-компоненты | Фреймворки |
|---|---|
| Низкоуровневые примитивы | Высокоуровневые абстракции |
| Встроены в браузеры | Сторонняя зависимость |
| Работают как есть | Требуется сборка |
| Базовые возможности | Продвинутые возможности |
| Плохой DX | Хороший DX |
Отказаться от зависимостей и не умереть
Using Web Components to Scale Your UX
Приложение создается несколькими командами с использованием различных инструментов и подходов, но его разрозненные части должны быть объединены в единое целое с сохранением высокой производительности.
Чтобы решить эту задачу, Adobe обратилась к веб-компонентам и библиотеке Lit. Элементы интерфейса Photoshop созданы на основе библиотеки Spectrum Web Components [...], которая работает с любым фреймворком или без него.
Innovations with FAST Web Components
В Edge 122 интерфейс Browser Essentials стал более отзывчивым. Интерфейс стал на 42% быстрее для пользователей Edge и на 76% быстрее для тех, кто работает на устройствах без SSD или у кого менее 8 ГБ ОЗУ!
Избранное — еще одна функция, которая получила улучшения отзывчивости интерфейса в Edge 124. Независимо от того, развернуто или свернуто избранное, работа с ним должна стать на 40% быстрее.
import { LitElement, html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
@customElement('app-counter')
class Counter extends LitElement {
	@property() count = 0;
	increment() {
		this.count += 1;
	}
	render() {
		return html`
			<button @click="${this.increment}">
				${this.count}
			</button>
		`;
	}
}
import React, { useState } from 'react';
function Counter() {
	const [count, setCount] = useState(0);
	const increment = () => {
		setCount(count + 1);
	};
	return (
		<button onClick={increment}>
			{count}
		</button>
	);
}
Слайды: wtf-web-components-minskjs.netlify.app
Канал: <divelopers>
Telegram: @alexnozer
Алексей Назаренко