Photo by NeONBRAND


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


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


Что такое веб-компоненты


Для начала нужно определиться, что именно входит в понятие веб-компонентов. Хорошее описание технологии есть на MDN. Если совсем коротко, то обычно в это понятие включают следующие возможности:


  • Custom elements – возможность регистрировать свои html-тэги с определенным поведением
  • Shadow DOM – создание изолированного контекста CSS
  • Slots – возможность комбинировать внешний html-контент со внутренним html компонента

В качестве примера напишем hello-world компонент, который будет приветствовать пользователя по имени:


// веб-компоненты должны наследоваться от стандартных html-элементов
class HelloWorld extends HTMLElement {
  constructor() {
    super();
    // создадим Shadow DOM
    this.attachShadow({ mode: "open" });
  }

  connectedCallback() {
    const name = this.getAttribute("name");
    // Отрендерим наш контент внутрь Shadow DOM
    this.shadowRoot.innerHTML = `Hello, <strong>${name}</strong> :)`;
  }
}

// зарегистрируем наш компонент как html-тэг
window.customElements.define("hello-world", HelloWorld);

Таким образом, каждый раз, когда на странице будет размещен тэг <hello-world name="%username%"></hello-world>, на его месте отобразится приветствие. Очень удобно!


Посмотреть этот код в действии можно здесь.


Вам все равно понадобятся фреймворки


Распространено мнение, что внедрение веб-компонентов сделает фреймворки ненужными, потому что встроенной функциональности будет достаточно для создания интерфейсов. Однако, это не так. Кастомные html-тэги действительно напоминают Vue или React компоненты, но этого недостаточно, чтобы заменить их целиком. В браузерах не хватает возможности частичного обновления DOM – подхода к описанию интерфейсов, когда разработчик просто описывает желаемый html, а фреймворк сам позаботится об обновлении DOM–элементов, которые действительно изменились по сравнению с прошлым состоянием. Этот подход существенно упрощает работу с большими и сложными компонентами, так что без него придется тяжеловато.


Кроме того, в предыдущем разделе с примером компонента вы могли заметить, что нам пришлось написать какое-то количество кода для регистрации компонента и активации Shadow DOM. Этот код будет повторяться в каждом создаваемом компоненте, так что имеет смысл вынести его в базовый класс – и вот у нас уже есть зачатки фреймворка! Для более сложных компонентов нам понадобятся еще подписка на изменения атрибутов, удобные шаблоны, работа с событиями и т.д.


На самом деле фреймворки, основанные на веб-компонентах, уже существуют, например lit-element. Он использует веб-компоненты, а также lit-html для умного обновления DOM внутри компонента, чтобы не рендерить его целиком. Написание компонентов таким образом гораздо удобнее, чем через нативное API.


Еще часто говорят о пользе веб-компонентов в виде уменьшения размера загружаемого Javascript. Однако lit-element, который использует веб-компоненты весит в лучшем случае 6 кб, в то время как есть preact, который использует свои компоненты, похожие на React, но при этом весит в 2 раза меньше, 3 кб. Таким образом, размер кода и использование веб-компонентов вещи ортогональные и одно другому никак не противоречит.


Shadow DOM и производительность


Для стилизации больших html-страниц может понадобится много CSS, и придумывать уникальные имена классам может оказаться сложно. Здесь на помощь приходит Shadow DOM. Эта технология позволяет создавать области изолированного CSS. Таким образом, можно отрендерить компонент со своими стилями, которые не будут пересекаться с другими стилями на странице. Даже если у вас будет имя класса, совпадающее с чем-то еще, стили не смешаются, если каждый из них будет жить в своем Shadow DOM. Создается Shadow DOM вызовом метода this.attachShadow(), а затем мы должны добавить внутрь Shadow DOM наши стили, либо тэгом <style></style> либо через <link rel="stylesheet">.


Таким образом, каждый экземпляр компонента получает свою копию CSS, что очевидно должно сказаться на производительности. Вот это демо показывает, насколько именно. Если рендер обычных элементов без Shadow DOM занимает порядка 30мс, то с Shadow DOM это около 50мс. Возможно, в будущем производители браузеров улучшат производительность, но в настоящее время лучше отказаться от мелких веб-компонентов и постараться делать компоненты типа <my-list items="myItems"> вместо отдельных <my-item item="item">.


Также стоит заметить, что у альтернативых подходов, вроде CSS-модулей, таких проблем нет, поскольку там все происходит на этапе сборки, и в браузер поступает обычный CSS.


Глобальные имена компонентов


Каждый веб-компонент привязывается к своему имени тега с помощью customElements.define. Проблема в том, что имена компонентов объявляются глобально, то есть если кто-то уже занял имя my-button, вы ничего не сможете с этим сделать. В маленьких проектах, где все имена компонентов контролируются вами, это не представляет особой проблемы, но если вы используете стороннюю библиотеку, то все может внезапно сломаться, когда вы они добавят новый компонент с тем же именем, что вы уже использовали сами. Конечно, от этого можно защититься конвенцией именования с использованием префиксов, но такой подход сильно похож на проблемы с именами CSS-классов, избавление от которых нам обещали веб-компоненты.


Tree-shaking


Из глобального регистра компонентов следует еще одна проблема – у вас нет четкой связи между местом регистрации компонента и его использованием. Например, в React любой используемый компонент должен быть импортирован в модуль


import { Button } from "./button";

//...

render() {
  return <Button>Click me!</Button>
}

Мы явным образом импортируем компонент Button. Если удалить импорт, то у нас произойдет ошибка в рендеринге. С веб-компонентами ситуация другая, мы просто рендерим html-тэги, а они магическим образом оживают. Аналогичный пример с кнопкой на lit-element будет выглядеть вот так:


import '@polymer/paper-button/paper-button.js';

// ...

render() {
  return html`<paper-button>Click me!</paper-button>`;
}

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


Отсутствие явной связи и между импортом и использованием не позволяет делать tree-shaking вашего кода, автоматическое удаление неиспользуемых импортов. Например, если мы импортируем несколько компонентов, но используем не все, они будут автоматически удалены:


import { Button, Icon } from './components';

//...

render() {
  return <Button>Click me!</Button>
}

Icon в этом файле не используется и будет спокойно удален. В ситуации с веб-компонентами этот номер не пройдет, потому что бандлер не в состоянии отследить эту связь. Ситуация очень напоминает 2010 год, когда мы вручную подключали в шапке сайта необходимые нам jquery-плагины.


Проблемы с типизацией


Javascript по своей природе динамический язык, и это нравится не всем. В больших проектах разработчики предпочитают типизацию, добавляя ее с помощью Typescript или Flow. Эти технологии прекрасно интегрируются с современными фреймворками типа React, проверяя корректность вызова компонентов:


class Button extends Component<{ text: string }> {}

<Button />  // ошибка: отсутствует обязательное поле text
<Button text="Click me" action="test" />  // ошибка: лишнее поле action

<Button text="Click me" /> // все как надо, ошибок нет

С веб-компонентами так не получится. В предыдущем разделе рассказывалось, что место определения веб-компонента статически никак не связано с его использованием, и по этой же причине Typescript не сможет вывести допустимые значения для веб-компонента. Здесь на помощь может прийти JSX.IntrinsicElements – специальный интерфейс, откуда Typescript берет информацию для нативных тегов. Мы можем добавить туда определение для нашей кнопки


namespace JSX {
  interface IntrinsicElements {
    'paper-button': {
      raised: boolean;
      disabled: boolean;
      children: string
    }
  }
}

Теперь Typescript будет знать о типах нашего веб-компонента, но они никак не связаны с его исходниками. Если в компонент добавят новые свойства, в JSX определение его будет нужно добавлять вручную. Кроме того, эта декларация никак не помогает нам при работе с элементом через querySelector. Там придется самим кастовать значение к нужному типу:


const component = document.querySelector('paper-button') as PaperButton;

Возможно, по мере распространения стандарта, в Typescript придумают способ статически типизировать веб-компоненты, но пока при использовании веб-компонентов придется попрощаться с типобезопасностью.


Групповое обновление свойств


Нативные браузерные компоненты, такие как <input> или <button>, принимают значения в виде текстовых атрибутов. Однако, иногда может понадобиться передавать более сложные данные в наши компоненты, объекты, например. Для этого предлагается использовать свойства с геттерами и сеттерами.


// находим наш компонент в DOM
const component = document.querySelector("users-list");

// передаем в него данные
component.items = myData;

На стороне компонента мы определяем сеттер, который эти данные обработает:


class UsersList extends HTMLElement {
  set items(items) {
    // сохраняем значение
    this.__items = items;
    // перерисовываем компонент
    this.__render();
  }
}

В lit-element для этого есть удобный декоратор – property:


class UsersList extends HTMLElement {
  @property()
  users: User[];
}

Однако, может случиться ситуация, что нам нужно обновить несколько свойств сразу:


const component = document.querySelector("users-list");

component.expanded = true;
component.items = myData;
component.selectedIndex = 3;

Каждый сеттер вызывает рендеринг, ведь он не знает, что там будут обновлены и другие свойства. В результате у нас будут два лишних обновления, с которыми нужно что-то делать. Стандарт ничего готового не предоставляет, поэтому разработчикам нужно выкручиваться самим. В lit-element это решают асинхронным рендерингом, то есть сеттер не вызывает обновление напрямую, а оставляет запрос на отложенный рендеринг, что-то вроде setTimeout(() => this.__render(), 0). Такой подход позволяет избавиться от лишних перерисовок, но усложняет работу с компонентом, например его тестирование:


component.items = [{ id: 1, name: "test" }];
// не сработает, рендер еще не произошел
// expect(component.querySelectorAll(".item")).toHaveLength(1);

await delay(); // нужно подождать пока обновление применится
expect(component.querySelectorAll(".item")).toHaveLength(1);

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


Выводы


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


  • Встраивание клиентской логики в большой сервер-рендерный проект. По такому пути сейчас идет Github. Они активно используют веб-компоненты для своего интерфейса и даже опубликовали в open-source некоторые из них. В ситуации, когда у вас большая часть страницы статическая или рендерится сервером, веб-компоненты помогут придать интерактивности некоторым частям.
  • Реализация микро-фронтендов. На странице рендерятся независимые виджеты, которые могут быть написаны на совсем разных фреймворках и разными командами, но им надо как-то уживаться вместе. При этом они вываливают свой CSS в глобальную область и всячески мешают друг другу. Для борьбы с этим у нас раньше были только iframe, теперь же мы можем завернуть отдельные микро-фронтенды в Shadow DOM, чтобы они жили там своей жизнью.

Есть также и вещи, которые я бы на веб-компонентах делать не стал:


  • UI-библиотека получится неудобной, по причине проблем с tree-shaking и типами, которые раскрыты в этой статье. Написание UI-компонентов (кнопок, инпутов и т.д.) на том же фреймворке, что и основная часть страницы (React, Vue и пр.) позволит им лучше взаимодествовать с основной частью страницы.
  • Для основонго контента страницы веб-компоненты не подойдут. С точки зрения пользователя, рендеринг страницы с единственным веб-компонентом <my-app /> ничем не отличается от использования SPA-фреймворка. Пользователь будет вынужден ждать пока прогрузится весь Javascript, чтобы наконец-то увидеть контент. И если в случае Angular/React/Vue это можно ускорить путем пре-рендера страницы на сервере, то в случае веб-компонентов таких возможностей нет.
  • Инкапсулировать части своего кода в веб-компоненты тоже смысла нет. Вы получите проблемы с производительностью, отсутствие типов и никаких особых преимуществ взамен.

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

Комментарии (159)


  1. BigDflz
    10.03.2019 13:07
    -5

    Уж если взялся писать — то надо писать всё, а не часть, и на основании этой части делать вывыды. Где упоминание о template, connectedCallback, disconnectedCallback, adoptedCallback, attributeChangedCallback,observedAttributes


    1. justboris Автор
      10.03.2019 13:15
      +6

      Статья не об основах веб-компонентов, об этом уже написано достаточно, например вот этот серия статей.


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


  1. greabock
    10.03.2019 13:40

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

    Гораздо важнее, что проблема с коллизией имен больше не лежит на разработчике компонентов. А разработчик, монтирующий компоненты в свое приложение, сам волен назвать любой компонент — так, как ему удобно его называть. Это так же относится не только к стандарту веб-компонентов, но и к любому фреймворку оперирующему понятием "компоненты" в том же смысле. Как то: React, Vue, Angular, и т.п.


    1. justboris Автор
      10.03.2019 13:58
      +3

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


      import { Button as ButtonA } from 'lib-a';
      import { Button as ButtonB } from 'lib-b';

      Веб-компоненты регистрируются в общем для всех списке html-тэгов. Если одна библиотека уже заняла имя cool-button, вы ничего уже с этим сделать не сможете.


      1. Riim
        10.03.2019 14:52

        вы ничего уже с этим сделать не сможете

        Можно унаследовать от такого компонента с изменением имени.


        1. justboris Автор
          10.03.2019 14:56

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


          1. Riim
            10.03.2019 15:01

            Обычно элементам добавляется какой-то префикс, например здесь добавляют paper.


            1. justboris Автор
              10.03.2019 15:30
              +1

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


              1. Riim
                10.03.2019 16:15

                Может хорошей практикой считать не регистрировать компоненты в UI-библиотеках, просто экспортировать класс и пусть использующий сам регистрирует под нужным ему именем. Я все компоненты сам писал так что такой проблемы не было, была проблема с именами атрибутов-свойств: уже существующих свойств дофига, у разных элементов они немного отличаются, плюс немного отличаются от браузера к браузеру. В результате можно легко переопределить существующее имя и получить какой-нибудь непонятный баг.


                1. justboris Автор
                  10.03.2019 17:36
                  +1

                  Регистрация компонентов самому выручит только если они не имеют своих зависимостей. В реальности они у них есть, например paper-input зависит от компонента iron-input. Чтобы не случился конфликт на уровне iron-input, его тоже нужно уметь переопределять...


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


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

                  Здесь помог бы typescript, который бы проверил, что вы используете только правильные свойства. Вот пример. Я пытаюсь определить свойство checked с неправильным типом, и typescript ругается, что такое свойство уже есть в базовом классе.


                  1. Riim
                    10.03.2019 18:03

                    только если они не имеют своих зависимостей

                    Ладно, убедили), проблема есть, но всё же при использовании префиксов она совсем незначительна.


                    Здесь помог бы typescript

                    Не на 100%, как я уже сказал, браузеры могут добавлять свои нестандартные свойства/методы для элементов.


      1. greabock
        10.03.2019 17:29
        +1

        Я просто не понимаю, почему нельзя сделать так:


        import { Button as ButtonA } from 'lib-a'
        import { Button as ButtonB } from 'lib-b'
        
        customElements.define('button-a', ButtonA)
        customElements.define('button-b', ButtonB)

        Это не троллинг, я просто пытаюсь понять, почему вас так беспокоит коллизия имен.


        1. justboris Автор
          10.03.2019 17:36
          +3

          Возникают проблемы с транзитивными зависимостями. Уже ответил выше.


          1. greabock
            11.03.2019 10:27

            О, теперь понимаю. А как вы бы решили эту проблему?
            Сходу я вижу три возможных варианта:


            1. Введение своей области видимости компонентов


              // допустим в конструкторе модуля
              this.customElements.define('button-a', ButtonA)

            2. Вендор-префикс. Это очевидно и это отлично работает в мире PHP (правда не все авторы пакетов соблюдают это правило, но большинство).


            3. Алиасы (это скорее развитие первого варианта)


              customElements.define('element-foo', ElementFoo, {
              aliases: {
                'button-bar' : 'button-baz'
              }
              })


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


            1. justboris Автор
              11.03.2019 11:07

              Первые два варианта – это про решение проблемы путем конвенции именования. Для 10 компонентов это еще сработвает, а для 100, для 1000? Мы это уже проходили с CSS, видно, что на больших масштабах имена не спасают. Но в CSS хотя бы самое страшное что могло случиться – это сломанные стили, то в случае конфликта имен веб-компонентов у вас будет выброшена ошибка в JS и код просто не будет исполняться дальше


              Алиасы (это скорее развитие первого варианта)

              А вот это может сработать! Примерно так это решается во Vue: Local Component Registration. Но для этого нужно изменить сам веб-стандарт.


  1. LabEG
    10.03.2019 17:24

    На самом деле проблемы совсем не страшные.

    • Tree-shaking и Глобальные имена компонентов — не обязательно регистрировать отдельно и глобально, можно сделать также как сделано в styled components

      import {ButtonElement} from "wclibs/button-element"
      
      const Button = wc.register(ButtonElement);
      ...
      render (<Button/>);
      

      Тогда много проблем сразу отваливается.
    • Проблемы с типизацией — Intrinsic описанный в статье решает проблему.
    • Групповое обновление свойств — это не проблема веб компонентов, это стандартное поведение DOM, а если писать без прослойки с VDOM то и не проблема вовсе.

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


    1. justboris Автор
      10.03.2019 18:46

      Tree-shaking и Глобальные имена компонентов

      Проблема в том, что API выглядит не так, как вы описываете, а по-другому


      customElements.define('button-element', ButtonElement);

      где имя компонента обязательно и должно быть уникально в рамках текущего документа.


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


      Проблемы с типизацией — Intrinsic описанный в статье решает проблему.

      Там же в статье и описаны недостатки. Обновили компонент, поменяли свойства, но забыли обновить Intrinsic. Typescript молчит, а в рантайме все ломается.


      это стандартное поведение DOM, а если писать без прослойки с VDOM то и не проблема вовсе.

      Вот пример:


      const input = document.querySelector('[name="test"]');
      input.disabled = true;
      input.value = 'bar';

      В случае нативного тэга input браузер сам позаботится об оптимизации и перерендерит input только после обновления всех свойств. А как вы будете оптимизировать свой собственный компонент?


      у нас большая компания, пишем на разных шаблонизаторах и веб компоненты помогают реюзать компоненты между ними

      Тем интереснее узнать, как вы решаете указанные проблемы. Давайте обмениваться опытом :)


      1. LabEG
        10.03.2019 19:37

        API выглядит не так, как вы описываете

        Я описал абстрактный сервис который будет заниматься регистрацией веб компонентов и возвращать готовый React компонент для его использования =). А у себя решили просто корпоративным префиксом, что то типо <prefix-button/> и ближайшие пару лет проблем точно не будет.

        Обновили компонент, поменяли свойства, но забыли обновить Intrinsic. Typescript молчит, а в рантайме все ломается.

        Это на самом деле не проблема, есть разные способы решения, начиная от Partial и заканчивая скриптом генерацией интринсиков на основе кода. Я же просто тесты использую.

        В случае нативного тэга input браузер сам позаботится об оптимизации и перерендерит input только после обновления всех свойств.

        Нет, он пересчитывает поштучно, именно поэтому виртуальный дом бывает много выгоднее:

        var elem = document.querySelector(".post__title_link");
        elem.textContent += "1";
        console.log(elem.getClientRects()[0].width); // 419.203125
        elem.textContent += "2";
        console.log(elem.getClientRects()[0].width); // 433.40625
        


        А как вы будете оптимизировать свой собственный компонент?

        Я не использую lit-element, у меня своя микро-надстройка. Просто на сеттере свойства я меняю только то что мне нужно, поэтому поведение полностью наследуется от нативного =)


        1. justboris Автор
          10.03.2019 20:24

          ближайшие пару лет проблем точно не будет

          ну что же, вам виднее


          Просто на сеттере свойства я меняю только то что мне нужно

          Подозреваю, что у вас получилось что-то вроде такого:


          class MyButton extends HTMLButtonElement {
             set loading(loading) {
               this.classList.toggle('button-loading', loading)
             }
          }

          Такой подход сработает на маленьких компонентах. А что, если компонент большой, к javascrtipt-логикой, какое-нибудь dropdown-menu, например, то как с ним быть?


          1. LabEG
            10.03.2019 20:34

            Да именно такая конструкция. Дроп даун очень простой на самом деле. Вот календарь это да.

            Больших компонентов всего три, и да, это больно делать без шаблонизатора.
            Поэтому посмотрим как будут развиваться события и когда будем писать более серьезные вещи выберем какой нибудь микро шаблонизатор.


            1. justboris Автор
              10.03.2019 22:01

              Даже если взять самый умный шаблонизатор, проблема с API все равно останется. Допустим, есть календарь:


              const calendar = document.querySelector('my-calendar');
              calendar.selectedDate = new Date(2019, 2, 10);
              calendar.minDate = new Date(2019, 0, 1);
              calendar.maxDate = new Date(2019, 12, 1);

              Каждое из этих изменений свойств вызовет ре-рендер, но реально нужен только последний.


              Придется городить какую-то оптимизацию, например:


              calendar.__renderBlocked = true;
              // здесь задаем наши свойства, ре-рендера нет
              calendar.__renderBlocked = false;
              // здесь произойдет ре-рендер

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


              1. syncro
                11.03.2019 06:57

                вопрос будет ли ваша оптимизация работающая на джаваскриптовом рантайме работать быстрее оптимизаций и рендеринга браузерного движка. Я делал бенчмарк 1000000 изменений данных прибинденых к интерфейсу, сравнивая вебкомпоненты + прокси и реакт и редакс. Нативный образец просчитался за 5-6 секунд и отобразил сразу финальное значение, в то время как рякт с пропсами (это был стартер прямо из yeoman) работал несколько минут, сожрал всю память и упал так и не отобразив ни одного изменения


                1. justboris Автор
                  11.03.2019 11:24

                  В вашей истории слишком много дополнительных элементов (Redux и его connect), тем более, что это известно, что в yeoman-стартеры любят напихать всякого лишнего для удобства DX в ущерб производительности. Предполагаю, что вы бы получили прирост скорости, даже если бы просто переписали все на ванильный React. Веб-компоненты тут не причем.


                  1. syncro
                    11.03.2019 11:59

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


                    1. justboris Автор
                      11.03.2019 12:03

                      Я так понимаю, текущее значение счетчика вы через innerHTML рендерили? А как будете рендерить более сложный контент?


                      1. syncro
                        11.03.2019 12:31

                        да, но правильнее даже наверное .textContent или setAttribute для заменяемых значений, так типобезопаснее и скорее всего прозиводительнее, а основная часть просто должна быть внутри тегов ну если надо динамически еще есть .insertAdjacentHTML()


              1. PaulMaly
                11.03.2019 10:18

                Или можно делегировать работу с DOM API, отслеживание связей стейта и DOM и т.д. компилятору. Пусть он мучается с этими императивными и не удобными штуками, а мы будем писать высокоуровнево и крайне декларативно. ;-)


                1. justboris Автор
                  11.03.2019 11:18

                  Даже если мы напишем идеальный компилятор, то что будет с элементом, который зависит сразу и от minDate, и от maxDate? Он будет обновлен два раза.


                  Не хватает возможности сказать компоненту "подожди рендериться, мы тебе еще не все данные задали".


                  1. PaulMaly
                    11.03.2019 12:41
                    +1

                    Мне почему-то кажется, что это уже микро-оптимизации какие-то. Учитывая, что всекие там реакты/вью вообще отдельно виртуальное дерево строят на каждый пчих, то 2 раза обновить элемент напрямую через DOM API по-любому будет достаточно быстро. Кстати такой компилятор уже есть — Svelte JS называется.


                    1. justboris Автор
                      12.03.2019 00:46

                      Посмотрел я на Svelte. Там тоже есть нормальный способ обновления всех свойств разом: component.set(state). И в дочерние компоненты это нормально протягивается, тоже всей пачкой.


                      Это только в веб-компонентах удобного API нет и нужно что-то изобретать


                      1. PaulMaly
                        12.03.2019 08:53

                        Да, в веб-компонентах забыли предоставить хоть какой-то механизм синхронизации стейта и DOM. Наряду с отсутствием props reflection и нормального SSR, делает область их применения крайне узкой. Я с вашей статьей по большей части согласен. У меня даже доклад есть на эту же тему примерно с теми же консернами.

                        Кстати, Svelte умеет в веб-компоненты одним булевым флагом и поддержка отличная:

                        custom-elements-everywhere.com/libraries/svelte/results/results.html

                        Так что мы просто пишем на нем, в если веб-компоненты таки выстрелят, изменим флажок в конфиге.


                        1. justboris Автор
                          12.03.2019 11:03

                          Вот так и правильно – пусть веб-компоненты будут деталью реализации фреймворка.

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


                          1. syncro
                            12.03.2019 11:26

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


                            1. justboris Автор
                              12.03.2019 11:53

                              Ваше описание веб-компонентов слишком идеализированное. Именно для более адекватного представления их возможностей и написана эта статья


                              дающих почти полную замену среднему фронтенд-фреймворку в сумме

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


                              они и работают тоже быстрее

                              По моим измерениям – это не так. Не согласны – приводите свои замеры с цифрами.


                              загрузки и выполнения лишнего жс кода на старте

                              Lit-element (использует веб-компоненты) – 6 Кб, Preact (не использует) – 3 Кб. Получается, неиспользование веб-компонентов наоборот уменьшает код?


                              Разница когда вы отключили жс-бибилиотеки заметна на глаз даже на простейших примерах

                              Вот этих самых примеров мне и не хватает. Что вы имеете в виду?


                              1. syncro
                                12.03.2019 12:27

                                для более адекватного представления

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

                                По моим измерениям – это не так. Не согласны – приводите свои замеры с цифрами.

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

                                Lit-element (использует веб-компоненты) – 6 Кб, Preact (не использует) – 3 Кб


                                это минифицированный код, который всеравно будет разбираться джаваскриптовым интерпретатором, в ваших кодах будут ссылки на методы «ядра» из этих библиотек, которые в интепретируемом языке будут делать бестолковые манпипуляции, если вы запускали хоть раз профайлер то видели, что он часто указывает на какую-то одну-две функции используемые повсеместно

                                Вот этих самых примеров мне и не хватает. Что вы имеете в виду?


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


                                1. justboris Автор
                                  12.03.2019 12:35

                                  Без конкретных цифр звучит неубедительно.


                                  1. syncro
                                    12.03.2019 12:39

                                    в этом треде я приводил пример когда затык кода на вебкомпонентах разрешился за 5-6 секунд, а рякт работал на том же коде несколько минут пока не сожрал всю доступную память.

                                    Вы можете также нагуглить публичные бенчмарки, я не знаю как они там мерили, но там в числе лидеров веб-компонентный Svelte (https://medium.com/@ajmeyghani/javascript-frameworks-performance-comparison-c566d19ab65b), но тут над понмимать, что этот фреймворк наворачивает свой mustache подобный шаблонизатор над нейтив темплейтами или вовсе без них в рантайме, т.е. занимается тем же бестолковым преобразованием строк в объекты дом с полнотекстовым поиском плейсхолдеров для замены вместо оптимизированного дерева (dom)


                                    1. PaulMaly
                                      13.03.2019 13:52

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

                                      Заметил, что вы крайне поверхностно относитесь к своим высказываниям и приводимым примерам.

                                      Для начала, Svelte ни разу не «веб-компонентный». Возможность скомпилировать svelte-компонент в веб-компонент есть, но ей почти никто не пользуется, из-за проблем описанных в данной статье в частности. Есть компиляторы, которые заточены на веб-компоненты, например, StencilJS, но Svelte работает с js классами, а не веб-компонентами.

                                      но тут над понмимать, что этот фреймворк наворачивает свой mustache подобный шаблонизатор над нейтив темплейтами или вовсе без них в рантайме, т.е. занимается тем же бестолковым преобразованием строк в объекты дом с полнотекстовым поиском плейсхолдеров для замены вместо оптимизированного дерева (dom)

                                      Здесь даже незнаешь куда пробу ставить. В Svelte нет никакого mustache-like шаблонизатора над нейтив темплейтами. Более того, он вообще не занимается работой со строками в рантайме, а как раз использует прямые вызовы в DOM API для этого.

                                      Прежде чем писать что-то с уверенным видом, лучше сперва слегка изучить вопрос. Имхо


                              1. PaulMaly
                                12.03.2019 13:37

                                Lit-element (использует веб-компоненты) – 6 Кб, Preact (не использует) – 3 Кб. Получается, неиспользование веб-компонентов наоборот уменьшает код?

                                SvelteJS — 0Кб ;)


                                1. justboris Автор
                                  12.03.2019 23:57

                                  Я с интересом читал ваши статьи про исчезающие фреймворки, поэтому про магию Svelte в курсе.


                                  Тем не менее там не совсем 0кб, потому что какой-то рантайм все таки грузится. Это примерно как babel-runtime, подключается по мере использования, но измерить его все-таки можно.


                                  Для вашей демки получилось 991 байт gzip. Понятное дело, что это хорошая цифра и к этому нужно стремиться, но это не 0.


                                  Как считал
                                  1. Собрал бандл с помощью rollup. Конфиг отсюда
                                  2. Выкинул из него все вхождения своего кода. Имена ключей не минифицируются, поэтому можно его вычислить
                                  3. Оставшийся бандл минифицируем обратно и смотрим: cat bundle.js | npx terser | npx gzip-size-cli


                                  1. syncro
                                    13.03.2019 04:22

                                    технологии входящие в веб-компоненты реализованы в браузерах нативно и как следствие вообще не требуют библиотек и работают быстрее


                                    1. PaulMaly
                                      13.03.2019 10:51

                                      работают быстрее

                                      Что-то не похоже


                                  1. PaulMaly
                                    13.03.2019 10:43

                                    Приятно, что вы читали мои статьи)) Если вдруг будете на HolyJS в мае — велком на мастер-класс, покодим еще.

                                    В целом, с вашим комментарием согласен. Отличие подхода «исчезающих фреймворков», конечно же не в том, что им каким-то магическим образом удалось убрать рантайм из рантайма. Суть в том, что этот рантайм решает лишь задачи вашего приложения, то есть фактически компилятор пишет за нас тот бойлерплейт код, который мы бы сами написали, если бы решали очень конкретную задачу, самым очевидным и простым способом. Без лишних абстракций и решений-комбайнов, подходящих для всех задач сразу. То есть на скриншоте в целом верно написано library: 0Kb, это конечно же не значит, что в рантайме не отрабатывает код.))))

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

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


                            1. PaulMaly
                              12.03.2019 13:29

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

                              Вы видимо Svelte не посмотрели, тогда бы хоть значи о чем речь. ))) Это как раз фреймворк, который может быть внедрен в любой другой фреймворк. Вот можете даже пример посмотреть тут. Кроме того любой компонент Svelte можно изменением одного флага компилировать в веб-компонент. Поддержка стандарта 100%, выше уже давал ссылку.

                              Веб компоненты в свою очередь стандартизированы в виде ряда технологий решающих разные задачи и дающих почти полную замену среднему фронтенд-фреймворку в сумме

                              Основная фича любого современного фреймворка — это ни разу не компоненты и их композиция в виде html-like тегов. Прежде всего, что дают нам фреймворки писать в state-based код, вместо event-based на котором основан весь DOM API. Кроме того, возможность простой и удобной синхронизации стейта и DOM с максимально эффективными манипуляциями в последний. Все это ровно то, чего нет в стандарте веб-компонентов, а то, что там есть не так уж и важно. Например, та же инкапсуляция стилей вполне адекватно решается в CSS Modules или том же Svelte.

                              Разница когда вы отключили жс-бибилиотеки заметна на глаз даже на простейших примерах.

                              То то Youtube в Firefox до сих пор тормозит безбожно.


                              1. syncro
                                12.03.2019 14:03

                                То то Youtube в Firefox до сих пор тормозит безбожно.


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


                                1. PaulMaly
                                  12.03.2019 14:55

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

                                  Видите как получается, на стандарт даже Гугл положиться не может, куда уж нам смертным.

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

                                  Боюсь вы не поняли) html imports не используется в Svelte никак, даже близко. Да и то, что они были деприкейтед не имеет отношения к принципу ответственности, скорее к принципу DRY, так как es6 модули и импорты уже завезли.

                                  Классы-инстансы для хранения и линковки состояния я могу легко подключить в компоненты с инжектором если что-бы красиво или нехитрым собственным мета-кодом.

                                  Прикол в том, что на выходе Svelte-компонент это именно класс, причем абсолютно ванильный )) Только писать его в разы удобнее, декларативнее и без убогого и многословного синтаксиса es6 классов.


  1. Synoptic
    10.03.2019 18:05

    В lit-element уже есть встроенный VDOM, lit-html, и другие базовые возможности.

    Не расскажете в двух словах, как оно там работает? Тут пишут, что lit-html VDOM не использует, а как оно в lit-element я не знаю.


    1. justboris Автор
      10.03.2019 18:50

      lit-element использует самый обычный lit-html, здесь скорее вопрос терминологии.

      Под VDOM имеется в виду то, что обновление компонента происходит не грубым element.innerHTML, а каким-то более умным способом. Каким именно – неважно.

      У вас есть какое-то лучшее слово для всего этого семейства технологий «умных обновляетелей DOM»?


      1. PaulMaly
        11.03.2019 10:13

        Например, «эффективные манипуляции в DOM». Все же термин VDOM — оносится к совершенно опредеенному способу работы с DOM и конечно же в lit-html никакого VDOM нет. Мне кажется упоминание VDOM в контексте lit-element/lit-html немного вводит в заблуждение.


        1. justboris Автор
          11.03.2019 11:37
          +1

          Обновил формулировки в статье, спасибо за фидбек!

          Но моё мнение остается тем же: когда lit-html заявляет: «мы не VDOM!» – это просто такой маркетинг, чтобы привлечь больше пользователей, которые по каким-то причинам не хотят React. Да, есть некоторые отличия под капотом, но это детали реализации, для пользователей библиотеки разницы нет.


          1. nuit
            11.03.2019 12:09

            для пользователей библиотеки разницы нет

            все vdom реализации как минимум предоставляют два примитива: динамический список чилдренов и динамические атрибуты. Первое в lit-html есть, второе отсутствует https://github.com/Polymer/lit-html/pull/213 .


            1. justboris Автор
              11.03.2019 12:28

              Спасибо за информацию! Но, кажется, недостающая фича ещё больше уменьшает смысл lit-html и его подхода


              1. nuit
                11.03.2019 12:58

                У lit-html как минимум есть смысл в том чтоб проверить теорию о том насколько эффективно такой подход будет работать на кэйсах с маленьким количеством динамических биндингов (простые веб приложения). Но на данный момент они как-то умудрились в этом бэнчмарке[1] во всех тестах показывать результаты хуже чем у vdom библиотеки, хотя в этом бэнчмарке они должны быть в выигрышном положении так как здесь практически нет динамических биндингов, не требуется использовать композицию и можно просто клонировать большой кусок dom'а и в дополнение они ещё абузят event delegation[2].

                1. krausest.github.io/js-framework-benchmark/current.html
                2. github.com/krausest/js-framework-benchmark/blob/e469a62889894cb4d4e2cac5923f14d91d1294f8/frameworks/keyed/lit-html/src/index.js#L126


          1. PaulMaly
            11.03.2019 12:47

            И все же vdom — это vdom. Это далеко не общий термин и он ни разу не обобщает подходы к повышению эффективности манипуляций в DOM. В том же Angular vdom нет, в Svelte тоже его нет, также как в lit-html, при это все они пытаются делать изменнения в DOM максимально эффективно. Полагаю без таких вещей как render virtual tree & reconcile, считать что-то vdom значит намеренно сужать разнообразие подходов. Именно в vdom полно маркетинга, поэтому многие молодые разработчики думают что на vdom свет клином сошелся.


  1. i360u
    10.03.2019 21:49

    Большая часть тезисов и выводов данной статьи весьма спорна, это мягко говоря. С веб-компонентами нет никаких особых проблем ни с контролем зависимостей (реестр компонентов дает вам всю необходимую информацию о том, что вы регистрируете в рантайме в любое время), ни с типами (вы вполне можете писать свой код на TS и создавать тайпинги для своих тегов) ни, тем более, с производительностью (автор бы сравнил скорость рендера с react/vue-компонентами, справедливости ради, а еще сам код можно разобрать по косточкам, ибо в нем речи не идет о компонентах, сравнивается теплое с мягким). Сфера применения указана также весьма странно, видно что автор копнул тему но явно недостаточно глубоко. Про VDOM тоже ерунда написана, в подходе с веб-компонентами вам вообще не обязательно иметь какие-либо промежуточные сущности между DOM и данными, вы можете производить инъекции максимально эффективно через старый добрый DOM API. Очень не советую рассматривать данную статью как какое-либо руководство к действию. Но с тем, что веб-компоненты это не замена фреймворкам, конечно, стоит согласиться, их скорее стоит рассматривать как основу для фреймворков нового типа, а также как композиционную основу для построения собственных велосипедов. В этой роли — они великолепны.


    1. justboris Автор
      10.03.2019 22:21
      +1

      автор бы сравнил скорость рендера с react/vue-компонентами, справедливости ради

      Вот две демо-страницы. Рендерится выпадающий список с 2000 элементов.



      В случае lit-element в последнем Хроме я наблюдаю явную просадку FPS при открытии меню. В Preact-версии такого нет. Исходный код демок можно посмотреть вот здесь.


      вы вполне можете писать свой код на TS и создавать тайпинги для своих тегов

      Как это сделать? Самое лучшее, что я нашел – это поддержка типов в Stencil, но она далеко не дотягивает то того, что я получаю с React.


      Про VDOM тоже ерунда написана, в подходе с веб-компонентами вам вообще не обязательно иметь какие-либо промежуточные сущности между DOM и данными

      VDOM иметь не обязательно, но для больших компонентов сильно желательно. Если компонент содержит внутри больше пары html-тэгов, то императивно обновлять их будет неудобно. Подробнее уже раскрыто в этой ветке комментариев.


      Сфера применения указана также весьма странно, видно что автор копнул тему но явно недостаточно глубоко

      Что не так со сферой применения? Есть еще какие-то неперечисленные варианты?


      1. syncro
        11.03.2019 06:40
        +1

        В случае lit-element в последнем Хроме я наблюдаю явную просадку FPS при открытии меню. В Preact-версии такого нет. Исходный код демок можно посмотреть вот здесь.


        lit-element это не совсем веб-компоненты, это штука больше похожая на react и видимо от тех же проблем страдающая, а метод рендеринга собственно веб-компонентов это Native Templates, когда вы клонируете экземпляр шаблона и апендите, это работает намного быстрее любого шаблонизатора, потому что на момент работы жс рантайма шаблон уже разобран браузером и не нуждается в значительных рендеринг операциях


        1. justboris Автор
          11.03.2019 11:49

          Возможно. Обновил демку из статьи, чтобы можно было сравнить Preact с ванильным подходом: web-components-eesuvphctg.now.sh Preact держится на уровне обычного DOM, Shadow DOM отстает.

          Попробовал добавить использование template-элемента, разницы никакой: web-components-vbrhqgtead.now.sh Видимо, на таких маленьких размерах html это особой роли не играет.


          1. syncro
            11.03.2019 12:07

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


            1. justboris Автор
              11.03.2019 12:31

              Создание шаблона происходит за пределами измеряемого отрезка, так что разницы никакой.


              затем вы там основную работу делаете строками

              А как ее делать иначе, если контент для разных элементов отличается?


              1. syncro
                11.03.2019 12:37

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


      1. i360u
        11.03.2019 09:12
        +1

        Во первых, ситуацию с LitElement — некорректно экстраполировать на веб-компоненты в целом. Эта либа написана извращенцами любителями функционального подхода в применении к глубоко «объектным», по своей сути, DOM-элементам, коими и являются веб-компоненты. Разница — в парсинге шаблонов: есть очень быстрый нативный парсинг (innerHTML до начала отрисовки компонента), и есть парсинг шаблонных строк в js, с разделением на чанки, складыванием в память точек вставки для биндингов и т.д. (как в lit-html). Априори второй способ медленнее. Также, именно по этой причине, любой фреймворк, основанный на близком подходе будет медленнее примерно раза в 2 (и больше) чем при использовании нативного парсинга шаблона до вставки элемента на страницу. Сделаю акцент: мы говорим о первичной отрисовке, есть еще апдейты отражающие измененные данные. Скорость этих апдейтов отличается не так радикально, но прямые изменения через свойства типа textContent будут также быстрее чем отложенный рендеринг в LitElement (процентов на 10, судя по моим тестам). По вышеописанным причинам, мне очень не нравится тот путь, который выбрали разработчики Polymer, хотя они и говорят о работе «со скоростью молнии».

        Во вторых: сделайте, наконец, корректный тест. Отобразите на странице 1000 компонентов, обязательно с одинаковой структурой, созданных с помощью LitElement, React и на чистых компонентах. Вы сами все увидите.

        Как это сделать? Самое лучшее, что я нашел – это поддержка типов в Stencil, но она далеко не дотягивает то того, что я получаю с React.


        Если компонент добавляется в разметку через парсинг html, то вот: github.com/Microsoft/vscode/issues/62976
        Если через импорт и ключевое слово «new», то также как и везде, когда вы используете кастомные типы.

        VDOM иметь не обязательно, но для больших компонентов сильно желательно


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

        Что не так со сферой применения? Есть еще какие-то неперечисленные варианты?


        Ага, есть. Мы уже, насколько я помню, об этом говорили: habr.com/en/post/422499


        1. nuit
          11.03.2019 09:45

          > Также, именно по этой причине, любой фреймворк, основанный на близком подходе будет медленнее примерно раза в 2 (и больше) чем при использовании нативного парсинга шаблона до вставки элемента на страницу.

          Можете продемонстрировать это на каком-нибудь примере?


          1. i360u
            11.03.2019 12:01

            Я конечно понимаю, что когда делаешь заявления о 2-кратной разнице в скорости, это обязывает к представлению доказательств. Но не думаю что их стоит мне оформлять в качестве ответа на коммент. Я написал на эту тему собственную либу и хочу посвятить ей отдельную статью, где и приведу все тесты. На оформление потребуется какое-то время, там очень много всего. Тем не менее, вы сами можете все легко проверить: lit-element.polymer-project.org/guide/start


            1. nuit
              11.03.2019 12:15
              +1

              Тем не менее, вы сами можете все легко проверить: lit-element.polymer-project.org/guide/start

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


              1. syncro
                11.03.2019 12:40

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


              1. i360u
                11.03.2019 12:55

                Простите великодушно, я не гугл, у меня нет ни собственного пиар-отдела ни полчища разработчиков на подхвате. Тесты — это вопрос вовсе не такой простой как может показаться на первый взгляд. В текущей ситуации мы равны: вы, также можете проявить инициативу и поиграть с примерами сами. Кроме того, я довольно подробно изложил причину разницы в производительности. Готовы с этим поспорить?


                1. nuit
                  11.03.2019 13:06

                  > Готовы с этим поспорить?

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


                  1. syncro
                    11.03.2019 13:18

                    возьмите вот этот заслуженный наверняка генератор

                    github.com/zakangelle/generator-redux-stack

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


                    1. nuit
                      11.03.2019 13:34

                      Беру тормозной virtual dom, традиционный бэнчмарк с кучей апдэйтов dbmonster[1], смотрю на профайлер в хроме и вижу «85.72% (program)». Чо оптимизировать то?

                      1. localvoid.github.io/ivi-examples/benchmarks/dbmon/?m=1


                      1. syncro
                        11.03.2019 13:41

                        да, течет и жрет ресурсы единственно возможного в одном потоке ядра хоть и не так быстро как было у меня с реактом и редаксом, это миллион апдейтов:)?
                        вот код который делает миллион апдейтов на нативе, но его браузерый рантайм оптимизирует и он выполняется за 5-6 секунд сразу показывая финальный результат%)

                                let data = { foo: 'bar'};
                                let template = document.importNode(document.getElementById('comp-template').content, true);
                        
                                let container = document.getElementById('container');
                                container.appendChild(template);
                                let dataEl = container.querySelector('#data');
                        
                                let dataProxy = new Proxy(data, {
                                    set(target, prop, value) {
                                        target[prop] = value;
                                        dataEl.innerHTML = value;
                                        return true;
                                    }
                                });
                        
                                (function() {
                                    console.log('starting ..');
                                    var i = 0;
                                    console.log(bench(() => {
                                        dataProxy.foo = ++i;
                                    }, 1000000, [], this)); // 1 million updates == 8.3 secs
                                })();
                        


                        1. nuit
                          11.03.2019 13:51

                          Что это за странный юзкэйс? Зачем вы трогаете innerHTML у одного элемента 1 миллион раз за фрэйм. Все современные декларативные библиотеки умеют батчинг.


                          1. i360u
                            11.03.2019 13:59

                            А еще батчинг умеет сам браузер, и делает это оптимальнее js.


                            1. nuit
                              11.03.2019 14:00
                              -1

                              А ещё нужно показывать цифры прежде чем продолжать делать различные заявления.


                          1. syncro
                            11.03.2019 14:16
                            +1

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


                            1. nuit
                              11.03.2019 14:38

                              вообще я сделал такой же точно по входным тербованям пример на реякте (вызывал инкремент у редакса)

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


                              1. syncro
                                11.03.2019 15:51

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


                                1. nuit
                                  11.03.2019 16:12

                                  > и задачи биндинга и рендеринга можно решить без архитектурно-алгоритмических ухищрений

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


                                  1. syncro
                                    11.03.2019 16:22

                                    когда есть геттеры/сеттеры и прокси, зачем вам дифф алгоритм? изменять данные и связи всегда дороже чем создавать новую копию на основе старых и этого изменения, а у компонентов и не должно быть состояния, которое нельзя потерять вместе с компонентом


                                    1. nuit
                                      11.03.2019 16:29

                                      Спортируйте эти ~60 строчек кода[1] написаные с использованием lit-html на ваниллу и все вопросы отпадут.

                                      1. github.com/localvoid/uibench-lit-html/blob/0fab2ca944c5dc585bc5c595ff43d7a0f0fc289a/src/main.js


                                      1. syncro
                                        11.03.2019 16:38

                                        да этот пример просто расширить скорее всего не выйдет, а вот на хорошо структурированный код angular-material/cdk я легко менял под свои задачи наследуя, расширяя и переопределяя классы компонентов, шаблоны, не переписывая весь грид с нуля, не копипастя и даже не особенно вникая во внутреннее устройство. В этом главная задача технологий — быть расширяемыми и доступными для участия в доработке и развитии другими людьми желательно не сильно завися от моды.


                                        1. nuit
                                          11.03.2019 16:46

                                          Причём тут расширить? Это простой пример когда приходит снэпшот данных и нужно обновить DOM структуру, не теряя внутреннее состояние. ~60 строчек кода на любой современной библиотеке, вот к примеру тоже самое на React[1]. Реализуйте тоже самое поведение без использования библиотек вроде lit-html, это ведь так легко «когда есть геттеры/сеттеры и прокси».

                                          1. github.com/localvoid/uibench-react/blob/master/js/fc.jsx


                                          1. syncro
                                            11.03.2019 18:37

                                            а вы уверенны, что это внутреннее состояние компонентам необходимо? мне кажется это всеравно что пытаться кастомные элементы постоянно себя целиком перерендеривать с диффом как в рякте


                                            1. nuit
                                              11.03.2019 18:44

                                              > а вы уверенны, что это внутреннее состояние компонентам необходимо?

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


                                              1. syncro
                                                11.03.2019 18:51

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


                                      1. syncro
                                        11.03.2019 16:41

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


                  1. i360u
                    11.03.2019 14:02

                    habr.com/en/post/443032/#comment_19864502 — вот для начала можно сравнить с тем, что привел автор статьи.


        1. justboris Автор
          11.03.2019 12:01

          Во вторых: сделайте, наконец, корректный тест. Отобразите на странице 1000 компонентов, обязательно с одинаковой структурой, созданных с помощью LitElement, React и на чистых компонентах.

          https://web-components-eesuvphctg.now.sh


          Участвуют vanilla.js, shadow-dom и preact. Vanilla и preact держатся на одном уровне, shadow dom остает. Исходники


          1. i360u
            11.03.2019 12:27

            Ну в вашем тесте снова нет ни одного веб-компонента… Зачем вы раз за разом это повторяете? Вы добавляете Shadow DOM в параграф!!!, и используете элементы списка которые могут вызывать сайд-эффекты, поскольку имеют свои стили по умолчанию в браузере. Зачем? Вы добавляете дополнительный элемент со стилями в Shadow DOM а это уже никак нельзя считать эквивалентом по структуре. В одном случае вы сначала создаете элемент, затем для него вызываете innerHTML, в другом размещаете все целиком в шаблоне. Это по вашему корректный тест? Можно просто сделать что-то типа

            <div><span>First Name</span> <span>Second Name</span></div>

            и завернуть это в компоненты по стандарту?


            1. i360u
              11.03.2019 12:46

              Еще упустил, в случае с preact у вас все элементы рендерятся просто внутри шаблона ОДНОГО компонента по сути… Это честно по вашему?


              1. justboris Автор
                11.03.2019 22:22

                Я пишу код так, как я бы его писал в реальном проекте. Если есть похожий паттерн на веб-компонентах, давайте его тоже использовать.


          1. i360u
            11.03.2019 13:34

            Вот пример на скорую руку:

            <!DOCTYPE html>
            <html lang="en">
            
            <head>
              <meta charset="UTF-8">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <meta http-equiv="X-UA-Compatible" content="ie=edge">
              <title>WC Test</title>
            </head>
            <script type="module">
            class TestElement extends HTMLElement {
              constructor() {
                super();
                this.name = 'John';
                this.secondName = 'Snow';
                this.attachShadow({
                  mode: 'open',
                });
                this.shadowRoot.innerHTML = `
                  <style>
                    :host {
                      display: block;
                      color: red;
                    }
                  </style>
                  <span>${this.name}</span> <span>${this.secondName}</span>
                `;
              }
            }
            window.customElements.define('test-element', TestElement);
            </script>
            
            <body>
              <script type="module">
                let startTime = window.performance.now();
                let elemetsTotal = 1000;
            
                for (let i = 0; i < elemetsTotal; i++) {
                  let tEl = document.createElement('test-element');
                  document.body.appendChild(tEl);
                }
            
                window.requestAnimationFrame(() => {
                  console.log('Elements total: ' + elemetsTotal + '; Rendering time: ' + (window.performance.now() - startTime) / 1000 + 's');
                });
              </script>
            </body>
            
            </html>


            1. justboris Автор
              11.03.2019 22:10

              Запустил ваше демо, получил рендеринг за 0.307s.

              Собрал свое демо на preact, получил 0.108s

              Код примера
              <!DOCTYPE html>
              <html lang="en">
                <head>
                  <meta charset="UTF-8" />
                  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
                  <meta http-equiv="X-UA-Compatible" content="ie=edge" />
                  <title>Preact Test</title>
                  <script src="https://unpkg.com/htm@2.1.1/preact/standalone.js"></script>
                </head>
                <style>
                  .element {
                    display: block;
                    color: red;
                  }
                </style>
                <script>
                  const { html, Component } = htmPreact;
                  class TestElement extends Component {
                    render() {
                      return html`
                        <span class="element">
                          <span>${this.props.name}</span> <span>${this.props.secondName}</span>
                        </span>
                      `;
                    }
                  }
              
                  function App({ elementsTotal }) {
                    return html`
                      <div>
                        ${Array.from(
                          { length: elementsTotal },
                          (_, index) =>
                            html`
                              <${TestElement}
                                key=${index}
                                name=${"John"}
                                secondName=${"Snow"}
                              />
                            `
                        )}
                      </div>
                    `;
                  }
                </script>
                <body>
                  <script type="module">
                    let startTime = window.performance.now();
                    let elementsTotal = 1000;
              
                    htmPreact.render(htmPreact.h(App, { elementsTotal }), document.body);
              
                    window.requestAnimationFrame(() => {
                      console.log(
                        "Elements total: " +
                          elementsTotal +
                          "; Rendering time: " +
                          (window.performance.now() - startTime) / 1000 +
                          "s"
                      );
                    });
                  </script>
                </body>
              </html>
              



              1. syncro
                12.03.2019 06:33

                этот тест не учитывает особенность реактоподобных фреймворков перерендеривать компоненты зря, например когда я открываю фейсбук у меня начинает расти потребление памяти до гигабайтов и паедаться до 5 % core i7 даже при бездействии


                1. PaulMaly
                  12.03.2019 18:09

                  Память кушают только фреймворки основанные на vdom, потому что они строят деревья в памяти. Компонент React, кроме этого, пытается быть чистой функцией от стейта, что тоже приводит к лишним действиям, если вручную не оптимизировать эти процессы. Тот же Svelte, у которого нет vdom, не имеет таких изъянов в потреблении памяти, хотя по вашей логике, это тоже реактоподобный фреймворк. Можете сами убедиться:



                  Svelte тут староват конечно. В 3-й версии были сделаны многие улучшения, но в целом сути это не меняет.


            1. PaulMaly
              12.03.2019 17:52
              +1

              Для интереса взял ваш код и код justboris для Preact, а также написал и собрал тоже самое на SvelteJS и запустил все на своей машине.

              Реализация на Svelte 3
              # Компонент Test
              <div>
                <span>{name}</span> <span>{secondName}</span>
              </div>
              
              <script>
                let name = 'John', secondName = 'Snow';
              </script>
              
              <style>
                div { display: block; color: red; }
              </style>
              

              # Компонент App
              {#each Array.apply(null, { length }) as _}
              <Test />
              {/each}
              
              <script>
                import Test from './Test.svelte';
                export let length = 0;
              </script>
              

              # main.js
              import App from './App.svelte';
              
              const startTime = window.performance.now(),
                length = 1000;
              
              const app = new App({
                target: document.body,
                props: { length }
              });
              
              window.requestAnimationFrame(() => {
                console.log(`
                  Elements total: ${length}; 
                  Rendering time: ${(window.performance.now() - startTime) / 1000}s
                `);
              });
              
              export default app;
              


              1. syncro
                13.03.2019 05:01

                вы ведь используете там преобразование строки (да еще и с интерполяцией) с разметкой в dom, а не нейтив темплейты, т.е. замеряете не то. Кроме, того вы это запускаете в эдже? Зачем там мета совместимости?


                1. PaulMaly
                  13.03.2019 10:50

                  вы ведь используете там преобразование строки (да еще и с интерполяцией) с разметкой в dom, а не нейтив темплейты, т.е. замеряете не то.

                  Я никаких преобразований строк не делаю. Да и непонятно, почему я должен использовать найтив темплейты? И при этом с какого-то перепугу замеряю не то.

                  Все просто, я взял пример от уважаемого i360u на веб-компонентах и пример от уважаемого justboris на Preact. Пример был придуман не мной, поэтому я действовал в предложенных обстоятельствах и просто реализовал подобную вещь на Svelte. Далее я запустил все 3 примера в абсолютно равных условиях, равное количество раз и опубликовал результаты.

                  Если у вас есть более «правильный» пример для замеров, то можем и его обсудить.

                  Кроме, того вы это запускаете в эдже?

                  В моем комментарии достаточно точно написано на чем я запускаю:
                  Macbook Pro 2015 года. Последний Хром.

                  Зачем там мета совместимости?

                  О чем вы вообще?


              1. i360u
                13.03.2019 11:59
                -1

                Я уже писал автору статьи, что его тест некорректен от слова совсем, напишу и вам: в одном случае создаются компоненты, в другом — заполняется шаблон одного компонента/контейнера. То есть аналогичный код на WC будет выглядеть как-то так:

                    class FilledElement extends HTMLElement {
                      constructor() {
                        super();
                        this.name = 'John';
                        this.secondName = 'Snow';
                        this.attachShadow({
                          mode: 'open',
                        });
                        let html = /*html*/ `
                          <style>
                            :host {
                              display: block;
                              color: red;
                            }
                          </style>
                        `;
                        for (let i = 0; i < 1000; i++) {
                          html += `<div>${this.name}</span> <span>${this.secondName}</div>`;
                        }     
                        this.shadowRoot.innerHTML = html;
                      }
                    }
                    window.customElements.define('filled-element', FilledElement);
                

                Сравните теперь скорость (у меня где-то 14ms). Не стоит раз за разом сравнивать теплое с мягким, если хотите сравнить скорость рендера тысячи переиспользуемых компонентов с собственными свойствами — то и сравнивайте именно их. Речь вообще изначально шла о том, что некорректно скорость веб-компонентов замерять на LitElement, и я написал почему. Хотите адекватный тест? Сравните сравнимое: чистые компоненты, LitElement, React и Vue.


                1. PaulMaly
                  13.03.2019 12:17

                  Я уже писал автору статьи, что его тест некорректен от слова совсем, напишу и вам: в одном случае создаются компоненты, в другом — заполняется шаблон одного компонента/контейнера.

                  Во-первых, это чисто ваш тест, я лишь портировал его на Svelte. Во-вторых, у меня 2 компонента: Test.svelte и App.svelte. Перебор массива происходит вне компонента Test.svelte, то результата работы скрипта — это 1000 компонентов Test на странице.

                  То есть аналогичный код на WC будет выглядеть как-то так:

                  Это не так. Вы засунули цикл в один компонент и пробежались массивом внутри него. То есть результат работы вашего скрипта — это один компонент с 1000-й div-элементов внутри. По вашему это аналогично?

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


                  Да, именно это я и делаю. Еще раз взгляните на код моей реализации, раз вы поленились открыть спойлер в комментарии выше:

                  # Файл Test.svelte — внимание, отдельный компонент!
                  <div>
                    <span>{name}</span> <span>{secondName}</span>
                  </div>
                  
                  <script>
                    let name = 'John', secondName = 'Snow';
                  </script>
                  
                  <style>
                    div { display: block; color: red; }
                  </style>
                  

                  # Файл App.svelte — внимание, отдельный компонент!
                  {#each Array.apply(null, { length }) as _}
                  <Test /> <!-- вот тут мы в цикле создаем length-компонентов Test -->
                  {/each}
                  
                  <script>
                    import Test from './Test.svelte';
                    export let length = 0;
                  </script>
                  

                  # Файл main.js — внимание, отдельный файл!
                  import App from './App.svelte';
                  
                  const startTime = window.performance.now(),
                    length = 1000;
                  
                  const app = new App({
                    target: document.body,
                    props: { length }
                  });
                  
                  window.requestAnimationFrame(() => {
                    console.log(`
                      Elements total: ${length}; 
                      Rendering time: ${(window.performance.now() - startTime) / 1000}s
                    `);
                  });
                  
                  export default app;
                  


                  Более того, в вашем примере всего 1 компонент (TestElement), а в моем примере компонента 2 (Test и App), что по-идее должно быть даже менее производительным. Код файла main.js практически соответствует вашему второму script тегу, кроме того, что создание 1000 компонентов Test происходит не внутри обычно js-цикла, а с помощью инструментов доступных в Svelte. Собственно именно так такой код и пишется и я не вижу причин, почему это не соответствует вашему примеру.

                  Эти компоненты не только переиспользуемые, они до кучи полностью инкапсулированные, включая стили (именно поэтому я спокойно юзаю просто имя тега div для стилей и не боюсь что все теги div станут красными), и даже разбиты на отдельные файлы. Компонент Test понятия не имеет кто и сколько раз нарисует его на странице.

                  Хотите адекватный тест? Сравните сравнимое: чистые компоненты, LitElement, React и Vue.

                  Не очень понял, почему эти 4 варианта сравнимы, а варианты на Preact и Svelte нет?


                  1. i360u
                    13.03.2019 12:31

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


                    1. PaulMaly
                      13.03.2019 12:40

                      Чтож, вот вам еще пример. Тут что по-вашему некорректно и замеряется неверно?


                      1. i360u
                        13.03.2019 12:59

                        то, что вы за стартовое принимаете время после компиляции. То есть, по сути, выкидываете из теста всю механику работы с шаблоном. Шедеврально.


                        1. PaulMaly
                          13.03.2019 13:34

                          И что? Так работает Svelte. Шаблоны существуют только до момента сборки, а дальше все это дело превращается в обычные вызовы DOM API.

                          Более того, сравнение со Svelte даже более корректное, чем с Preac/React и любым другим VDOM решением, именно потому что Svelte в рантайме не использует все эти тяжелые абстракции, так же как и ваш ванильный код.

                          Ведь получается, что у меня на выходе js класс и вызовы DOM API и у вас тоже js класс и вызовы DOM API. И при этом мой код работает быстрее, почему? Ответ очевиден — из-за веб-компонентов, которые вы используете, а я нет.


                          1. i360u
                            13.03.2019 14:09

                            А то, что с таким-же успехом можно сравнивать рендеринг компонентов со статичным html-шаблоном (или результатом работы любого PHP-шаблонизатора). То, что вы не учитываете инициализацию шаблона с добавлением в него начальных данных, вы просто рендерите готовый html-код. Да, именно так работает Svetle, и именно поэтому его некорректно сравнивать с фреймворками, которые я привожу в пример. В этом подходе есть как плюсы так и свои минусы, обсуждение которых выходит за рамки текущей дискуссии. Я никак не могу понять, вы больше надо мной издеваетесь или над здравым смыслом? Может вам перечитать ветку сначала, чтобы понять, что тащить сюда Svetle для сравнения, не было никакого смысла? Речь шла о разнице между скоростью нативного парсинга HTML и первичного парсинга в JS-рантайме, реализованного в LitElement. Причем тут Svetle? Может он поддерживает инкапсуляцию стилей? Может компоненты на Svetle я могу использовать в проекте на React или Angular? Может он не требует никаких собственных зависимостей и поддерживает нативный синтаксис?


                1. justboris Автор
                  13.03.2019 12:18

                  Понятно, вот здесь возникли разночтения. Изначальная идея теста была в том, что мы создаем переиспользуемый веб-компонент (что-то вроде Card из material design) и рендерим их в списке (назовем его CardsList). В случае веб-компонентов Card и CardsList сливаются вместе ради производительности. Либо удобство разработки либо скорость, надо выбрать что-то одно.


                  В случае Preact/Svelte/другого фреймворка накладные расходы на создание переиспользуемого компонента меньше, поэтому выйдет и удобно, и производительно.


                  1. i360u
                    13.03.2019 12:35
                    -1

                    Ничего никуда не сливается в случае, если вы хотите переиспользовать Card.

                    В случае Preact/Svelte/другого фреймворка накладные расходы на создание переиспользуемого компонента меньше, поэтому выйдет и удобно, и производительно.

                    с чего такой вывод?


                    1. justboris Автор
                      13.03.2019 12:43

                      А чем рендеринг Card в цикле будет отличаться от рендеринга TestElement?


                1. PaulMaly
                  13.03.2019 12:35

                  Решил переписать свой пример, так чтобы вам было понятнее, что это одно и тоже:

                  # Файл Test.svelte — Ваш аналог TestElement

                  <div>
                    <span>{name}</span> <span>{secondName}</span>
                  </div>
                  
                  <script>
                    let name = 'John', secondName = 'Snow';
                  </script>
                  
                  <style>
                    div { display: block; color: red; }
                  </style>
                  


                  # Файл main.js — Ваш аналог второго тега script
                  import Test from './Test.svelte';
                  
                  const startTime = window.performance.now(),
                    length = 1000;
                  
                  for (let i = 0; i < length; i++) {
                    new Test({ target: document.body });
                  }
                  
                  window.requestAnimationFrame(() => {
                    console.log(`
                      Elements total: ${length}; 
                      Rendering time: ${(window.performance.now() - startTime) / 1000}s
                    `);
                  });
                  

                  Отличия лишь в том, что чтобы поместить компонент Test на страницу я вызываю конструктор компонента, а вы его создаете с помощью createElement. Внутри конструктора делается все тот же target.appendChid()

                  Ну и что вы думаете? Вот результаты:

                  # Svelte
                  Rendering time: 0.12290499999653548s
                  Rendering time: 0.11193000001367182s
                  Rendering time: 0.10559000005014241s
                  Rendering time: 0.12220999994315207s
                  Rendering time: 0.10138999996706843s

                  Как и ожидалось стало чуть быстрее, потому что исчез дополнительный компонент App.svelte, но не принципиально, потому что он был слишком мал. Кроме того, размер доставляемого js кода также уменьшился до 2.8Kb.

                  Так что, спасибо вам за замечание, теперь все еще очевиднее стало.


                  1. i360u
                    13.03.2019 12:38

                    ох… не подскажете что это такое у вас с расширением *.svelte? Как оно в браузер попало? А еще не подскажете, почему этот фреймворк называют «магически изчезающим»?


                    1. PaulMaly
                      13.03.2019 12:48

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

                      не подскажете что это такое у вас с расширением *.svelte?

                      .svelte — это формат Single File Components специфичный для фреймворка SvelteJS, который дает возможность удобно и декларативно описывать переиспользуемые компоненты (включая стили). Примерно такой же подход используется в таких фреймворках как Vue, Ractive, Riot. Даже Angular и React стараются все инкапсулировать в один файл, который удобно переиспользовать в разных проектах.

                      Как оно в браузер попало? А еще не подскажете, почему этот фреймворк называют «магически изчезающим»?

                      Особенность фреймворка Svelte в том, что по-факту это компилятор SFC в ванильные классы без зависимостей. В итоге в браузер попадает просто js код.

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


                      1. i360u
                        13.03.2019 12:51

                        Вы бы хоть для приличия изучили то, о чем вам другие люди говорят.

                        боже, это был сарказм. О должен был вам намекнуть почему сравнение некорректно. Но вижу это бесполезно.


                        1. PaulMaly
                          13.03.2019 13:42

                          боже, это был сарказм.

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

                          Сравнение более чем корректно, по причинам описанным тут.

                          Хорошо, давайте тогда забудем о Svelte. Ведь он «исчезает» в рантайме. У меня есть просто js файл компонента (не важно откуда я его взял, написал сам или мне его Svelte сгенерил), который экспортирует JS класс, а внутри делает вызовы в DOM API:

                          export default class Test {
                             constructor({ target }) {
                                 const div = document.createElement('div');
                                 
                                 /* другие вызовы DOM API */
                          
                                 target.appendChild(div);
                             }
                          }
                          


                          И дальше я его использую так:

                          for (let i = 0; i < 1000; i++) {
                            new Test({ target: document.body });
                          }
                          


                          Чем это принципиально отличается от вашего класса:

                          class TestElement extends HTMLElement {
                            constructor() {
                              super();
                              this.name = 'John';
                              this.secondName = 'Snow';
                              this.attachShadow({
                                mode: 'open',
                              });
                              this.shadowRoot.innerHTML = `
                                <style>
                                  :host {
                                    display: block;
                                    color: red;
                                  }
                                </style>
                                <span>${this.name}</span> <span>${this.secondName}</span>
                              `;
                            }
                          }
                          


                          И вашего использования:

                          for (let i = 0; i < 1000; i++) {
                            let tEl = document.createElement('test-element');
                            document.body.appendChild(tEl);
                          }
                          


                          Кроме как тем, что вы ипользуете веб-компонента, а я нет?


    1. PaulMaly
      11.03.2019 10:09

      С веб-компонентами нет никаких особых проблем ни с контролем зависимостей (реестр компонентов дает вам всю необходимую информацию о том, что вы регистрируете в рантайме в любое время)

      Ну да, а когда 15+ лет назад все складывали в window тоже можно было сказать, что есть единый реестр и можно в любой момент получить информацию. Только вот что-то практика показала, что любой глобальный объект или реестр ничего хорошего не несет.

      вы можете производить инъекции максимально эффективно через старый добрый DOM API.

      Старый да, но добрый ли? И для кого это и когда DOM API был добрым? По мне так это дикая, плохо стандартизированная до сих пор, жутко императивная и не удобная штука. Ни разу не видел, чтобы на сколь-либо крупном проекте использование чистого DOM API несло добро.

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

      Вот тут сложно не согласиться.


      1. i360u
        11.03.2019 10:56
        +1

        Ну да, а когда 15+ лет назад все складывали в window тоже можно было сказать, что есть единый реестр и можно в любой момент получить информацию. Только вот что-то практика показала, что любой глобальный объект или реестр ничего хорошего не несет.


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

        Старый да, но добрый ли? И для кого это и когда DOM API был добрым? По мне так это дикая, плохо стандартизированная до сих пор, жутко императивная и не удобная штука. Ни разу не видел, чтобы на сколь-либо крупном проекте использование чистого DOM API несло добро.


        В всех UI-фреймворках используется DOM API. Это какбэ единственный способ взаимодействия с DOM, доступный из js-рантайма. Или это все недостаточно крупные проекты? Я правильно понимаю, innerHTML, textContent и селекторы — это ужасно сложно и нестандартизированно?


        1. PaulMaly
          11.03.2019 12:54

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

          Чем глобальный скоуп кастом-элементов так сильно отличается от глобального скоупа в виде window? Разве он как-то исключает коллизию имен? Разве если я попробую зарегистрировать компонент с уже существующем именем он не даст мне это сделать, перезаписав существующий, или хоть как-то сообщит об этом? Может быть там внутри есть какой-то поиск, который позволил бы мне в произвольном месте узнать имя нужного мне компонента, если, к примеру, имена динамические? По большому счету это ровно такая же помойка с ровно теми же проблемами.

          Я правильно понимаю, innerHTML, textContent и селекторы — это ужасно сложно и нестандартизированно?

          Тем, кто работает с этими фреймвоками в 99% случаев не требуется взаимодействовать с DOM API никаким образом. Лично мне, как разработчику фронтенда, а не разработчику фреймворка, хочется работать с гараздо более высокоуровневым апи, чем DOM API. Грязную работу я вполне готов делегировать фреймворку.


          1. i360u
            11.03.2019 13:09

            Разве если я попробую зарегистрировать компонент с уже существующем именем он не даст мне это сделать, перезаписав существующий, или хоть как-то сообщит об этом? Может быть там внутри есть какой-то поиск, который позволил бы мне в произвольном месте узнать имя нужного мне компонента, если, к примеру, имена динамические?

            Именно. Иногда стоит глянуть на доки прежде чем комментировать.
            Тем, кто работает с этими фреймвоками в 99% случаев не требуется взаимодействовать с DOM API никаким образом.

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


            1. PaulMaly
              11.03.2019 13:26

              Именно. Иногда стоит глянуть на доки прежде чем комментировать.

              Что именно? В первым пунктом сплоховал, да, выдаст исключение. Недавно смотрел доклад, там походу докладчик сам спутал. Но это по большому счету ничего не меняет. Только то, что это не произойдет в тихую. Используя window мы тоже всегда проверяли наличие ключа и можно было кинуть исключение, но проблемы при этом все еще оставались.

              Еще неплохо следить за контекстом того, на что отвечаете. Я писал о подходе реализованном в LitElement, если я не ошибаюсь.

              Нет, вы писали что мол в веб-компонентах никаких прослоек не нужно, можно все через DOM API. Вопрос, почему именно в веб-компонентах не нужно, а в фреймворках нужно? В вашем высказывании явно указано, что когда мы пишем на веб-компонентах, тогда лучше юзать DOM API. Я и написал, что я как разработчик не хочу использовать низкоуровневые апи в задачах, связанных с разработкой продуктов. Что касается Lit-Element, полагаю что они использовали lit-html потому что это их же разработка, внутри которой DOM API используется напрямую. Зачем им еще раз писать тоже самое в lit-element не очень понятно.

              Тем не менее, если фронтендер не владеет основами работы с DOM (а в большинстве случаев ничего сложнее основ и не требуется), на мой взгляд, ему не место в профессии.

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


              1. syncro
                11.03.2019 13:45
                +1

                Я и написал, что я как разработчик не хочу использовать низкоуровневые апи в задачах, связанных с разработкой продуктов. Что касается Lit-Element, полагаю что они использовали lit-html потому что это их же разработка, внутри которой DOM API используется напрямую. Зачем им еще раз писать тоже самое в lit-element не очень понятно.


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


              1. i360u
                11.03.2019 13:48

                Но это по большому счету ничего не меняет.

                Это все меняет. Вы не хотели коллизий? Вы их не получите. Вы хотите реализовать проверку? Пожалуйста. В чем пробле то? developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry
                Нет, вы писали что мол в веб-компонентах никаких прослоек не нужно, можно все через DOM API.

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


                1. PaulMaly
                  11.03.2019 17:52

                  В чем пробле то?

                  Проблема в том, что если один 3rd-party элемент `calendar-popup` юзает внутри кастомный элемент `date-select` и другой 3rd-party элемент `datetime-chooser` юзает внутри элемент с таким же именем `date-select`, то при установке этих двух элементов к себе в приложения я неизбежно получу исключение. Соответственно мне либо придется изучать внутреннюю структуру веб-компонентов, чтобы понять могут ли они конфликтовать, либо править в чужие компоненты. Ни то, ни другое меня не прельщает. По-моему это довольно очевидная проблема, которая автоматически порождается глобальным скоупом, даже таким «умным» как реестр.

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

                  Я спокоен, тем более что вы ведете диалог вполне корректно. Но мне интересно, ведь кастомные элементы — это чисто userland история. Кроме того, вы предлагаете не использовать готовые абстракции над манипуляциями в DOM, а делать прямые вызовы в DOM API исходя из специфики каждого конкретного компонента. Как же это сочетается с «не надо писать на голых компонентах» или «веб-компоненты — это основа для будущих фреймворков»?


                  1. syncro
                    11.03.2019 18:24

                    то при установке этих двух элементов к себе в приложения я неизбежно получу исключение.


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


                    1. PaulMaly
                      11.03.2019 22:14
                      +1

                      Все так, но вы видимо не поняли проблему. У меня вот есть приложение и я качаю с гитхаба 2 веб-компонента и вставляю их себе на страницу:

                      ....
                      <script src="./components/calendar-popup.js"></script>
                      <script src="./components/datetime-chooser.js"></script>
                      ....
                      <calendar-popup></calendar-popup>
                      ....
                      <datetime-chooser></datetime-chooser>
                      


                      Захожу посмотреть как они классно работают, а вместо этого вижу в консольке:

                      VM82:1 Uncaught DOMException: Failed to execute 'define' on 'CustomElementRegistry': this name has already been used with this registry


                      Как так, думаю я, имена то разные у меня везде. Но оказывается что Вася, который делал calendar-popup и Джордж, который делал datetime-chooser для внутренних нужд этих компонентов сделали ряд внутренних компонентов (ну там, чтобы логику разделить, код переиспользовать и все такое прочее), и назвали, а главное задефайнили эти компоненты одинаково:

                      // Вася код
                      class DateSelect extends HTMLElement {}
                      customElements.define('date-select', DateSelect);
                      
                      // Джордж код
                      class DtSelect extends HTMLElement {}
                      customElements.define('date-select', DtSelect);
                      


                      Чем в такой ситуации мне поможет это исключение или проверка? Тут мы конечно вспомним про всякие там соглашения и префиксы, только многолетняя практика показывает, что никакие соглашения не спасают от таких ситуаций.


                      1. syncro
                        12.03.2019 06:39

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

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


                        1. PaulMaly
                          12.03.2019 11:26
                          +1

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

                          Джордж задефайнил компонент date-select чтобы использовать его внутри компонента datetime-chooser, другого способа использовать веб-компонент не определено стандартом. Пользовательский код компонента datetime-chooser вообще не должен знать что внутри есть какой-то там date-select, потому что это означает вникать в детали реализации.

                          Сбутстрапить компоненты наинжектив им заодно зависимостей должен был некоторый общий код, какой-то аналог DI фреймворка

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

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

                          Во-вторых, вы утверждаете, что в Angular нельзя просто взять 3rd-party компонент и заиспользовать его, без ручного управления всему его зависимостями через DI? Вы уверены в этом? Потому что в моем примере речь именно об этом — компонент date-select не используется нигде кроме как внутри компонента datetime-chooser.

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

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

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


                          1. syncro
                            12.03.2019 11:36

                            Джордж задефайнил компонент date-select чтобы использовать его внутри компонента datetime-chooser, другого способа использовать веб-компонент не определено стандартом.


                            что-бы использовать одни элементы в других нет необходимости дефайнить их в строго определенном порядке, в этом крутизна веб-компонентов, что пока вы не задефайнили кастомный элемент браузер его просто пометил как неизвестный и пропустил

                            Во-первых, вы опять доказали, что сами по себе, веб-компоненты практически недееспособны, раз чтобы заюзать парочку компонентов нужно сверху крутить DI


                            крутить или не крутить решают разработчики ферймворка, вот вам не нравится — вы не крутите, а тот кто понимает зачем это и имеет опыт — прикрутит

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


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


                            1. PaulMaly
                              12.03.2019 11:58

                              что-бы использовать одни элементы в других нет необходимости дефайнить их в строго определенном порядке, в этом крутизна веб-компонентов, что пока вы не задефайнили кастомный элемент браузер его просто пометил как неизвестный и пропустил

                              Даже не знаю, что тут комментировать. Спасибо капитан, все так. Только какое отношение это имеет к описанной проблеме? Я разве писал вам, что если компонент не будет задефайнен его нельзя поместить в разметку?

                              так и где тут проблема?

                              вы дествительно не видите разницы между тем, чтобы просто взять и использовать 3rd-party компонент и тем, когда вам сперва нужно изучить все его зависимости, каким-то образом заинджектить/задефайнить их, чтобы имена не конфликтовали с абсолютно всеми компонентами вашего приложения. Нет?

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

                              зато с участием можно сделать как угодно: я знаю минимум 2 способа несложно сделать это самому, есть готовые библиотеки-инжекторы

                              зачем мне инжекторы, если для меня это должен быть черный ящик из одного компонента?


                              1. syncro
                                12.03.2019 12:33

                                не будет задефайнен его нельзя поместить в разметку?


                                вы писали что его надо дефайнить прямо сразу в том же жс выдав себя за боба;)

                                не будет задефайнен его нельзя поместить в разметку?


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

                                зачем мне инжекторы, если для меня это должен быть черный ящик из одного компонента?

                                бывают задачи шеринга некоторого кода или состояния между компонентами, можно пытаться ограничится одними эвентами, но это будет несколько экстенсивно


                                1. PaulMaly
                                  12.03.2019 13:21

                                  вы писали что его надо дефайнить прямо сразу в том же жс выдав себя за боба;)

                                  Видимо вы не уловили логическую связь между 2мя предложениями. Если бы вы прочитали следующее предложение, то поняли бы, почему Джордж вынужден дефайнить компонент внутри:

                                  Пользовательский код компонента datetime-chooser вообще не должен знать что внутри есть какой-то там date-select, потому что это означает вникать в детали реализации.


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

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

                                  Не знаю, что конкретно вы подразумеваете под статической сборкой, но уже давно есть динамическая подгрузка компонентов и code-splitting. Которые кстати также основаны es6 стандарте модулей. Подмену компонентов в рантайме также можно легко реализовать в любом фреймворке. Всех этих возможностей хватает, чтобы реализовать любой функционал, а вот полезность возможности отложенного дефайна веб-компонентов до конца не ясна. Думается мне это сделано все из-за тех же модулей, но тогда это никакая не суперспособность, используя import() и вебпак мы получаем модули даже в тех браузерах, где нет нативной поддержки.

                                  Все понимают, что любой вася может собрать хеловорлд СПА на рякте, вы сделайте что-бы у вас безконфликтно на экране работало и взаимодейтсвовало 5 инстансов рякта и столкнетесь со всеми теми же проблемами и решениями.

                                  Стесняюсь спросить, а зачем мне 5 инстансов реакта на странице? Мне трафик девать некуда или что?

                                  бывают задачи шеринга некоторого кода или состояния между компонентами, можно пытаться ограничится одними эвентами, но это будет несколько экстенсивно

                                  Да, «ёшкин код». Мы с вами разве общаемся на тему шаринга кода и состояния? Это вообще отдельная большая тема и отдельная боль при использовании веб-компонентов. Причем тут ивенты тоже?

                                  Еще раз — если я взял сложный 3rd-party компонент и хочу его поместить в свое приложение:

                                  1. Я НЕ должен вникать в его структуру и хоть как-то инжектить/дефайнить его внутренние компоненты и другие детали реализации. Все это нарушение инкапсуляции, да и просто не удобно.
                                  2. Я НЕ должен получить «протекающие» из этого компонента наружу внутренние компоненты. То есть присловутый date-select вообще не должен хоть как-то появиться во внешнем по отношению к родительскому компоненту коде.
                                  3. Я должен иметь возможность просто взять и подключить его в свое приложение, без страха получить эксепшен.
                                  4. Для того, чтобы считать систему регистрации веб-компонентов хоть сколько то рабочим решением, я НЕ должен быть вынужден обмазаться с ног до головы DI/инжекторами/депенденситрекерами/other bullshit, иначе это не готовая к использованию система, а просто костыль.



                                  1. syncro
                                    12.03.2019 13:31

                                    то поняли бы, почему Джордж вынужден дефайнить компонент внутри


                                    он не должен этого делать ни в каком случая, в этом сила веб-компонентов, что вы можете дефайнить компонеты потом в том числе и для динамического рендеринга

                                    Подмену компонентов в рантайме также можно легко реализовать в любом фреймворке.


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

                                    Еще раз — если я взял сложный 3rd-party компонент и хочу его поместить в свое приложение


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


                                    1. PaulMaly
                                      12.03.2019 13:46

                                      он не должен этого делать ни в каком случая, в этом сила веб-компонентов, что вы можете дефайнить компонеты потом в том числе и для динамического рендеринга

                                      Да, «YOбушки-воробушки». Хорошо, кто тогда должен задефайнить компонент date-select, чтобы я мог использовать его родительский компонент datetime-chooser? Напишите пожалуйста в коде. Хватит голословных утверждений.

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

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

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

                                      Какие еще тагнеймы? Я про компоненты фреймворков тут писал, а не про WC. То что этого сделать на WC нельзя просто потому что они такие, итак понятно.

                                      вы лично может не хотите, а кому-то без этого не решить задачи.

                                      Может хватит «воды»? Давайте конкретный пример, где без этого не обойтись, иначе это не аргумент.

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

                                      Конкретнее, что и чем ограничивает и как с этим борятся веб-компоненты?

                                      Пока все что вы пишете, это просто набор букаф. Судя по общему треду, я не один сделал такой вывод по отношению к вам.


          1. syncro
            11.03.2019 13:23

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


  1. syncro
    11.03.2019 06:46

    Мы явным образом импортируем компонент Button. Если удалить импорт, то у нас произойдет ошибка в рендеринге. С веб-компонентами ситуация другая, мы просто рендерим html-тэги, а они магическим образом оживают.


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


    1. justboris Автор
      11.03.2019 11:56

      Слабая связность хороша, когда она делается опционально, по желанию. То есть я мог бы при желании параметризовать компонент Icon для кнопки, чтобы желающие могли его подменить.

      Но бывает нужна и жесткая связь. Есть кнопка, и она собрана и протестирована с вполне конкретной иконкой и ее замена не гарантирует работоспособность. (например, под иконку зарезервировано 16х16px, а новая иконка будет 20х20px). Обязательная слабая связность заставляет разработчиков раскрывать потребителям свои детали реализации, что ухудшает обратную совместимость компонента.


      1. syncro
        11.03.2019 12:18

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


  1. PaulMaly
    11.03.2019 09:54

    Мне почему-то кажется что в lit-element не встроенного VDOM. Там под капотом lit-html, который работает иначе.


  1. superyateam
    11.03.2019 11:44

    сколько лет прошло, а до сих пор читаю статьи из серии:

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


    1. syncro
      11.03.2019 12:23

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


  1. Synoptic
    11.03.2019 12:38

    так например многие использовали разные «шаблонизаторы» надстраивающие или подменяющие native templates, когда в них нет особой необходимости

    syncro, они и сейчас их надстраивают, а разве это плохо? Альтернатива — голый HTML. Всяко лучше, чем руками писать кучу кода для очередного биндинга или евент листенера.


    1. syncro
      11.03.2019 12:47
      +1

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


      1. Synoptic
        11.03.2019 12:54

        Я имею в виду что-то типа .prop="${myProp}" или @click="${onClick}" — не пойму чем это вредит и как можно обойтись без этих конструкций, не пиша при этом кучу кода или не велосипедя.


        1. syncro
          11.03.2019 13:11

          обычно все эти аттрибуты добавляются к исходной верстке в исходно обычный хтмл (если фреймворк требует полного переписывания это отдельная боль, чаще от работы с дизайном в таких системах просто отказываются), т.е. у вас в процессе получается 2 шаблона, макетный и фреймворковый. Каждый такой атрибут это потенциальный баг, т.к. велика вероятность, что разработчик-интегратор его пропустит, опечатается или что-то такое еще сделает случайно-произвольное. Кроме второго шаблона, вам все равно надо еще докодировать еще настоящей бизнес-логики, или доинтегрировать если это не первая итерация и тут количество потенциальных ошибок будет возрастать геометрически помноженное на проблемы коммуникации если этим занимаются разные узкие специалисты. В тоже время в промежуточном шаблоне нет необходимости, т.к. хтмл вместе с цсс селекторами сам по себе является прекрасным шаблонизатором, т.е. вам достаточно иметь некоторый обычный хтмл + цсс и js код производящий всю связывающую магию ориентируясь на свою конфигурацию и адресуя элементы по селекторам с появлением shadow dom даже снова можно вспомнить про более эффективные с точки зрения производительности айдишники, поиск внутри под-дерева не будет тормозить в любом случае, а прямой биндинг данных даст безусловный выйгрыш относительно разных хитрых придумок с дифом перерисовок или масштабированием операций изменения.


          1. Synoptic
            11.03.2019 13:23

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

            — Правильно ли понимаю, что магия, необходимая для замены `@click='${onClick}'` из моего примера будет выглядеть как метод, в котором обработчик вешается императивно? Обработчик обычно не один, вешаются они обычно при инициализации компонента, а значит это будет что-то типа метода `initListeners` вида

            initListeners() {
              this.shadowRoot.querySelector('#button-1').addEventListener(...)
              ...
              this.shadowRoot.querySelector('#button-N').addEventListener(...)
            }
            


            Если так, то я сталкивался с таким подходом в Backbone.js, он ведет к сложностям — начиная с того, что невозможно визуально сходу понять, какой обработчик к какому элементу относится — нужно глазами смотреть на шаблон, на id компонента, затем смотреть что на него вешается. Или я неверно понял? Покажете пример?

            — Про биндинги по сути та же история — в случае lit-html насколько я знаю там и так прямой биндинг на template part, или прямо на textContent. В случае с синтаксическим сахаром шаблонизации, глазами видно какой биндинг на каком элементе, + пользоваться возможностями IDE по быстрому переходу. В случае же, если у нас где-то лежит метод, в котором инициализируются биндинги вручную, получается тоже что я описал про евент-листенеры. Плохо читабельно и непонятно зачем.


            1. syncro
              11.03.2019 13:35

              вы можете использовать и onclick=«elementId.someMethod()», однако кроме биндинга эвентов шаблонизаторы и фреймворков привносят еще много своих констркуций (аттрибуты биндинга, анимации, конфигурации данные и т.п.), которыми разработчики активно пользуются (а как же не пользоваться если так проще) создавая систему из 2х шаблонизаторов, потому что сразу на коде фреймворка сделать макет выйдет равноценно полной реализации. Я не вижу большой порочности в походе бекбона, хотя ее можно было бы улучшить прямым биндингом, есть люди которые вообще фантизируют разработку искусственным интеллектом, а не только без работы над дизайном-макетом. Понятно, что можно просто использовать компоненты цсс фреймворка для многих случаев, но вы просто понижаете градус задач говоря «ах, раз этого нет значит оно и не нужно».


              1. Synoptic
                11.03.2019 13:47

                Ну я как раз про тонкий слой сахара, без фанатизма в духе Angular.

                вы можете использовать и onclick=«elementId.someMethod()»

                Насколько я помню, это не работает для Custom Events.

                потому что сразу на коде фреймворка сделать макет выйдет равноценно полной реализации

                Не согласен. Мы же говорим про веб-компоненты и фреймворки вокруг них. Эти фреймворки как раз и отличаются малым количеством постороннего синтаксиса — тот же Polymer, lit-element. И как раз на таких фреймворках сверстать макеты без их реализации намного проще чем учить условный JSX/Angular templates. Можно просто вставить верстку, а девелопер потом понадобавляет биндингов, циклов и условных конструкций как ему надо.

                Я не вижу большой порочности в походе бекбона,

                Порочность как минимум в том, что вы пишете id элемента в двух местах — на самом элементе и там, где вешаете event listener. А если вешаете несколько — то повторяете id еще несколько раз. Это очевидно увеличивает количество шаблонного кода -> может привести к ошибкам.

                Понятно, что можно просто использовать компоненты цсс фреймворка для многих случаев, но вы просто понижаете градус задач говоря «ах, раз этого нет значит оно и не нужно».

                Тут связь обсуждаемого и CSS-фреймворков я не уловил.


                1. syncro
                  11.03.2019 14:06

                  Насколько я помню, это не работает для Custom Events.

                  вообще, я был сторонником addEventListener и прочего биндинга-хендлинга сугубо в коде, на мой взгляд правильнее добавить проверки и бросить иключение или даже ворнинг если элемент не был найден селектором. Что до повторного упоминания айдишника, то при биндинге в шаблоне вы точно так же дважды минимум упоминаете имя метода-обработчика, так что тут как ни крути:) но наверное можно использовать особенность мапинга элементов по айди на глобальный (или может уже и стековый для компонента в теневом поддереве) скоуп
                  Тут связь обсуждаемого и CSS-фреймворков я не уловил.

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


                  1. Synoptic
                    11.03.2019 15:46

                    то при биндинге в шаблоне вы точно так же дважды минимум упоминаете имя метода-обработчика

                    Ну… это хотя бы позволяет использовать возможности IDE(cmd+click, find usages или method rename в Webstorm, напр.), в случае с id придется полагаться только на текстовый поиск.

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


                    1. syncro
                      11.03.2019 16:01

                      Ну… это хотя бы позволяет использовать возможности IDE


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

                      При следовании архитектуре веб-компонентов, все внутренние обработчики будут внутри класса-компонента, все внешние вы и так не узнаете если они «прокинуты через пропсы», хотя, на самом деле, в случае нативного кода все прямые бинды достаточно просто вычисляются из Web Developer Tools а непрямые через свойство path у эвента который вы поймаете дебагером, а вот если у вас модульное приложение на фреймворке со свой шиной и биндингом это не понятно как сделать, а если вы имели ввиду монолит это немного не тот класс решаемых задач.


                      1. Synoptic
                        11.03.2019 16:07

                        Еще раз — ваш первый комментарий был про шаблонизаторы фреймворков на основе веб-компонентов, а не про React или другие фреймворки. Вот у меня проект на lit-element, и там IDE позволяет переименовывать методы, искать вхождения, в общем в этом плане сильно проще и нагляднее чем руками селекторы писать и хардкодить ID.

                        Если фигачить руками квериселекторы backbone-style это «архитектура веб-компонентов», то такая архитектура не по мне.


                        1. syncro
                          11.03.2019 16:13

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


                          1. Synoptic
                            11.03.2019 16:25

                            т.к это нарушение принципа разделения ответственностей

                            [известная картинка с разными точками зрения на то, что такое компонент]
                            Понятно, что лично вам может быть говнокодить удобненько. Но я много и много раз видел вермешели смешивающего кода в который невозможно (противно) было развивать и поддерживать. Прикладные задачи всегда превращают такой код в помойки говнокодов.

                            Это хорошо, что скатились на личности достаточно быстро. По моему опыту, проекты, код которых которые можно назвать говнокодом чаще используют именно описанный вами подход, и основаны как раз на Backbone.js, jQuery.


                            1. syncro
                              11.03.2019 16:33
                              +1

                              >> [известная картинка с разными точками зрения на то, что такое компонент]

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

                              говнокодом чаще используют именно описанный вами подход


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


                              1. Synoptic
                                11.03.2019 16:49

                                т.е. вы считаете что .js файлы это подходящее место для верстки?

                                Мне больше нравился декларативный подход Polymer до версии 3, но HTML Imports приказали долго жить. Этот подход не имел ничего против расширения синтаксиса тогда, когда это необходима — по сути все ограничивалось как раз биндингами и евент листенерами, остальное реализовалось кастомными тегами.

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

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

                                Хотелось бы напомнить, что речь с моей стороны шла только о возможности добавлять биндинги и евент листенеры без синтаксического сахара в шаблонах. Я хотел понять какая есть альтернатива, вы объяснили, мне не понравилось — ну, бывает.


                                1. syncro
                                  11.03.2019 18:34

                                  чем по непонятным причинам стремиться к абсолютной синтаксической чистоте HTML, CSS и JS в пределах вью.


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

                                  Я хотел понять какая есть альтернатива, вы объяснили, мне не понравилось — ну, бывает.


                                  важно понимать, что альтернатива может существовать, ее конкретных реализаций в современных фреймворках я и правда не видел, но дело тут в том, что 90% современного фронтенда слепо увлечены анти-паттерновыми решениями (в популярных сейчас фреймворках) и бестолковыми идеями навроде таких, что веб-разработка больше не нужна и все заменит искусственный интеллект и конструкторы сайтов с готовыми виджетами или что у них есть какая-то такая важная оптимизационная идея-причина, которая отменяет инженерные практики


                                  1. Synoptic
                                    11.03.2019 18:40

                                    Но зачем в 2019 году нужен автопрефиксер? Тем более в приложении на базе веб компонентов? И зачем при использовании Shadow DOM и нормальной декомпозиции так уж нужны препроцессоры?

                                    Есть ли реальные юз кейсы, где видно пользу от предлагаемого вами решения? Пока мне видны только минусы.

                                    важно понимать, что альтернатива может существовать, ее конкретных реализаций в современных фреймворках я и правда не видел, но дело тут в том, что 90% современного фронтенда слепо увлечены анти-паттерновыми решениями (в популярных сейчас фреймворках) и бестолковыми идеями навроде таких, что веб-разработка больше не нужна и все заменит искусственный интеллект и конструкторы сайтов с готовыми виджетами или что у них есть какая-то такая важная оптимизационная идея-причина, которая отменяет инженерные практики

                                    Извините, в контексте дискуссии этот абзац ни о чем.


                                    1. syncro
                                      11.03.2019 19:02

                                      Есть ли реальные юз кейсы, где видно пользу от предлагаемого вами решения? Пока мне видны только минусы.


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


                                      1. Synoptic
                                        11.03.2019 19:09

                                        Много слов, мало смысла. Не понял, и не хочу больше продолжать беседу.


  1. hellbeast92
    11.03.2019 21:18

    1) На счет производительности стилей, в скорости инцилизации мы явно имеем проигрыш, а вот что насчет обновления? А я скажу что, в пару раз быстрее, так что shadowDOM позволяет нам очень быстро модифицировать стили в отличие от lite dom.
    2) На счет Tree-shaking и жесткой связанности с импортами: очень просто решается путем написания небольшой ф-и(лучше декоратора в случае ТС) которая одновременно регистрирует компонент в риесторе элементов и перегружает toString у класса, чтобы toString возращал тэг.В итоге мы получаем что-то вроде
    import { Button } from './button'


    render () {
    return `<${Button}></${Button}>`
    }

    3) При обновлении нескольких пропертей в том же LitElement как и во всех адекватных фраемворках изменения бачуются и вызывется один перерендр


    1. justboris Автор
      11.03.2019 22:34

      А я скажу что, в пару раз быстрее, так что shadowDOM позволяет нам очень быстро модифицировать стили в отличие от lite dom.

      Интересное предположение. А бенчмарк покажете?


      На счет Tree-shaking и жесткой связанности с импортами: очень просто решается

      Тут две проблемы


      1. Lit-html динамические значение в именах тегов не поддерживает: https://github.com/Polymer/lit-html/issues/78
      2. Даже если вы экспортируете компонент как константу, его все равно можно инстанцировать напрямую, без импорта. И по закону Мерфи это конечно же произойдет.

      При обновлении нескольких пропертей в том же LitElement как и во всех адекватных фраемворках

      Это хорошо, что во фреймворках есть решение. Но изначально евангелисты гугла и компания вещали нам, что в веб-компонентах все уже есть, что фреймворки поверх получатся маленькие и легкие. А в реальности оказалось, что нужно и с батчингом заморачиваться, и с умным обновлением DOM (см. lit-html) и так далее по тексту статьи.