Я уверен, что среди уважаемой аудитории найдутся те, кто меня поймет. Дело в том, что во всем изобилии популярных библиотек и фреймворков для веб-фронтэнда, лично мне, не нравятся, практически, все альтернативы. В каждом из вариантов я нахожу для себя существенные минусы, которые не дают мне спокойно ими пользоваться. Начинается всегда все радужно: интересный концепт, здравые, на первый взгляд, рассуждения в пунктах «за»… Но затем, все, снова и снова, начинает упираться в избыточные зависимости, лишние сущности и попытки решить проблемы, которые разработчики сами же и создали. Нам предлагают выучить новый синтаксис, принять новые идеи, узнать кучу умных слов, установить множество «необходимых» пакетов. Ок, я люблю все новое, и люблю умные слова. Но меня сильно обескураживает, когда то, что можно сделать просто, люди начинают фрактально усложнять. Вы наверняка уже читали исповеди тех, кто также как и я, отчаялся искать во всем этом здравый смысл и решил уйти в другую крайность — все писать «на ваниле». Со мной это случилось, когда я разочаровался в проекте Polymer, создаваемом при активном участии разработчиков из Google. Сначала мне все очень нравилось, девизом этого движения была фраза «Use the platform!», что для меня означало: «не стоит делать в коде того, что браузер сам сделает эффективнее». Команда Polymer сделала очень многое для внедрения новых стандартов и возможностей на уровне платформы, и за это им — огромное спасибо. Но когда эти цели были достигнуты, они сами принялись нарушать свои-же принципы. И вот их новая библиотека (LitElement) уже отказывается работать напрямую в браузере без установки специального окружения, потому как ребята не следуют стандартам… Я продолжаю наблюдать за развитием LitElement, и даже вижу явные признаки возвращения этих заблудших на путь истинный, но сам я этим уже пользоваться не буду, потому, что теперь у меня есть кое-что получше.
Что?
Holiday.js — библиотека для тех, кому нужно все необходимое и не нужно ничего лишнего. Holiday — потому, что работа с ней для меня — это праздник. А также потому, что писал я ее, преимущественно, по праздникам или будучи в отпуске. Тем не менее, это вполне серьезный проект, с помощью которого разрабатывается целый ряд сложных приложений для того самого «кровавого энтерпрайза», которым любят пугать новичков велосипедостроения. Все свои персональные проекты я также пишу на Holiday.js.
Основано все на группе стандартов Custom Elements и Shadow DOM, как вы уже могли догадаться, и если вы уже знакомы с ними, вам будет очень легко во всем разобраться.
В чем смысл?
Если бы я сам читал подобную статью, чтобы не потерять дальнейший интерес, я захотел бы увидеть пример кода компонента. Вот он:
import {HdElement} from '../../holiday/core/hd-element.js';
class MyComponent extends HdElement {
constructor() {
super();
this.state = {
imageURL: 'images/photo.jpg',
firstName: 'John',
secondName: 'Snow',
};
}
}
MyComponent.template = /*html*/ `
<style>
:host {
display: block;
padding: 10px;
background - color: #fff;
color: #000;
}
</style>
<img bind="src: imageURL" />
<div bind="textContent: firstName"></div>
<div bind="textContent: secondName"></div>
`;
MyComponent.is = 'my-component';
Здесь мы видим, что у компонента есть некое состояние и шаблон со странными атрибутами элементов "bind". Вот эти атрибуты — и есть основная магия: они позволяют распарсить шаблон и сохранить копию результата в памяти для дальнейшего использования (клонирования) БЕЗ дополнительной обработки строки шаблона в JS рантайме, что дает высокую производительность при первичной отрисовке, когда создаются экземпляры каждого компонента. В отличие от многих современных библиотек, в которых участок DOM, за который отвечает компонент, определяется функцией от состояния, в Holiday.js шаблон — это полуфабрикат, который определяет начальную структуру и дает возможность привязать данные уже позже, с помощью обычного DOM API, и это работает очень быстро. К вопросу скорости мы еще вернемся.
Перечислю и другие преимущества:
- Максимальная приближенность к нативным API, минимум дополнительных абстракций
- Нативный синтаксис шаблонов: вам не нужно учить новые HTML и CSS
- Для начала работы, вам не нужно ничего дополнительно устанавливать, в вашем проекте вообще может не быть папки node_modules
- Легкость: вы не будете заставлять пользователей долго ждать загрузки
- В комплект входит глобальный стейт-менеджмент, роутинг, UI-библиотека: вы не почувствуете себя в пустыне
Основная документация проекта: https://holiday-js.web.app/
Жизненный цикл
Давайте рассмотрим этапы жизненного цикла компонента:
import {HdElement} from '../../holiday/core/hd-element.js';
class LifecycleExample extends HdElement {
constructor() {
super();
// Определение начального состояния:
this.state = {
name: '',
bigName: '',
};
/*
В некоторых случаях, компонент может быт добавлен в DOM как "неизвестный элемент по умолчанию", и конструктор компонента может быть вызван позже. Чтобы не потерять данные, сеттеры лучше определять через метод "defineAccessor":
*/
this.defineAccessor('name', (name) => {
// Some accessor code:
this.setStateProperty('name', name);
});
/*
На этапе работы конструктора, компонент еще не добавлен в DOM и вы можете модифицировать его содержимое без опасности замедлить работу за счет перерисовок в браузере.
*/
}
connectedCallback() {
super.connectedCallback();
/*
Нативный коллбэк.
Элемент добавлен в DOM.
На этом этапе вы можете получить доступ к потомкам элемента или к значению его атрибутов.
*/
}
stateUpdated(path) {
/*
Данный метод может быть использован для вычисляемых свойств:
*/
if (path === 'name') {
this.setStateProperty('bigName', this.state.name.toUpperCase());
}
}
attributeChangedCallback(attributeName, oldValue, newValue) {
/*
Нативный коллбэк.
Вызов происходит при изменении значений атрибутов.
*/
super.attributeChangedCallback(attributeName, oldValue, newValue);
}
disconnectedCallback() {
/*
Нативный коллбэк.
Элемент удален из DOM. На данном этапе можно удалить подписки на события или данные для очистки памяти
*/
}
}
LifecycleExample.template = /*html*/ `
<div bind="textContent: name"></div>
<div bind="textContent: bigName"></div>
`;
/*
Список HTML-атрибутов, на которые компонент будет реагировать:
*/
LifecycleExample.logicAttributes = [
// Значения этих атрибутов, будут автоматически присвоены свойствам компонента с аналогичными именами
'name',
];
/*
Регистрация компонента в реестре CustomElements:
*/
LifecycleExample.is = 'lifecycle-example';
Шаблоны
Поскольку шаблоны в Holiday.js — это просто стандартные шаблонные литералы, вы имеете полную свободу в том, как размещать их в проекте и как формировать их содержимое. Можно их выносить в отдельные модули, можно определять вместе с кодом компонента, а можно генерировать налету из данных. В шаблонах можно использовать выражения и результаты выполнения функций. Все это дает большие возможности и свободу самовыражения. Например, вы можете импортировать в шаблон общие стили, использовать переменные и вычисление значений в CSS без использования препроцессоров и с большим удобством, собирать шаблоны из готовых кусков.
Важной частью шаблонов веб-компонентов являются "слоты". Слоты позволяют обозначить специальные места в «теневом» DOM вашего компонента, в которые будут выводится его прямые потомки в общем DOM-дереве. Пример:
<header>
<slot name="header"></slot>
</header>
<div class="toolbar">
<slot name="toolbar"></slot>
</div>
<div class="content">
<slot></slot>
</div>
<footer>
<slot name="footer"></slot>
</footer>
Размещение в слотах:
<my-component>
<div slot="header">Header content</div>
<toolbar-component slot="toolbar"></toolbar-component>
<div>Content of the default slot...</div>
<div slot="footer">Footer content...</div>
</my-component>
Как вы уже догадались, это позволяет создавать компоненты-лейауты, где нужные модули можно легко разложить по ячейкам.
Как уже упоминалось выше, для динамической привязки данных и обработчиков, используется атрибут «bind»:
<div bind="textContent: content; onclick: on.clicked"></div>
Для привязки значений атрибутов используется символ "@" в начале наименования свойства:
<div bind="@style: style">Content...</div>
Больше подробностей вы сможете найти в соответствующем разделе документации: https://holiday-js.web.app/?templates
Управление состоянием
Внутри компонента:
class MyComponent extends HdElement {
constructor() {
super();
// Определение структуры и начальных значений для объекта состояния:
this.state = {
content: 'Initial Content',
style: 'color: #f00',
on: {
clicked: () => {
console.log('Clicked!');
},
},
};
// Обновление данных происходит с помощью метода "setStateProperty":
window.setTimeout(() => {
// Обновление одного свойства:
this.setStateProperty('content', 'Updated Content');
// Обновление нескольких свойств:
this.setStateProperty({
'content': 'Updated Content',
'style': 'color: #00f',
});
}, 1000);
}
}
Для управления состоянием на уровне всего приложения, используется модуль HdState.
Пример использования:
import {HdState} from '../../holiday/core/hd-state.js';
// Подписка на значение:
HdState.subscribe('ui.loading', (val) => {
console.log(val);
});
// Публикация нового значения:
HdState.publish('ui.loading', true);
// Либо:
HdState.publish({
'ui.loading': true,
'ui.sidePanelActive': true,
});
Для работы с глобальным состоянием, сперва необходимо определить его общую схему:
HdState.applyScheme({
ui: {
loading: {
/*
При записи значений осуществляется контроль типов, поэтому для каждого поля необходимо указать тип:
*/
type: Boolean,
// И значение по умолчанию:
value: false,
},
sidePanelActive: {
type: Boolean,
value: false,
},
},
user: {
authorized: {
type: Boolean,
value: false,
},
name: {
type: String,
value: null,
// Значение поля может быть сохранено и восстановлено после перезагрузки страницы:
cache: true,
},
},
});
Схема также может быть удобно разделена на отдельные логические блоки.
Чтобы избежать утечек памяти, от неактуальных подписок следует избавляться:
let subscription = HdState.subscribe('ui.loading', (val) => {
console.log(val);
});
window.setTimeout(() => {
// Unsubscribe:
subscription.remove();
}, 10000);
Документация по работе со стейтом: https://holiday-js.web.app/?state
Инструменты разработки
Самый минимальный набор для начала работы — текстовый редактор и браузер. Да, этого достаточно, чтобы попробовать. Стоит оговориться, что в такой конфигурации, браузером должен быть именно Firefox. Он умеет загружать ES-модули без использования локального сервера.
Для более полноценной работы, вам понадобится git и любой приглянувшийся вам веб-сервер (для работы с ES-модулями в других браузерах).
Для подсветки HTML-синтаксиса и автодополнений в шаблонных литералах, я использую расширение для VS Code, изначально предназначенное для работы с lit-html: https://github.com/pushqrdx/vscode-inline-html
Для сборки кода перед публикацией, можно использовать любой популярный сборщик, такой как webpack или rollup.
Производительность
Бенчмарки для фреймфорков писать непросто. Для репрезентативного сравнения, вы должны убедится, что сравниваете сравнимые вещи и, насколько это возможно, минимизируете влияние сайд-эффектов. А значит, должны иметь четкое представление от том, что происходит «под капотом» в каждом случае. Сперва, нужно определиться с тем, какие именно параметры мы будем измерять. Для нашего случая, нам важны:
- Время до начала исполнения основной программы (загрузка)
- Время первичной отрисовки компонентов
- Время обновления данных компонентов и их перерисовки
К сожалению, у нас нет надежного способа узнать момент, когда браузер выполнил программу и полностью закончил рендер. На уровне браузера существует множество собственных оптимизаций, которые для нас являются чем-то типа «черного ящика» и могут отличаться в разных движках. Что мы можем использовать: Performance API, requestAnimationFrame и нестандартный метод requestIdleCallback, который работает только в Chrome и Firefox. На первое время хватит.
С чем будем сравнивать Holiday.js? Я начал с двух библиотек: React (куда же без него) и LitElement (похожие технологии в основе). Код бенчмарков вы можете найти в этих репозиториях:
https://github.com/foxeyes/holiday-benchmark
https://github.com/foxeyes/lit-benchmark
https://github.com/foxeyes/react-benchmark
К сожалению, с автоматизацией тестов я пока не закончил, а это тоже непросто, потому как автоматизация не должна сама по себе создавать сайд-эффектов. В данный момент я продолжаю работу над этим вопросом и готовлю соответствующий инструмент с использованием Puppeteer (очень полезная штука, рекомендую). А пока, вы можете попробовать тесты в ручном режиме (Chrome/Firefox) и проверить результаты в консоли браузера. Вы увидите, что Holiday.js уверенно лидирует во всех случаях.
Вы можете заметить, что в случае с React, при выводе компонентов создается дополнительный div-контейнер, который служит для того, чтобы уровнять количество создаваемых DOM-элементов. Я решил что, так будет справедливо, потому что веб-компонент — это всегда DOM-элемент и некий контейнер, и соответствие структур DOM-дерева в разных тестах очень важно. К тому-же избавиться от лишней вложенности возможно и в Holiday.js и вывести данные так-же, как это происходит в React. Однако, если данный вопрос кажется вам спорным, обратите внимание, в первую очередь, на скорость обновлений.
Поддержка браузерами
Нет, Internet Explorer не поддерживается. Этого возможно было бы добиться с определенными усилиями (для 11-й версии), но отказ от поддержки — принципиальная позиция. Во первых, я считаю, что IE давно должен умереть, и в наших с вами силах этому поспособствовать. Во вторых, люди, которые выступают за поддержку IE, часто допускают серьезную логическую ошибку: доля IE в общем трафике, по актуальным данным caniuse ~ 1.6% для всех встречающихся версий. А самый популярный браузер, идущий с большим отрывом впереди всех остальных — это мобильный Chrome (~35%). Так вот, поддерживая IE, вы сознательно отказываетесь от использования большого количества технологий, которые просто необходимы для реализации полноценной адаптивности и мобильности в ваших приложениях, то есть, ради незначительного меньшинства, усложняете жизнь значительному большинству своих пользователей. Или существенно удорожаете разработку и поддержку своих продуктов. В третьих, Microsoft, в последнее время, активно принуждает пользователей отказаться от Windows 7, так что доля IE трафика в ближайшее время будет все быстрее снижаться. Признаю, что существуют случаи, когда поддержку IE никак не обойти, но такие случаи гораздо более редки, чем мы привыкли думать.
15 января этого года, Microsoft выпустили релизную версию нового Edge, на движке Chromium. C этого момента можно считать, что Holiday.js нативно поддерживается во всех актуальных версиях популярных браузеров. Для более старых Edge — потребуется полифилл. Возможность работы с полифиллами реализована в Holiday.js, но сам скрипт нужно подключать отдельно.
Минимализм
В основе концепции Holiday.js лежат простота и минимализм. Одним из важных этапов в работе над этим проектом для меня было удаление функционала, который я поторопился реализовать, но впоследствии понял, что без него можно обойтись. Поэтому, если вам покажется, что чего-то очень не хватает, задайте себе вопрос, а так ли это в действительности? Я для себя ответил на него так: если что-то еще сильно понадобится — лучше создать расширенный класс отдельно, с наследованием от основного. Пока этот подход меня не подводил. Holiday.js, в его текущем состоянии, проверен в бою и показал себя очень положительно в работе.
Планы
Если вы дочитали до этого места, значит мои усилия уже потрачены не зря. Даже если вы не захотите использовать Holiday.js, вы можете, как минимум, почерпнуть что-то интересное для себя в работе с веб-компонентами как таковыми. Я буду очень рад, если данный материал положит начало формированию сообщества. Буду очень рад конструктивной дискуссии, критике, багрепортам, пуллреквестам и, конечно, звездочкам на GitHub. Конечно, я не смог затронуть все аспекты, и с удовольствием отвечу на ваши вопросы. Прошу принять во внимание, что до сего момента, я работал над этой библиотекой один, а делать все в одиночку, включая документацию, тесты и сопутствующие инструменты, параллельно с основной работой и пет-проектами — сложно.
Тем не менее, в ближайших планах:
- Больше сравнений с другими популярными библиотеками и фреймворками
- Минималистичный и гибкий инструмент для тестирования «PingPong» на основе Puppeteer
- Генератор базового проекта с примерами (Starter Kit)
- Дальнейшее развитие документации
Скрещиваю пальцы, жму «Опубликовать»…
JustDont
Если вы говорите про быстродействие — то где у вас инструменты, работающие на скорость обновления страницы? Оптимизировать создание 10К веб-компонентов, если уж они начнут тормозить — далеко не так проблемно (например: выкинуть веб-компоненты и переписать всё то же на ванильном html), как организовать хорошие схемы обновления, при котором изменения в развесистом многоуровневом стейте на 10К ключей не приведут к перерисовке всей страницы, если изменится что-то касающееся только корневого элемента. Или парочки элементов где-то посреди длинных и больших ветвей.
Хвалиться тем, что вы быстро создаете веб-компоненты — сомнительное достижение. Тем более, что веб-компоненты пока что всё равно гораздо более медленные, чем обычный DOM, и в критичных случаях оптимизация до упора — неизбежно выкинет и сами компоненты.
Где освещение вопросов компоновки элементов? Практически любые серьезные случаи вёрстки требуют циклов, которые у вас реализованы максимально примитивно (Изменились данные? Зафигачим новый innerHTML!), а ради чего-то непримитивного предлагается пойти почитать кусок исходного кода и поразмышлять о том, как оно работает. А работает оно всё равно очень странным образом, добавление или удаление одного элемента из массива — перерисует вообще всё. Не очень понятно, где там performant.
А еще чуть более серьезные случаи часто требуют выражения через рекурсию (или нагораживание костылей ради того, чтоб от рекурсии избавиться, но у вас-то для людей всё написано, верно?) — про неё вообще примерно никак не упомянуто.
Стилизация: тут нам сходу предлагается принять в объятья shadow DOM, который в настоящий момент убог и не масштабируется. Стилизация компонентов с верхнего уровня через переменные — в реальности работает только тогда, когда эти компоненты никто кроме вас не потребляет, и наперед известно, что вообще вам понадобится перекрашивать, а что — никогда нет.
Ну и в 2020 году это всё написано в JS (с многочисленными // @ts-ignore в коде, что отдельно доставляет).
Нет, мне конечно тоже не нравится, что парни, делающие LitElement — категорически не хотят выкидывать из него полифиллы и прочую рухлядь, но это далеко не так страшно, чтоб ради победы над этим делать очередной очень велосипедистый велосипед.
PS: Всё вышенаписанное — это, к слову, не попытки придраться к чему-нибудь, а те проблемы, которые у меня как у фронтэндера уже больше пяти лет вызывают максимальную боль; и соответственно на эти аспекты инструментов я и смотрю в первую очередь.
i360u Автор
Странно, что вы их не заметили. github.com/foxeyes/holiday-benchmark/blob/master/app/main.js#L22 — тут есть пример обновления, и это обновление, происходит значительно быстрее чем у того-же LitElement.
Отчего же сомнительное? Вы серьезно?
Это с чего вдруг? Из бенчмарков видно, что это не так. Уж точно не так категорично, ибо создание дополнительных узлов со стилями внутри Shadow DOM — конечно несет дополнительные издержки, но простой компонент-контейнер со стилизацией «снаружи» сильно по скорости не отличается от обычного div. Как и компоненты-контейнеры из других фреймворков, с более сложной структурой, не сильно отличаются от аналогичных веб-компонентов, которые быстрее. А вот в механизмах работы с темплейтами и биндингами уже, как раз, большая разница.
Эммм… все там. Возможно, я и упустил что-то, ибо все и сразу не захватишь, это все требует значительного времени… Просто открою вам один секрет, innerHTML — это самый простой способ и, очень часто, самый эффективный. Особенно, когда вы имеете дело с коллекциями, создающимися один раз для текущего вида. Или, когда элементов не много. Как можно работать с большими коллекциями — я показывал в примере выше, вроде ничего сложного. Суть моего подхода, которую вы не можете принять, в том, что для каждого случая с циклами — хорош свой подход, и плох один — универсальный (сильно увеличивает толщину ваших абстракций).
Об этом мы с Вами уже дискутировали. С тех пор мое мнение о том, что вы глубоко заблуждаетесь — не изменилось. Показываю как расшарить стили для Shadow DOM:
Без CSS-переменных, если вы не успели с ними подружиться. Стилизуйте как хотите, с любого уровня. Без протечек и повторения кода. Пожалуйста.
Я использую TS для статического анализа в обязательном порядке и для всех проектов. Но принципиально не пишу на TS при этом, используя аннотации типов в формате JS Doc. Это позволяет мне эффективно работать без пересборок, сорсмапов и прочего, что мне мешает. @ts-ignore — в бенчмарках это, кхм, абсолютно нормально, особенно когда используются экспериментальные API.
Разница в производительности до 2-х раз — для меня достаточная причина. Контроль над DOM — тоже достаточная. Удобство работы с шаблонами — тоже.
JustDont
Я даже целый абзац написал про то, что имеется в виду, но вы каким-то образом этого всего не прочли. Меня стейт-менеджмент в приложении к хоть сколько-нибудь сложной структуре интересует (не надо отвечать, что у вас стейт-менеджмент есть, пожалуйста), а не то, как вообще изменить стейт одного конкретного компонента.
А где у вас бенчмарк на vanilla js?
Ну и насчёт «с чего» — во-первых абстракции небесплатны, а во-вторых — некоторые вещи в вебкомпонентах происходят далеко не самым удобным образом. Например, всё содержимое всех shadow root жадно обрабатывается браузером. Проще говоря, если у вас есть вебкомпонент с shadow root, подключенный в дерево — всё его содержимое будет парситься и обрабатываться браузером, даже если это всё висит в глубоком display: none. Из-за чего одна и та же по логической структуре страница может сильно грузить браузер просто потому, что на ней есть теневой DOM, против той, на которой его нет.
Рассуждать о производительности в терминах «сильно» и «несильно» — лучше не стоит. А то ниже по тексту у вас «до 2х раз» — и это вдруг для вас офигенно, а тут — «сильно не отличается» (потому что вы не измеряли, скорее всего) и неважно.
Открою вам один секрет — этот «самый простой и очень часто эффективный» способ прост и эффективен в конкретных рамках. Как, собственно, практически все простые и эффективные способы.
И чем оно проще и эффективнее в границах применимости, тем сложнее с этим что-либо сделать вне границ применимости. Вы считаете, что ваш дефолтный innerHTML в основном достаточен? Ну считайте — а мой опыт говорит мне о том, что самые жесткие затыки производительности случаются как раз тогда, когда кто-нибудь «простой и эффективный» innerHTML через компонентность размажет по куче циклов и уровней DOM. И внезапно окажется, что сто раз по сто раз сделать innerHTML не так уж и просто и эффективно, особенно, когда в реальности надо всего лишь сделать 100 изменений, а не 100 по 100.
Я вам об этом и пишу — где ваше многообразие подходов-то, в самой библиотеке? Благо их там буквально два-три можно придумать, с одного полюса «все выкинули и нарисовали снова», с другого — обнаружение изменений стейта и минимум изменений в самом DOM, ну и, возможно, какой-то компромиссный вариант. Это как раз то, чему следует быть в крутом фреймворке для людей.
У вас же пока что два варианта — дефолтный на первом полюсе, второй — на втором полюсе строго для неизменного массива, и на первом для всего остального.
Отлично, выше по тексту мы наоптимизировали на скорости, а теперь давайте бухнем инлайновую подстановку по одной на каждый вид компонента, чтоб юзеры качали это всё себе. Как раз всё обратно и замедлим для баланса.
Это я даже не говорю про то, что у нормальных людей стили лежат в css. А вы такие приходите с вашими компонентами и говорите человечьим голосом: «а вы, добрые люди, вытащите ваши темы из css, распихайте их по строкам, и подоткните в мою либу компонентов».
Как там у вас в заглавии-то отличнейше написано?
Всё, что я от вас слышу про shadow DOM — это мужественное решение проблем, созданных самой shadow DOM.
Вы не показали никаких преимуществ в контроле на DOM и в работе с шаблонами относительно других шаблонизаторов.
i360u Автор
Что, простите, качали? Зачем на каждый вид? Что за бред?
Простите, я сейчас на личности перейду: я часто встречаю ваши комментарии на Хабре, и часто их плюсую. Тот ваш пост про компоненты я тоже плюсанул, хоть и не согласен сильно с частью про Shadow DOM. Но каждый раз, когда нам приходится общаться друг с другом непосредственно — начинается срач с длинными простынями, в котором я не вижу ничего конструктивного. Я предлагаю закончить с этим. Можно просто принять тот факт, что есть люди с разным опытом и разной практикой, идущие разными путями.
На конкретные вопросы по библиотеке, о том, как сделать что-то конкретное, я отвечу.
JustDont
Эм, стили? Те самые, которые вы подтыкаете? Я не понимаю, что вы тут не понимаете.
Предположим, у юзеров вашей библиотеки есть некий theme.css, который раскрашивает ваши компоненты так, как им надо. Дальше есть два варианта: вы или подтыкаете этот theme.css полностью в стиль каждого компонента через импорт, или вы предлагаете юзерам вашей библиотеки разбить его на кусочки — для каждого компонента своя часть — и подтыкаете все эти части к соответствующим компонентам примерно так, как в вашем предыдущем комментарии.
В обоих случаях вы всё так же загрузите весь объем theme.css (повторы браузер, разумеется, заново грузить не будет), в обоих случаях вы лишитесь преимуществ ранней загрузки css, потому что оно будет у вас не в <head>, а ниже; в обоих случаях вы лишитесь оптимизаций CSSOM, потому что для каждого shadow root итоговые стили будут посчитаны отдельно. Единственный плюс теневого DOM — изоляцию стилей — вы фактически выкидываете вон, потому что повальное подтыкание какого-то общего css с верхних уровней приложения — это то же самое, как если бы вы просто обычным образом подключили css на страницу. Только с кучей добавочных минусов, вызванных использованием теневого DOM.
i360u Автор
Скажите, это полностью или порезано на кусочки? И в какой момент тут нарушается изоляция?
JustDont
Это — вообще никак не отличается от css variables и имеет всё те же проблемы. Завтра мне нужен будет margin, послезавтра outline, послепослезавтра еще что-нибудь — и что мне делать, писать в спортлото или пачтить чужую библиотеку?
i360u Автор
Ок, а когда вам нужен margin или outline с обычным CSS, куда вы обычно пишите?
JustDont
В css пишу, сразу после селектора, выбирающего мне нужный компонент.
i360u Автор
Приведите пример.
JustDont
Серьезно?
i360u Автор
JustDont
См. https://habr.com/ru/post/485688/#comment_21201010
Мужественная борьба с shadow DOM, в результате которой немногочисленные плюсы пропадают, а многочисленные минусы — все остаются. В этом конкретном случае (в каждый компонент его собственный кусочек) — изоляция остаётся, но по производительности это самый удручающий вариант: множество кусков css без особо ранней загрузки, множество shadow root, каждый из которых обсчитывается отдельно; а юзерам вашей библиотеки еще дополнительно надо сидеть и переносить стили из css в POJO по категориям.
i360u Автор
Это какие плюсы у меня пропали? Инкапсуляция? Возможность управлять темами? Вычислять свойства? Может быть стили у меня разбросаны по разным местам? Или что-то повторяется? Или я изобретаю франкенштейнов, типа БЭМ, чтобы мой проект случайно не развалился, когда кто-то неосторожно написал в «спортлото»?
JustDont
Окей, хорошо, на безрыбье инкапсуляции css мне и вправду нечего возразить — если инкапсуляция реально критична, то придётся жить с shadow DOM, потому что все остальные решения в любом случае еще хуже (а shadow DOM может таки со временем доработают).
Другое дело, что критична она тогда, когда проект становится весьма большим, а тогда же критичным становится и быстродействие, с которым у shadow DOM всё не суперски (впрочем, опять же, альтернатива — только обычный css, потому что все другие решения еще более медленные).
i360u Автор
Как по вашему, насколько компонент с Shadow DOM медленнее рендерится, чем просто DOM элемент с такими-же стилями? Если вы пишите «всё не суперски», насколько действительно велика проблема?
i360u Автор
Поскольку вы молчите, отвечу сам. Я набросал по быстрому такие тесты:
github.com/foxeyes/shadow-benchmark
На моей машине чистые издержки на ShadowRoot — примерно 10% скорости.
На внутренние стили с отдельным узлом падение скорости уже значительные, но затраты примерно соответствуют затратам на дополнительный узел DOM.
В итоге, конечно, веб-компоненты добавляют накладные расходы, но ничего критичного я не наблюдаю. Тем более, что обычные ноды никто не отменял, ShadowRoot у вас в проекте будет не везде, а значит затраты возрастают не линейно и, тем более, не экспоненциально. В итоге, мы имеем обычный инженерный компромисс: на одной чаше весов удобство и инкапсуляция и небольшая просадка по скорости (относительно оптимизированного ванильного html, без библиотек), на другой — чуть более высокая скорость + препроцессоры со своим синтаксисом, монструозный БЭМ и длинные простыни CSS в далеке от места применения.
i360u Автор
Немного глубже раскрою тему с innerHTML:
У веб-компонентов, есть одна очень важная особенность: они сами могут быть добавлены в DOM через innerHTML. Со всей своей логикой и возможностью самостоятельно подписаться на нужные данные, и отвечать за их обновление. Также, они могут отвечать за действия, которые необходимо выполнить при их удалении, например за очистку памяти. И они могут самоуничтожаться при необходимости. Это может кардинально изменить то, как вы организуете то, что вы называете «компоновкой».
JustDont
А что, это могло быть кому-то не очевидно?
i360u Автор
Видимо тем, кто боится что придется перерендерить innerHTML целиком при изменении одного из элементов массива с данными…
JustDont
Степень страха в этом случае у нормального разработчика всецело определяется тем, сколько DOM тянет с собой этот самый «один из элементов массива с данными». Я еще могу позвать сюда товарища khim, который вам быстро в популярных выражениях объяснит, что невозможность пролистать бесконечную ленту на patreon.com дальше нескольких страниц назад — это в конечном счете история того, как кто-то очень храбрый не побоялся перерендерить кое-что целиком. Впрочем, там это всё еще усугублено реактом — на каком-нибудь «сверхбыстром» фреймворке такой тупой прием продержался бы не 5-8 страниц, а 20-30, но итог будет точно такой же.
i360u Автор
Вы видимо не поняли. Рендер изменений будет только там, где они, непосредственно, произошли. Именно это кому-то не очевидно.
JustDont
Это вот здесь-то?
Откуда тут «рендер изменений только там <...>», когда тут полностью шаблон массива меняется?
И в вашем «For other performance sensitive cases you can use this component.» всё то же самое, если вы будете мутировать сам массив — нет переделки там только тогда, когда массив у вас иммутабелен, а меняется только его содержимое.
Я даже не буду говорить, что иммутабельные массивы удобны далеко не всегда.
i360u Автор
Хм, конечно не здесь. Почему вообще вы выбрали именно этот кусок кода, если сами нашли правильный? Тем не менее, это далеко не все способы, для сложных случаев (графы данных) я предпочитаю использовать хранилища с подпиской по уникальным ключам.
JustDont
Во-первых, потому что он не правильный — если уж оптимизировать рендер, то делать это вокруг иммутабельности массива (а не количества изменений в DOM) — это какой-то местячковый случай.
Во-вторых, еще раз обращаю ваше внимание на то, что этим всем подходам, благо их немного, следует быть реализованными в библиотеке, а не «ну это вы сами напишите, если надо».
i360u Автор
Вы можете написать свою библиотеку, в которой можете реализовать все, что, по вашему, следует. Попытки охватить все частные случаи приводят к тому, с чего я начал статью. Особенно если эти случаи исключительно умозрительны. Эффективность моего решения подтверждена моей-же практикой.