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


lit-element — это обертка (базовый шаблон) для нативных веб-компонентов. Она реализует множество удобных методов, которых нет в спецификации. За счет своей близости к нативной реализации lit-element показывает очень хорошие результаты в различных benchmark относительно других подходов (на 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/.


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



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


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

Также прошу обратить внимание на еще один полифилл — custom-elements-es5-adapter.js. Согласно спецификации, в нативный 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, которые должны быть изменены.


Почти все из этого кода было в простом примере, но добавлен декоратор @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. Правила преобразования указаны тут.
  • type: указывает на один из базовых типов, который будет содержать данное свойство. Используется как «подсказка» для конвертера о том, какой тип должно содержать свойство.
  • reflect: указывает на то, должен ли атрибут быть связан со свойством (true) и изменяться в соответствии с правилами из type и converter.
  • hasChanged: есть у каждого свойства, содержит функцию, определяющую, есть ли изменение между старым и новым значением, соответственно возвращает Boolean. Если true – то запускает обновление элемента.
  • noAccessor: данное свойство принимает Boolean и по умолчанию false. Оно запрещает генерировать геттеры и сеттеры для каждого свойства для обращения к ним из класса. Это не отменяет конвертацию.

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


<!-- index.html -->
<ladder-of-letters letters="абвгде"></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);

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



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



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



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


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


  1. Стилизация через добавление тега style в метода render

    render() {
      return html`
        <style>
          p {
            color: green;
          }
        </style>
        <p>Hello World</p>
      `;
    }
    


  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 элемента в соответствии со спецификацией. Таким образом улучшается перфоманс при большом количестве элементов, т.к. при регистрации нового компонента он уже знает, какие свойства ему определяют его стили, их не надо регистрировать каждый раз и пересчитывать.




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




Плюс, не забывайте, что таким образом мы также можем разделить, какие стили будут добавлены и рассчитаны на странице. Например, использовать медиазапросы не в 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.



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



Мое мнение: практически нет ни одного адекватного кейса, когда пользователь, работая на мобильном устройстве, неожиданно окажется перед полноценным монитором с шириной экрана 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): вызывается всякий раз, когда элемент обновляется.

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


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


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


  • Чтобы создать 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


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


  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. Там предложены генераторы для сборщиков на основе webpack и rollup, туллинг для тестирования веб-компонентов и их демонстрации с помощью storybook, а также советы и рекомендации по разработке и настройки IDE.


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


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

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


  1. BerkutEagle
    27.03.2019 06:26

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

    Huawei и Samsung предоставляют такой кейс :)
    image


    1. dagot32167 Автор
      27.03.2019 08:15

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


      1. ColdPhoenix
        27.03.2019 08:56

        Ну есть еще смена ориентации, ресайз окна.
        Вы же не будете привязываться строго к 1080р?


        1. dagot32167 Автор
          27.03.2019 09:07

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

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

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


          1. justboris
            27.03.2019 10:54

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

            А здесь стили все равно загружаются, просто не выводятся в CSS. Есть ли вообще в этом выгода?


            1. dagot32167 Автор
              27.03.2019 11:39

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


              1. justboris
                27.03.2019 12:21

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


                1. dagot32167 Автор
                  27.03.2019 12:30

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


                  1. justboris
                    27.03.2019 15:38

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


                    Именно это и побудило меня оставить коммент


                    1. dagot32167 Автор
                      27.03.2019 16:53

                      Сорь, но:

                      1. Из текста «Я не являюсь экспертом в данной технологии», «это дико, но имеет место быть», «Мое мнение». Я не считаю нужным вешать там огромный баннер «не делайте так!», там где высказываю личное мнение. По поводу бесполезности оптимизации, если есть желание, я предлагаю Вам доказать несостоятельность этого подхода. Я высказал свое мнение и в комментах подтверждаю «По идее, это должно повлиять на конечную скорость отрисовки. А по факту, в большинстве случаев — это «экономия на спичках».»
                      2. На этом, считаю, что данная ветка в дальнейшем не актуальна. Если есть желание в дальнейшем подискутировать на эту тему, то велком в личку


              1. justboris
                27.03.2019 12:22

                использовать динамический импорт и нет проблем с объемом

                А вот это интересно. Как можно сделать динамический импорт стилей в lit-element?


                1. dagot32167 Автор
                  27.03.2019 12:31

                  еще не пробовал. ща попробую сделать такую сборку и посмотреть что получится


                1. dagot32167 Автор
                  27.03.2019 15:28

                  немного фуфуфу, но самый простой вариант например такой codesandbox.io/s/my55o3zywx
                  Для поддержки абсолютно не рабочий вариант и нужно еще сменить parcel на что то более управляемое, что бы дробил на чанки, но как пример сойдет


  1. ThisMan
    27.03.2019 09:22

    У lit-element есть еще интересное св-во updateComplete ( https://lit-element.polymer-project.org/guide/lifecycle#updatecomplete ) — это промис, который резолвится после того, как компонент обновился.


    async someAction() {
       this.prop = 'new value';
       await this.updateComplete;
       console.log('update complete');
    }

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


    async someAction() {
      this.prop = 'new value';
      await this.updateComplete;
      await children.updateComplete;
    }

    В более сложных пилить костыли.


    1. dagot32167 Автор
      27.03.2019 11:34

      спасибо. еще не пользовался этим методом. учту в дальнейшей работе