Как стать автором
Обновить

Комментарии 34

Если вы говорите про быстродействие — то где у вас инструменты, работающие на скорость обновления страницы? Оптимизировать создание 10К веб-компонентов, если уж они начнут тормозить — далеко не так проблемно (например: выкинуть веб-компоненты и переписать всё то же на ванильном html), как организовать хорошие схемы обновления, при котором изменения в развесистом многоуровневом стейте на 10К ключей не приведут к перерисовке всей страницы, если изменится что-то касающееся только корневого элемента. Или парочки элементов где-то посреди длинных и больших ветвей.

Хвалиться тем, что вы быстро создаете веб-компоненты — сомнительное достижение. Тем более, что веб-компоненты пока что всё равно гораздо более медленные, чем обычный DOM, и в критичных случаях оптимизация до упора — неизбежно выкинет и сами компоненты.

Где освещение вопросов компоновки элементов? Практически любые серьезные случаи вёрстки требуют циклов, которые у вас реализованы максимально примитивно (Изменились данные? Зафигачим новый innerHTML!), а ради чего-то непримитивного предлагается пойти почитать кусок исходного кода и поразмышлять о том, как оно работает. А работает оно всё равно очень странным образом, добавление или удаление одного элемента из массива — перерисует вообще всё. Не очень понятно, где там performant.
А еще чуть более серьезные случаи часто требуют выражения через рекурсию (или нагораживание костылей ради того, чтоб от рекурсии избавиться, но у вас-то для людей всё написано, верно?) — про неё вообще примерно никак не упомянуто.

Стилизация: тут нам сходу предлагается принять в объятья shadow DOM, который в настоящий момент убог и не масштабируется. Стилизация компонентов с верхнего уровня через переменные — в реальности работает только тогда, когда эти компоненты никто кроме вас не потребляет, и наперед известно, что вообще вам понадобится перекрашивать, а что — никогда нет.

Ну и в 2020 году это всё написано в JS (с многочисленными // @ts-ignore в коде, что отдельно доставляет).

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

PS: Всё вышенаписанное — это, к слову, не попытки придраться к чему-нибудь, а те проблемы, которые у меня как у фронтэндера уже больше пяти лет вызывают максимальную боль; и соответственно на эти аспекты инструментов я и смотрю в первую очередь.
где у вас инструменты, работающие на скорость обновления страницы?

Странно, что вы их не заметили. github.com/foxeyes/holiday-benchmark/blob/master/app/main.js#L22 — тут есть пример обновления, и это обновление, происходит значительно быстрее чем у того-же LitElement.

Хвалиться тем, что вы быстро создаете веб-компоненты — сомнительное достижение

Отчего же сомнительное? Вы серьезно?

Тем более, что веб-компоненты пока что всё равно гораздо более медленные, чем обычный DOM

Это с чего вдруг? Из бенчмарков видно, что это не так. Уж точно не так категорично, ибо создание дополнительных узлов со стилями внутри Shadow DOM — конечно несет дополнительные издержки, но простой компонент-контейнер со стилизацией «снаружи» сильно по скорости не отличается от обычного div. Как и компоненты-контейнеры из других фреймворков, с более сложной структурой, не сильно отличаются от аналогичных веб-компонентов, которые быстрее. А вот в механизмах работы с темплейтами и биндингами уже, как раз, большая разница.

Где освещение вопросов компоновки элементов?

Эммм… все там. Возможно, я и упустил что-то, ибо все и сразу не захватишь, это все требует значительного времени… Просто открою вам один секрет, innerHTML — это самый простой способ и, очень часто, самый эффективный. Особенно, когда вы имеете дело с коллекциями, создающимися один раз для текущего вида. Или, когда элементов не много. Как можно работать с большими коллекциями — я показывал в примере выше, вроде ничего сложного. Суть моего подхода, которую вы не можете принять, в том, что для каждого случая с циклами — хорош свой подход, и плох один — универсальный (сильно увеличивает толщину ваших абстракций).

Стилизация: тут нам сходу предлагается принять в объятья shadow DOM, который в настоящий момент убог и не масштабируется.

Об этом мы с Вами уже дискутировали. С тех пор мое мнение о том, что вы глубоко заблуждаетесь — не изменилось. Показываю как расшарить стили для Shadow DOM:
const COMMON_CSS = /*css*/ `
  color: black;
  padding: 10px;
`;

const TEMPLATE = /*html*/ `
<style>
  :host {
    ${COMMON_CSS}
  }
</style>
<div>Бла бла бла</div>
`;

Без CSS-переменных, если вы не успели с ними подружиться. Стилизуйте как хотите, с любого уровня. Без протечек и повторения кода. Пожалуйста.

Ну и в 2020 году это всё написано в JS (с многочисленными // @ts-ignore в коде, что отдельно доставляет).

Я использую TS для статического анализа в обязательном порядке и для всех проектов. Но принципиально не пишу на TS при этом, используя аннотации типов в формате JS Doc. Это позволяет мне эффективно работать без пересборок, сорсмапов и прочего, что мне мешает. @ts-ignore — в бенчмарках это, кхм, абсолютно нормально, особенно когда используются экспериментальные API.

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

Разница в производительности до 2-х раз — для меня достаточная причина. Контроль над DOM — тоже достаточная. Удобство работы с шаблонами — тоже.
тут есть пример обновления, и это обновление, происходит значительно быстрее чем у того-же LitElement

Я даже целый абзац написал про то, что имеется в виду, но вы каким-то образом этого всего не прочли. Меня стейт-менеджмент в приложении к хоть сколько-нибудь сложной структуре интересует (не надо отвечать, что у вас стейт-менеджмент есть, пожалуйста), а не то, как вообще изменить стейт одного конкретного компонента.

Это с чего вдруг? Из бенчмарков видно, что это не так.

А где у вас бенчмарк на vanilla js?
Ну и насчёт «с чего» — во-первых абстракции небесплатны, а во-вторых — некоторые вещи в вебкомпонентах происходят далеко не самым удобным образом. Например, всё содержимое всех shadow root жадно обрабатывается браузером. Проще говоря, если у вас есть вебкомпонент с shadow root, подключенный в дерево — всё его содержимое будет парситься и обрабатываться браузером, даже если это всё висит в глубоком display: none. Из-за чего одна и та же по логической структуре страница может сильно грузить браузер просто потому, что на ней есть теневой DOM, против той, на которой его нет.

простой компонент-контейнер со стилизацией «снаружи» сильно по скорости не отличается от обычного div

Рассуждать о производительности в терминах «сильно» и «несильно» — лучше не стоит. А то ниже по тексту у вас «до 2х раз» — и это вдруг для вас офигенно, а тут — «сильно не отличается» (потому что вы не измеряли, скорее всего) и неважно.

Просто открою вам один секрет, innerHTML — это самый простой способ и, очень часто, самый эффективный.

Открою вам один секрет — этот «самый простой и очень часто эффективный» способ прост и эффективен в конкретных рамках. Как, собственно, практически все простые и эффективные способы.
И чем оно проще и эффективнее в границах применимости, тем сложнее с этим что-либо сделать вне границ применимости. Вы считаете, что ваш дефолтный innerHTML в основном достаточен? Ну считайте — а мой опыт говорит мне о том, что самые жесткие затыки производительности случаются как раз тогда, когда кто-нибудь «простой и эффективный» innerHTML через компонентность размажет по куче циклов и уровней DOM. И внезапно окажется, что сто раз по сто раз сделать innerHTML не так уж и просто и эффективно, особенно, когда в реальности надо всего лишь сделать 100 изменений, а не 100 по 100.

Суть моего подхода, которую вы не можете принять, в том, что для каждого случая с циклами — хорош свой подход, и плох один — универсальный (сильно увеличивает толщину ваших абстракций).

Я вам об этом и пишу — где ваше многообразие подходов-то, в самой библиотеке? Благо их там буквально два-три можно придумать, с одного полюса «все выкинули и нарисовали снова», с другого — обнаружение изменений стейта и минимум изменений в самом DOM, ну и, возможно, какой-то компромиссный вариант. Это как раз то, чему следует быть в крутом фреймворке для людей.

У вас же пока что два варианта — дефолтный на первом полюсе, второй — на втором полюсе строго для неизменного массива, и на первом для всего остального.

Показываю как расшарить стили для Shadow DOM

Отлично, выше по тексту мы наоптимизировали на скорости, а теперь давайте бухнем инлайновую подстановку по одной на каждый вид компонента, чтоб юзеры качали это всё себе. Как раз всё обратно и замедлим для баланса.

Это я даже не говорю про то, что у нормальных людей стили лежат в css. А вы такие приходите с вашими компонентами и говорите человечьим голосом: «а вы, добрые люди, вытащите ваши темы из css, распихайте их по строкам, и подоткните в мою либу компонентов».

Как там у вас в заглавии-то отличнейше написано?
Но затем, все, снова и снова, начинает упираться в избыточные зависимости, лишние сущности и попытки решить проблемы, которые разработчики сами же и создали.

Всё, что я от вас слышу про shadow DOM — это мужественное решение проблем, созданных самой shadow DOM.

Контроль над DOM — тоже достаточная. Удобство работы с шаблонами — тоже.

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

Что, простите, качали? Зачем на каждый вид? Что за бред?

Простите, я сейчас на личности перейду: я часто встречаю ваши комментарии на Хабре, и часто их плюсую. Тот ваш пост про компоненты я тоже плюсанул, хоть и не согласен сильно с частью про Shadow DOM. Но каждый раз, когда нам приходится общаться друг с другом непосредственно — начинается срач с длинными простынями, в котором я не вижу ничего конструктивного. Я предлагаю закончить с этим. Можно просто принять тот факт, что есть люди с разным опытом и разной практикой, идущие разными путями.

На конкретные вопросы по библиотеке, о том, как сделать что-то конкретное, я отвечу.

Что, простите, качали? Зачем на каждый вид? Что за бред?

Эм, стили? Те самые, которые вы подтыкаете? Я не понимаю, что вы тут не понимаете.

Предположим, у юзеров вашей библиотеки есть некий theme.css, который раскрашивает ваши компоненты так, как им надо. Дальше есть два варианта: вы или подтыкаете этот theme.css полностью в стиль каждого компонента через импорт, или вы предлагаете юзерам вашей библиотеки разбить его на кусочки — для каждого компонента своя часть — и подтыкаете все эти части к соответствующим компонентам примерно так, как в вашем предыдущем комментарии.
В обоих случаях вы всё так же загрузите весь объем theme.css (повторы браузер, разумеется, заново грузить не будет), в обоих случаях вы лишитесь преимуществ ранней загрузки css, потому что оно будет у вас не в <head>, а ниже; в обоих случаях вы лишитесь оптимизаций CSSOM, потому что для каждого shadow root итоговые стили будут посчитаны отдельно. Единственный плюс теневого DOM — изоляцию стилей — вы фактически выкидываете вон, потому что повальное подтыкание какого-то общего css с верхних уровней приложения — это то же самое, как если бы вы просто обычным образом подключили css на страницу. Только с кучей добавочных минусов, вызванных использованием теневого DOM.
import {COMMON_CSS} from '../../css/common-css.js';

const TEMPLATE = /*html*/ `
<style>
  :host {
    color: ${COMMON_CSS[currentTheme].textColor};
    padding: ${COMMON_CSS[currentTheme].paddng};
  }
</style>
<child-component></child-component>
`;

Скажите, это полностью или порезано на кусочки? И в какой момент тут нарушается изоляция?
Это — вообще никак не отличается от css variables и имеет всё те же проблемы. Завтра мне нужен будет margin, послезавтра outline, послепослезавтра еще что-нибудь — и что мне делать, писать в спортлото или пачтить чужую библиотеку?
Ок, а когда вам нужен margin или outline с обычным CSS, куда вы обычно пишите?
В css пишу, сразу после селектора, выбирающего мне нужный компонент.
Приведите пример.
Серьезно?
web-component {
  background-color: white;
}

web-component child-component {
  background-color: gray;
}
export const COMMON_CSS = {
  day: {
    webComponent: /*css*/ `
      :host {
        background-color: white;
        outline: none;
        margin: 2px;
      }
      child-component {
        background-color: gray;
      }
    `,
  },
  night: {
    ...
  },
};

const TEMPLATE = /*html*/ `
<style>${COMMON_CSS[currentTheme].webComponent}</style>
<child-component></child-component>
`;
См. https://habr.com/ru/post/485688/#comment_21201010
Мужественная борьба с shadow DOM, в результате которой немногочисленные плюсы пропадают, а многочисленные минусы — все остаются. В этом конкретном случае (в каждый компонент его собственный кусочек) — изоляция остаётся, но по производительности это самый удручающий вариант: множество кусков css без особо ранней загрузки, множество shadow root, каждый из которых обсчитывается отдельно; а юзерам вашей библиотеки еще дополнительно надо сидеть и переносить стили из css в POJO по категориям.
Это какие плюсы у меня пропали? Инкапсуляция? Возможность управлять темами? Вычислять свойства? Может быть стили у меня разбросаны по разным местам? Или что-то повторяется? Или я изобретаю франкенштейнов, типа БЭМ, чтобы мой проект случайно не развалился, когда кто-то неосторожно написал в «спортлото»?
Окей, хорошо, на безрыбье инкапсуляции css мне и вправду нечего возразить — если инкапсуляция реально критична, то придётся жить с shadow DOM, потому что все остальные решения в любом случае еще хуже (а shadow DOM может таки со временем доработают).

Другое дело, что критична она тогда, когда проект становится весьма большим, а тогда же критичным становится и быстродействие, с которым у shadow DOM всё не суперски (впрочем, опять же, альтернатива — только обычный css, потому что все другие решения еще более медленные).
Как по вашему, насколько компонент с Shadow DOM медленнее рендерится, чем просто DOM элемент с такими-же стилями? Если вы пишите «всё не суперски», насколько действительно велика проблема?
Поскольку вы молчите, отвечу сам. Я набросал по быстрому такие тесты:
github.com/foxeyes/shadow-benchmark
На моей машине чистые издержки на ShadowRoot — примерно 10% скорости.
На внутренние стили с отдельным узлом падение скорости уже значительные, но затраты примерно соответствуют затратам на дополнительный узел DOM.
В итоге, конечно, веб-компоненты добавляют накладные расходы, но ничего критичного я не наблюдаю. Тем более, что обычные ноды никто не отменял, ShadowRoot у вас в проекте будет не везде, а значит затраты возрастают не линейно и, тем более, не экспоненциально. В итоге, мы имеем обычный инженерный компромисс: на одной чаше весов удобство и инкапсуляция и небольшая просадка по скорости (относительно оптимизированного ванильного html, без библиотек), на другой — чуть более высокая скорость + препроцессоры со своим синтаксисом, монструозный БЭМ и длинные простыни CSS в далеке от места применения.
Немного глубже раскрою тему с innerHTML:

У веб-компонентов, есть одна очень важная особенность: они сами могут быть добавлены в DOM через innerHTML. Со всей своей логикой и возможностью самостоятельно подписаться на нужные данные, и отвечать за их обновление. Также, они могут отвечать за действия, которые необходимо выполнить при их удалении, например за очистку памяти. И они могут самоуничтожаться при необходимости. Это может кардинально изменить то, как вы организуете то, что вы называете «компоновкой».
У веб-компонентов, есть одна очень важная особенность: они сами могут быть добавлены в DOM через innerHTML.

А что, это могло быть кому-то не очевидно?
Видимо тем, кто боится что придется перерендерить innerHTML целиком при изменении одного из элементов массива с данными…
Степень страха в этом случае у нормального разработчика всецело определяется тем, сколько DOM тянет с собой этот самый «один из элементов массива с данными». Я еще могу позвать сюда товарища khim, который вам быстро в популярных выражениях объяснит, что невозможность пролистать бесконечную ленту на patreon.com дальше нескольких страниц назад — это в конечном счете история того, как кто-то очень храбрый не побоялся перерендерить кое-что целиком. Впрочем, там это всё еще усугублено реактом — на каком-нибудь «сверхбыстром» фреймворке такой тупой прием продержался бы не 5-8 страниц, а 20-30, но итог будет точно такой же.
Вы видимо не поняли. Рендер изменений будет только там, где они, непосредственно, произошли. Именно это кому-то не очевидно.
Это вот здесь-то?
class MyComponent extends HdElement {
  constructor() {
    super();
    this.state = {
      html: '<div>Initial Content</div>',
    };
  }

  set data(data) {
    this.setStateProperty({
      'html': data.map(item => `<data-item>${item.text}</data-item>`).join(''),
    });
  }

}

MyComponent.template = /*html*/ `
<div class="menu" bind="innerHTML: html"></div>
`;

Откуда тут «рендер изменений только там <...>», когда тут полностью шаблон массива меняется?
И в вашем «For other performance sensitive cases you can use this component.» всё то же самое, если вы будете мутировать сам массив — нет переделки там только тогда, когда массив у вас иммутабелен, а меняется только его содержимое.

Я даже не буду говорить, что иммутабельные массивы удобны далеко не всегда.
Хм, конечно не здесь. Почему вообще вы выбрали именно этот кусок кода, если сами нашли правильный? Тем не менее, это далеко не все способы, для сложных случаев (графы данных) я предпочитаю использовать хранилища с подпиской по уникальным ключам.
Почему вообще вы выбрали именно этот кусок кода, если сами нашли правильный?

Во-первых, потому что он не правильный — если уж оптимизировать рендер, то делать это вокруг иммутабельности массива (а не количества изменений в DOM) — это какой-то местячковый случай.
Во-вторых, еще раз обращаю ваше внимание на то, что этим всем подходам, благо их немного, следует быть реализованными в библиотеке, а не «ну это вы сами напишите, если надо».
Вы можете написать свою библиотеку, в которой можете реализовать все, что, по вашему, следует. Попытки охватить все частные случаи приводят к тому, с чего я начал статью. Особенно если эти случаи исключительно умозрительны. Эффективность моего решения подтверждена моей-же практикой.
Документация выполнена в удивительном стиле ))

Замечательный проект, но есть вопрос по поводу бенчмарков.


В вашем тестовом компоненте объявлено только одно свойство, а что будет если их там будет несколько?


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


Поэтому, как мне кажется, если переписать бенчмарк и добавить в тестовый компонент больше свойств, то React и lit-element выберутся вперед.

Да, обновления синхронные, и именно поэтому, в том числе, они работают быстрее. Браузер сам не начинает рендер до окончания синхронного цикла. Тем не менее, и для асинхронного обновления, когда это нужно, имеется метод: setStatePropertyLater (при его использовании скорость приближается к LitElement).
React и lit-element выберутся вперед

Не выберутся, проверено. Но я внесу данный момент в бенчмарки чтобы не оставаться голословным.
Спасибо за отзыв.

И еще один вопрос возник: а как у вас делается условный рендеринг компонента? Что-то вроде этого:


if(color == 'blue') {
   return <BlueButton />
} else {
   return <RedButton />
}
При первичном определении шаблона:
<div>
  ${color === 'blue' ? `<blue-btn></blue-btn>` : `<red-btn></red-btn>`}
</div>

Динамически:
<div bind="innerHTML: html.currentColorButton"></div>

Или, что будет более правильно:
<div>
  <my-btn bind="textContent: actualButtonText; @color: actualButtonColor"></my-btn>
</div>

Вариант с innerHTML оригинальный, работать будет, но опять возникает вопрос производительности. innerHTML знаменит как источник замедления рендеринга.

Это тонкий момент, который мне, видимо, следует описать подробнее, возможно даже отдельный материал этому посвятить. Вкратце: если innerHTML используется для участков DOM, которые должны быть созданы один раз на время всей жизни компонента — это вполне эффективный способ. Если менять динамически — то зависит от сложности структуры внутри. Например, вывести пункты выпадающего списка — вполне сгодится. Если мы динамически меняем сложноструктурированные данные — то эффективные апдейты могут становится частью логики дочерних элементов, которые могут сами обновлять свои данные. То есть одно универсальное, но громоздкое решение, я предлагаю декомпозировать примерно в соответствие с композицией самих данных. Я планирую сделать примеры.
Для скорости лучше использовать innerDOM:
const DATA = [
  {
    firstName: 'John',
    secondName: 'Snow',
  },
  {
    firstName: 'Jane',
    secondName: 'Flower',
  },
];

class MyComponent extends HdElement {
  constructor() {
    super();
    this.state = {
      list: {
        dom: this.prepareDomFragment(DATA, PersonCard, null, 'dataProperty'),
      },
    };
  }
}
MyComponent.template = /*html*/ `
<div class="list" bind="innerDOM: list.dom"></div>
`;

Подробнее тут: holiday-js.web.app/?templates
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории