- PVSM.RU - https://www.pvsm.ru -

Знакомство с lit-element и веб-компонентами на его основе

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

lit-element [1] — это обертка (базовый шаблон) для нативных веб-компонентов. Она реализует множество удобных методов, которых нет в спецификации. За счет своей близости к нативной реализации lit-element показывает очень хорошие результаты в различных benchmark [2] относительно других подходов (на 06.02.2019г).

Бонусы, которые я вижу от использования lit-element как базового класса веб-компонентов:

  1. Данная технология реализует уже вторую версию и «переболела детскими болезнями», свойственными только что появившимся инструментам.
  2. Сборка может осуществляться как polymer, так и webpack, typescript, rollup и т.д., это позволяет встроить lit-element в любой современный проект без каких-либо проблем.
  3. У lit-element очень удобная система работы с property в плане типизации, инициирования и конвертирования значений.
  4. lit-element реализует почти такую же логику, как у реакт, т.е. он предоставляет самый минимум — единый шаблон построения компонентов и его рендеринга и не ограничивает разработчика в выборе экосистемы и дополнительных библиотек.

Создадим простой веб-компонент на lit-element. Обратимся к документации. Нам необходимо следующее:

  1. Добавить в нашу сборку npm пакет с lit-element
    npm install --save lit-element
  2. Создать наш компонент.

Например, нам надо создать веб-компонент, инициализирующийся в теге my-component. Для этого создадим js файл my-component.js и определим его базовый шаблон:

// для импорта базового шаблона на основе lit-element
import { } from ''; 

// для создания логики самого компонента
class MyComponent { }    

// для регистрации компонента в браузере
customElements.define();

Первым делом импортируем наш базовый шаблон:

import { LitElement, html } from 'lit-element';
// LitElement - это базовый шаблон (обертка) для нативного веб-компонента
// html - функция lit-html, которая обрабатывает переданную ей строку, парсит 
// и вставляет полученный html в структуру документа

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

// прошу обратить внимание, в нативной реализации
// вместо LitElement мы бы использовали HTMLElement
class MyComponent extends LitElement {
  // жизненный цикл компонента LitElement гораздо богаче
  // и нам не обязательно вызывать constructor или connectedCallback
  // мы можем сразу указать что именно должен отрисовать наш компонент
  // прошу так же заметить, что по умолчанию к компоненту добавляется
  // shadowDOM с опцией {mode: 'open'}
  render() {
    return html`<p>Hello World!</p>`
  }
}

И последнее — зарегистрировать веб-компонент в браузере

customElements.define('my-component', MyComponent);

В итоге получаем следующее:

import { LitElement, html } from 'lit-element';

class MyComponent extends LitElement {
  render() {
    return html`<p>Hello World!</p>`
  }
} 

customElements.define('my-component', MyComponent);

Если исключить необходимость подключать my-component.js к html, то это все. Самый простой компонент готов.

Предлагаю не изобретать велосипед и взять готовую сборку lit-element-build-rollup. Следуем инструкции:

git clone https://github.com/PolymerLabs/lit-element-build-rollup.git
cd lit-element-build-rollup
npm install
npm run build
npm run start

После выполнения всех команд переходим на страницу в браузере http://localhost:5000/ [3].

Если взглянем в html, увидим, что перед закрывающим тегом находится webcomponents-loader.js [4]. Это набор полифиллов для веб-компонентов, и для кроссбраузерной работы веб-компонента желательно, чтобы был данный полифилл. Посмотрим на таблицу браузеров [5], реализующих все стандарты для работы веб-компонентов, там указано, что EDGE все еще не до конца реализует стандарты (я молчу про IE11, который до сих пор требуется поддерживать).

Знакомство с lit-element и веб-компонентами на его основе - 1

Реализовано 2 варианта этого полифилла:

  1. webcomponents-bundle.js [6] — данная версия содержит все возможные варианты полизаполнения, все они инициируются, но каждый полифилл будет работать только на основании обнаруженных признаков.
  2. webcomponents-loader.js [7] — это минимальный загрузчик, который на основании обнаруженных признаков подгружает нужные полифиллы

Также прошу обратить внимание на еще один полифилл — custom-elements-es5-adapter.js [8]. Согласно спецификации, в нативный customElements.define могут быть добавлены только ES6 классы. Для лучшей производительности код на ES6 стоит передавать только тем браузерам, которые его поддерживают, а ES5 — всем остальным. Так не всегда получается сделать, поэтому для лучшей кроссбраузерности, рекомендуется весь ES6 код переводить в ES5. Но в таком случае веб-компоненты на ES5 не смогут работать в браузерах. Для решения этой проблемы и существует custom-elements-es5-adapter.js.

Теперь давайте откроем файл ./src/my-element.js

import {html, LitElement, property} from 'lit-element';

class MyElement extends LitElement {
  // @property - декоратор, который может обработать babel и ts
  // он нужен для определения типа переменной и дальнейшей
  // ее проверки, силами транспайлера
  @property({type: String}) myProp = 'stuff';
  render() {
    return html`
      <p>Hello World</p>
      ${this.myProp}
    `;
  }
}

customElements.define('my-element', MyElement);

Шаблонизатор lit-html может обработать строку по-разному. Приведу несколько вариантов:

// статичный элемент: 
html`<div>Hi</div>`

// выражение: 
html`<div>${this.disabled ? 'Off' : 'On'}</div>`

// свойство: 
html`<x-foo .bar="${this.bar}"></x-foo>`

// атрибут: 
html`<div class="${this.color} special"></div>`

// атрибут типа boolean, если checked === false,
// то данный атрибут не будет добавлен в HTML: 
html`<input type="checkbox" ?checked=${checked}>`

// обработчик события: 
html`<button @click="${this._clickHandler}"></button>`

Советы по оптимизации функции render():

  • не должна изменять состояние элемента,
  • не должна иметь side effects,
  • должна зависеть только от свойств элемента,
  • должна возвращать одинаковый результат при передаче одинаковых значений.

Не делайте обновление DOM вне функции render().

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

Почти все из этого кода было в простом примере, но добавлен декоратор [9] @property для свойства myProp. Данный декоратор указывает на то, что мы ожидаем атрибут с именем myprop в нашем my-element. Если такой атрибут не задан, ему по умолчанию задается строковое значение stuff.

<!-- Атрибут myProp не задан, по этому он будет сгенерирован в веб-компоненте
  со значением 'stuff' -->
<my-element></my-element> 

<!-- Атрибут myprop из нотации в строчном формате соотносится 
  с нотацией lowerCamelCase т.е. myProp и в веб-компоненте 
  этому свойству будет задано значение 'else' -->
<my-element myprop="else"></my-element>

lit-element предоставляет 2 способа работы с property:

  1. Через декоратор.
  2. Через статический геттер properties.

Первый вариант дает возможность указать каждое свойство отдельно:

@property({type: String}) prop1 = '';
@property({type: Number}) prop2 = 0;
@property({type: Boolean}) prop3 = false;
@property({type: Array}) prop4 = [];
@property({type: Object}) prop5 = {};

Второй – указать все в одном месте, но в этом случае, если у свойства есть значение по умолчанию, его необходимо прописывать в методе конструктора класса:

static get properties() {
  return { 
    prop1: {type: String},
      prop2: {type: Number},
      prop3: {type: Boolean},
      prop4: {type: Array},
      prop5: {type: Object}
  };
}
constructor() {
  this.prop1 = '';
  this.prop2 = 0;
  this.prop3 = false;
  this.prop4 = [];
  this.prop5 = {};
}

API для работы с properties в lit-element довольно обширное:

  • attribute: может ли свойство стать наблюдаемым атрибутом. Если значение false, то атрибут будет исключен из наблюдения, для него не будет создан геттер. Если true или attribute отсутствует, то свойство, указанное в геттере в формате lowerCamelCase, будет соотноситься с атрибутом в строчный формат. Если задана строка, например my-prop – то будет соотноситься с таким же названием в атрибутах.
  • converter: содержит описание того, как преобразовать значение из/в атрибута/свойства. Значением может быть функция, которая работает для сериализации и десериализации значения, либо это может быть объект с ключами fromAttribute и toAttribute, эти ключи содержат отдельные функции для конвертации значений. По умолчанию свойство содержит преобразование в базовые типы Boolean, String, Number, Object и Array. Правила преобразования указаны тут [10].
  • type: указывает на один из базовых типов, который будет содержать данное свойство. Используется как «подсказка» для конвертера о том, какой тип должно содержать свойство.
  • reflect: указывает на то, должен ли атрибут быть связан со свойством (true) и изменяться в соответствии с правилами из type и converter.
  • hasChanged: есть у каждого свойства, содержит функцию, определяющую, есть ли изменение между старым и новым значением, соответственно возвращает Boolean. Если true – то запускает обновление элемента.
  • noAccessor: данное свойство принимает Boolean и по умолчанию false. Оно запрещает генерировать геттеры и сеттеры для каждого свойства для обращения к ним из класса. Это не отменяет конвертацию.

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

<!-- index.html -->
<ladder-of-letters latters="абвгде"></ladder-of-letters>

//ladder-of-letters.js
import {html, LitElement, property} from 'lit-element';

class LadderOfLetters extends LitElement {
  @property({
    type: Array,
    converter: {
      fromAttribute: (val) => {
        // console.log('in fromAttribute', val);
        return val.split('');
      }
    },
    hasChanged: (value, oldValue) => {
      if(value === undefined || oldValue === undefined) {
        return false;
      }
      // console.log('in hasChanged', value, oldValue.join(''));
      return value !== oldValue;
    },
    reflect: true
  }) letters = [];

  changeLetter() {
    this.letters = ['Б','В','Г','Д','Е'];
  }

  render() {
    // console.log('in render', this.letters);
				
    // для стилизации есть директивы, тут не использовано
    // что бы не нагромождать функционала в примере
    return html`
      <div>${this.letters.map((i, idx) => html`<span style="font-size: ${idx + 2}em">${i}</span>`)}</div>
            
      // @click это краткая запись о том, что мы добавляем слушатель
      // на событие 'click' по данному элементу
      <button @click=${this.changeLetter}>Изменить на 'БВГДЕ'</button>  
    `;
  }
}

customElements.define('ladder-of-letters', LadderOfLetters);

в итоге получаем:

Знакомство с lit-element и веб-компонентами на его основе - 2

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

Знакомство с lit-element и веб-компонентами на его основе - 3

а используя reflect мы можем увидеть также изменения в html

Знакомство с lit-element и веб-компонентами на его основе - 4

При изменении этого атрибута кодом вне этого веб-компонента мы также вызовем перерисовку веб-компонента.

Теперь рассмотрим стилизацию компонента. У нас есть 2 способа стилизовать lit-element:

  1. Стилизация через добавление тега style в метода render
    render() {
      return html`
        <style>
          p {
            color: green;
          }
        </style>
        <p>Hello World</p>
      `;
    }
    

    Знакомство с lit-element и веб-компонентами на его основе - 5

  2. Через статический геттер styles
    import {html, LitElement, css} from 'lit-element';
    
    class MyElement extends LitElement {
      static get styles() {
        return [
          css`
            p {
              color: red;
            }
          `
        ];
      }
      render() {
        return html`
          <p>Hello World</p>
        `;
      }
    }
    
    customElements.define('my-element', MyElement);
    

В итоге получаем, что тег со стилями не создается, а прописывается (>= Chrome 73) в Shadow DOM элемента в соответствии со спецификацией [11]. Таким образом улучшается перфоманс при большом количестве элементов, т.к. при регистрации нового компонента он уже знает, какие свойства ему определяют его стили, их не надо регистрировать каждый раз и пересчитывать.

Знакомство с lit-element и веб-компонентами на его основе - 6

При этом, если данная спецификация не поддерживается, то создается обычный тег style в компоненте.

Знакомство с lit-element и веб-компонентами на его основе - 7

Плюс, не забывайте, что таким образом мы также можем разделить, какие стили будут добавлены и рассчитаны на странице. Например, использовать медиазапросы не в css, а в JS и имплементировать только нужный стиль, например (это дико, но имеет место быть):

static get styles() {
  const mobileStyle = css`p { color: red; }`;
  const desktopStyle = css`p { color: green; }`;

  return [
    window.matchMedia("(min-width: 400px)").matches ? desktopStyle : mobileStyle
  ];
}

Соответственно, это мы увидим, если пользователь зашел на устройстве с шириной экрана более 400px.

Знакомство с lit-element и веб-компонентами на его основе - 8

А это – если пользователь зашел на сайт с устройства с шириной менее 400px.

Знакомство с lit-element и веб-компонентами на его основе - 9

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

Теперь предлагаю ознакомиться с методами жизненного цикла lit-element:

  • render(): реализует описание DOM элемента с помощью lit-html. В идеале, функция render – это чистая функция, которая использует только текущие свойства элемента. Метод render() вызывается функцией update().
  • shouldUpdate(changedProperties): реализуется, если необходимо контролировать обновление и рендеринг, когда были изменены свойства или вызван requestUpdate(). Аргумент функции changedProperties – это Map, содержащий ключи измененных свойств. По умолчанию данный метод всегда возвращает true, но логику метода можно изменить, чтобы контролировать обновлением компонента.
  • performUpdate(): реализуется для контроля времени обновления, например для интеграции с планировщиком.
  • update(changedProperties): этот метод вызывает render(). Также он выполняет обновление атрибутов элемента в соответствии со значением свойства. Установка свойств внутри этого метода не вызовет другое обновление.
  • firstUpdated(changedProperties): вызывается после первого обновления DOM элемента непосредственно перед вызовом updated(). Этот метод может быть полезен для захвата ссылок на визуализированные статические узлы, с которыми нужно работать напрямую, например, в updated().
  • updated(changedProperties): вызывается всякий раз, когда DOM элемента обновляется и отображается. Реализация для выполнения задач после обновления через API DOM, например, фокусировка на элементе.
  • requestUpdate(name, oldValue): вызывает запрос асинхронного обновления элемента. Это следует вызывать, когда элемент должен обновляться на основе некоторого состояния, не вызванного установкой свойства.
  • createRenderRoot(): по умолчанию создает Shadow Root для элемента. Если использование Shadow DOM не нужно, то метод должен вернуть this.

Как происходит обновление элемента:

  • Свойству задают новое значение.
  • Если свойство hasChanged(value, oldValue) возвращает false, элемент не обновляется. Иначе планируется обновление путем вызова requestUpdate().
  • requestUpdate(): обновляет элемент после microtask (в конце event loop и перед следующей перерисовкой).
  • performUpdate(): выполняется обновление, и продолжает остальную часть update API.
  • shouldUpdate(changedProperties): обновление продолжается, если возвращается true.
  • firstUpdated(changedProperties): вызывается когда элемент обновляется в первый раз, сразу же перед вызовом updated().
  • update(changedProperties): обновляет элемент. Изменение свойств в этом методе не вызывает другого обновления.
    • render(): возвращает lit-html шаблон для отрисовки элемента в DOM. Изменение свойств в этом методе не вызывает другого обновления.

  • updated(changedProperties): вызывается всякий раз, когда элемент обновляется.

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

На работе у меня проект на adobe experience manager (AEM), в его авторинге пользователь может делать drag & drop компонентов на страницу, и по идеологии AEM этот компонент содержит тег script, в котором содержится все что нужно для реализации логики данного компонента. Но по факту, такой подход порождал множество блокирующих ресурсов и сложностей с реализацией фронта в данной системе. Для реализации фронта были выбраны веб-компоненты как способ не изменять рендеринг на стороне сервера (с чем он прекрасно справлялся), а также мягко, поэлементно, обогащать старую реализацию новым подходом. На мой взгляд, есть несколько вариантов реализации подгрузки веб-компонентов для данной системы: собрать бандл (он может стать очень большим) или разбить на чанки (очень много мелких файлов, нужна динамическая подгрузка), или использовать уже текущий подход с встраиванием script в каждый компонент, который рендерится на стороне сервера (очень не хочется к этому возвращаться). На мой взгляд, первый и третий вариант – не вариант. Для второго нужен динамический загрузчик, как в stencil. Но для lit-element в «коробке» такого не предоставляется. Со стороны разработчиков lit-element была попытка создать динамический загрузчик [13], но он является экспериментом, и использовать его в продакшен не рекомендуется. Также от разработчиков lit-element есть issue [14] в репозиторий спецификации веб-компонентов [15] с предложением добавить в спецификацию возможность динамически подгружать необходимый js для веб-компонента на основе html разметки на странице. И, на мой взгляд, этот нативный инструмент – очень хорошая идея, которая позволит создавать одну точку инициализации веб-компонентов и просто добавлять ее на всех страницах сайта.

Для динамической подгрузки веб-компонентов на основе lit-element ребятами из PolymerLabs был разработан split-element [13]. Это эксперементальное решение. Работает оно следующим способом:

  • Чтобы создать SplitElement, вы пишете два определения элементов в двух модулях.
  • Одним из них является «заглушка», которая определяет загруженные части элемента: обычно это имя и свойства. Свойства должны быть определены с заглушкой, чтобы lit-element мог своевременно генерировать наблюдаемые атрибуты для вызова customElements.define().
  • Заглушка также должна иметь статический асинхронный метод загрузки, который возвращает класс реализации.
  • Другой класс – это «реализация», которая содержит все остальное.
  • Конструктор SplitElement загружает класс реализации и выполняет upgrade().

Пример заглушки:

import {SplitElement, property} from '../split-element.js';

export class MyElement extends SplitElement {
  // MyElement содержит асинхронную функцию load которая будет
  // вызвана в момент при вызове connectedCallback() пользовательского элемента
  static async load() {
    // через динамический импорт указывается путь и класс 
    // элемента который будет имплементирован вместо MyElement
    return (await import('./my-element-impl.js')).MyElementImpl;
  }

  // желательно указать некоторое первоначальное значение
  // для свойств веб-компонента
  @property() message: string;
}
customElements.define('my-element', MyElement);

Пример реализации:

import {MyElement} from './my-element.js';
import {html} from '../split-element.js';

// MyElementImpl содержит render и всю логику веб-компонента
export class MyElementImpl extends MyElement {
  render() {
    return html`
      <h1>I've been upgraded</h1>
      My message is ${this.message}.
    `;
  }
}

Пример SplitElement на ES6:

import {LitElement, html} from 'lit-element';
export * from 'lit-element';

// подменяем базовый класс LitElement на SplitElement
// в котором реализуем логику асинхронной подгрузки
export class SplitElement extends LitElement {
  static load;
  static _resolveLoaded;
  static _rejectLoaded;
  static _loadedPromise;
  static implClass;

  static loaded() {
    if (!this.hasOwnProperty('_loadedPromise')) {
      this._loadedPromise = new Promise((resolve, reject) => {
        this._resolveLoaded = resolve;
        this._rejectLoaded = reject;
      });
    }
    return this._loadedPromise;
  }

  // функция которая сменит прототип для веб-компонента
  // с его загрузчика на реализацию
  static _upgrade(element, klass) {
    SplitElement._upgradingElement = element;
    Object.setPrototypeOf(element, klass.prototype);
    new klass();
    SplitElement._upgradingElement = undefined;
    element.requestUpdate();
    if (element.isConnected) {
      element.connectedCallback();
    }
  }

  static _upgradingElement;

  constructor() {
		
    if (SplitElement._upgradingElement !== undefined) {
      return SplitElement._upgradingElement;
    }

    super();
    const ctor = this.constructor;
    if (ctor.hasOwnProperty('implClass')) {
      // Реализация уже загружена, немедленно обновить
      ctor._upgrade(this, ctor.implClass);
    } else {
      // Реализация не загружена
      if (typeof ctor.load !== 'function') {
        throw new Error('A SplitElement must have a static `load` method');
      }
      (async () => {
        ctor.implClass = await ctor.load();
        ctor._upgrade(this, ctor.implClass);
      })();
    }
  }

  // Заглушка не должна что либо рендерить
  render() {
    return html``;
  }
}

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

npm install @babel/plugin-syntax-dynamic-import

А в настройках .babelrc добавить

{
  "plugins": ["@babel/plugin-syntax-dynamic-import"]
}

Тут я сделал небольшой пример реализации веб-компонентов с отложенной подгрузкой: https://github.com/malay76a/elbrus-split-litelement-web-components [16]

следующему выводу: инструмент вполне рабочий, надо все определения веб-компонентов собирать в один файл, а описание самого компонента через чанки подключать отдельно. Без http2 данный подход не работает, т.к. формируется очень большой пул мелких файлов, описывающих компоненты. Если исходить из принципа atomic design [17], то импортирование атомов необходимо определять в организме, а вот организм уже подключать как отдельный компонент. Одно из «узких» мест – это то, что пользователю в браузер придет множество определений пользовательских элементов, которые будут так или иначе инициализированы в браузере, и им будет определено первоначальное состояние. Такое решение избыточно. Один из вариантов простого решения для загрузчика компонентов это следующий алгоритм:

  1. подгрузить обязательные утилиты,
  2. подгрузить полифиллы,
  3. собрать пользовательские элементы из light DOM:
    1. выбираются все элементы DOM содержащие дефис в названии тега,
    2. фильтруется список и формируется список из первых элементов.
  4. запустить проход по циклу полученных пользовательских элементов:
    1. на каждый навешивается Intersection Observer,
    2. при попадании первого пользовательского элемента во вьюпорт +- 100px произвести загрузку ресурсов через динамический import.
    1. ИЛИ повторяется с пункта 3 только для пользовательского элемента и его shadowDOM,
    2. ИЛИ компоненты, содержащие в shadowDOM другие компоненты, декларативно реализуют подгрузку необходимых зависимостей, указав import в голове JS.

Для более удобной работы с веб-компонентами и lit-element я бы предложил обратить внимание на проект open-wc.org [18]. Там предложены генераторы для сборщиков на основе webpack и rollup, туллинг для тестирования веб-компонентов и их демонстрации с помощью storybook, а также советы и рекомендации по разработке и настройки IDE.

Дополнительные ссылки:

  1. Let's Build Web Components! Part 5: LitElement [19]
  2. Web Component Essentials [20]
  3. A night experimenting with Lit-HTML… [21]
  4. LitElement To Do App [22]
  5. LitElement app tutorial part 1: Getting started [23]
  6. LitElement tutorial part 2: Templating, properties, and events [24]
  7. LitElement tutorial part 3: State management with Redux [25]
  8. LitElement tutorial part 4: Navigation and code splitting [26]
  9. LitElement tutorial part 5: PWA and offline [27]
  10. Lit-html workshop [28]
  11. Awesome lit-html [29]

Автор: dagot32167

Источник [30]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/312697

Ссылки в тексте:

[1] lit-element: https://github.com/Polymer/lit-element

[2] benchmark: https://vogloblinsky.github.io/web-components-benchmark/

[3] http://localhost:5000/: http://localhost:5000/

[4] webcomponents-loader.js: https://github.com/webcomponents/webcomponentsjs

[5] таблицу браузеров: https://www.webcomponents.org/

[6] webcomponents-bundle.js: https://github.com/webcomponents/webcomponentsjs#using-webcomponents-bundlejs

[7] webcomponents-loader.js: https://github.com/webcomponents/webcomponentsjs#using-webcomponents-loaderjs

[8] custom-elements-es5-adapter.js: https://github.com/webcomponents/webcomponentsjs#custom-elements-es5-adapterjs

[9] декоратор: https://github.com/tc39/proposal-decorators#decorators

[10] тут: https://lit-element.polymer-project.org/guide/properties#conversion

[11] спецификацией: https://wicg.github.io/construct-stylesheets/

[12] документации: https://lit-element.polymer-project.org/guide/lifecycle

[13] попытка создать динамический загрузчик: https://github.com/PolymerLabs/split-element

[14] issue: https://github.com/w3c/webcomponents/issues/782

[15] репозиторий спецификации веб-компонентов: https://github.com/w3c/webcomponents

[16] https://github.com/malay76a/elbrus-split-litelement-web-components: https://github.com/malay76a/elbrus-split-litelement-web-components

[17] atomic design: http://bradfrost.com/blog/post/atomic-web-design/

[18] open-wc.org: http://open-wc.org/

[19] Let's Build Web Components! Part 5: LitElement: https://dev.to/bennypowers/lets-build-web-components-part-5-litelement-906

[20] Web Component Essentials: https://books.google.ru/books?id=HStxDwAAQBAJ&pg=PA71&lpg=PA71&dq=LitElement+performance&source=bl&ots=d_axG-lsTB&sig=sdyQdApVjn0kGsi4ov6x6d3Sm6Q&hl=ru&sa=X&ved=2ahUKEwirjtq11KbfAhWKZlAKHdPXB34Q6AEwB3oECAAQAQ#v=onepage&q=LitElement&f=false

[21] A night experimenting with Lit-HTML…: https://medium.com/@lucamezzalira/a-night-experimenting-with-lit-html-585a8c69892a

[22] LitElement To Do App: https://medium.com/@westbrook/litelement-to-do-app-1e08a31707a4

[23] LitElement app tutorial part 1: Getting started: https://www.youtube.com/watch?v=UcCsGZDCw-Q

[24] LitElement tutorial part 2: Templating, properties, and events: https://www.youtube.com/watch?v=s6P3R-J0IiI

[25] LitElement tutorial part 3: State management with Redux: https://www.youtube.com/watch?v=_Gt12UhGLY0

[26] LitElement tutorial part 4: Navigation and code splitting: https://www.youtube.com/watch?v=JajSgc7xelI

[27] LitElement tutorial part 5: PWA and offline: https://www.youtube.com/watch?v=ToxKlmqgZHw

[28] Lit-html workshop: https://github.com/LarsDenBakker/lit-html-workshop

[29] Awesome lit-html: https://github.com/web-padawan/awesome-lit-html

[30] Источник: https://habr.com/ru/post/445438/?utm_campaign=445438