Custom elements в бою +10


Добрый день!

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

В связи с этим, при наличии желания увеличить скорость разработки и обеспечить единообразие продуктов, было решено разработать общую компонентную базу. О том, как создавался ui kit, и о долгих боях с дизайнерами мы умолчим, а вот о реализации данной задачи я и хочу поговорить.
На фронте у нас демократия или даже анархия. Люди вольны использовать те решения, с которыми им удобно работать. На данный момент в бою есть проекты на AngularJS, Angular, React, Vanilla, и есть также проекты на Vue для внутреннего использования. Вот на этом моменте наш взор и обратился на web components.

Web Components


Давайте кратко осмотрим концепцию web components. В основе лежит концепция custom elements, которая позволяет расширять класс HTMLElement, создавая свои собственные html тэги, со скрытой от пользователя бизнес логикой. Звучит круто, выглядит приятно. Давайте посмотрим, что мы можем сделать. Здесь и далее исходный код приведен на typescript.

Чтобы создать custom element, нам нужно сделать следующее. Описать класс и зарегистрировать компонент.

export class NewCustomElement extends HTMLElement {
  constructor() {
    super();
    console.log('Here I am');
  }
}
if (!customElements.get('new-custom-element')) {
  /* Зарегистрируем компонент, если его еще нет */
  customElements.define('new-custom-element', NewCustomElement);
}

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

Еще custom elements дают нам несколько хуков для отслеживания жизни компонента.

export class NewCustomElement extends HTMLElement {
  constructor() {
    super();
    console.log('I am created');
  }
  
  /* Вызывается каждый раз, когда элемент вставляется в DOM, согласно лучшим практикам, такие операции, как первый рендер компонента, стоит делать именно на данном шаге */
  connectedCallback() {
    console.log('Now I am in Dom');
    this._render();
    this._addEventListeners();
  }

  /* Вызывается каждый раз, когда элемент удаляется из DOM, хорошее место, чтобы произвести уборку */
  disconnectedCallback() {
    console.log('I am removed now');
    this._removeEventListeners();
  }

  /* Так объявляется список отслеживаемых атрибутов */
  static get observedAttributes() {
    return ['date'];
  }

  /* Вызывается, когда изменен один из отслеживаемых атрибутов */
  attributeChangedCallback(attrName, oldVal, newVal) {
    switch (attrName) {
        case 'date': {
          /* Обрабатываем изменение атрибута, например перерендериваем соответствующую часть компонента */
          break;
        }
     }
  }
  
  /* Элемент перенесен в новый документ */
  adoptedCallback() {
    /* Не знаю, что с этим делать, поделитесь в комментариях своими предложениями */
  }
}

Также мы можем генерировать события в компонентах через метод dispatchEvent

export class NewCustomElement extends HTMLElement {
  //////
  _date: Date = new Date();
  set date(val: Date) {
    this._date = val;
    this.dispatchEvent(new CustomEvent('dateChanged', {
        bubbles: true,
        cancelable: false,
        detail: this._date
      }));
  }
  //////
}

Будущее наступило, говорили они, пишешь код один раз и используешь его везде, говорили они


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

Посмотрим, какие мы получили плюсы.

  • Reusable: мы получили действительно переиспользуемую библиотеку. На данный момент она работает в vanilla проекте, подключаясь как собранный Webpack bundle, и проекте angular 7, подключаясь исходниками typescript в AppModule
  • Понятное поведение: если следовать лучшим практикам, то мы получаем компоненты с понятным поведением, которые легко интегрируются в существующие фреймворки, например для angular, с помощью бананов в коробке, в нативных приложениях через атрибуты, или работу с property, отражающими атрибуты
  • Единый стиль: это некоторое повторение пункта о реюзабельности, но все же. Теперь на всех проектах используются единые строительные блоки для конструирования UI.
  • Честно, не могу больше придумать плюсов: расскажите, чем WebComponents помогли Вам.

Далее попробую описать вещи, которые мне скорее не понравились.

  • Трудозатраты: затраты на разработку компонент несравнимо выше, нежели разработка под фреймворк.
  • Именование: компоненты регаются глобально, поэтому и имена классов, и имена тэгов приходится префиксить. Учитывая, что у нас еще есть библиотеки компонент, реализованные под фреймворки, которые именовались как <company-component-name>, то вэб-компоненты пришлось префиксить дважды <company-wc-component-name>.
  • ShadowRoot: согласно лучшим практикам, рекомендуется использовать shadowRoot. Однако, это не очень удобно, так как не остается возможности повлиять на внешний вид компонента извне. А такая необходимость часто встречается.
  • Render: без фреймворков приходится забыть о data binding и реактивности (LitElement в помощь, но это еще одна зависимость).
  • Будущее не наступило: Чтобы сохранить поддержку пользователей на старом уровне (у нас это ie11 и все, что посвежее), приходится прикручивать полифилы, es5 — целевой стандарт, что создает дополнительные проблемы.
  • Сами полифилы: Чтобы завести все это добро под IE, пришлось немало помучиться, и принять несколько некрасивых решений, так как полифилы от webcomponent ломают что-то внутри ангуляра, вызывая переполнение call stack. В итоге пришлось полифилить полифилы, получив лишние зависимости.

Я не знаю, какой сделать из всего этого вывод. Если Microsoft-таки сделает браузер на базе chromium и прекратит поддержку IE и Edge — то да, станет проще дышать.

Есть одна странная польза: можно давать разработку чистых web components начинающим разработчикам — пускай посмотрят как оно, писать на JS без фреймворков. Один коллега долго не мог понять, почему изменение property в компоненте не отражалось сразу в DOM. Вот они — люди, выращенные на фреймворках. И я такой же.




К сожалению, не доступен сервер mySQL