Паттерн «Импорт при взаимодействии» (часть 1)

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

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

Ссылка на оригинал

Паттерн заключается в отложенной загрузке ресурсов, то есть только тогда, когда пользователю нужна какая-либо часть интерфейса.

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

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

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

Это может негативно отразиться на таких метриках как:

  • FID (First Input Delay)

  • TBT (Total Blocking Time)

  • TTI (Time To Interactive)

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

  • Пользователь впервые кликает на компоненте

  • Компонент попал во viewport

  • Или отложить загрузку компонента до того момента, пока браузер не будет бездействовать ( через requestIdleCallback )

Различные варианты как мы можем загружать ресурсы:

  • Сразу - обычный способ для загрузки скриптов

  • Lazy (для роутера) - загружать, когда пользователь посещает страницы

  • Lazy (при взаимодействии) - пользователь кликнул на элементе

  • Lazy (viewport) - загружать когда юзер доскроллил до компонента

  • Prefetch - предварительная загрузка, но после загрузки critical resources

  • Preload - загрузка сразу

Отложенный импорт при взаимодействии очень часто встречается и мы разберем дальше несколько примеров. Вы могли его видеть например в Google Docs, где применение этого паттерна позволяет экономить около 500кб:

Другие ситуации где пригодится этот паттерн - это всевозможные сторонние виджеты.

Загрузка youtube видео по клику:

А вот на сайте android.com :

Аутентификация

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

Виджеты для чатов

Calibre app улучшили производительность их виджета на 30% через использование подобного "фасадного подхода". Они сделали кнопку чата на CSS и HTML, и по клику на нее подгружают нужный бандл.

Postmark также отметили, что всегда загружали свой help виджет сразу, хотя не всем пользователям требовался данный функционал. Причем весь виджета составляем 314кб - больше чем вся их главная страница. Для улучшения UX они заменили виджет на CSS + HTML компонент, и грузили всю библиотеку только после клика по кнопке. Таким образом они смогли уменьшить метрику TTI (Time To Interactive) с 7.7 до 3.7 секунд.

Другие примеры

Компания NE Digital использует react-scroll для анимированного скролла к началу страницы. Вместо того чтобы сразу загружать всю библиотеку, они загружают ее по клику на кнопке, экономя около 7КБ.

handleScrollToTop() {
    import('react-scroll').then(scroll => {
      scroll.animateScroll.scrollToTop({
      })
    })
}

Как мы можем применять этот паттерн ?

Чистый JS

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

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

const btn = document.querySelector('button');

btn.addEventListener('click', e => {
  e.preventDefault();
  import('lodash.sortby')
    .then(module => module.default)
    .then(sortInput()) // use the imported dependency
    .catch(err => { console.log(err) });
});

Также можно динамически инлайнить скрипты:

const loginBtn = document.querySelector('#login');

loginBtn.addEventListener('click', () => {
  const loader = new scriptLoader();
  loader.load([
      '//apis.google.com/js/client:platform.js?onload=showLoginScreen'
  ]).then(({length}) => {
      console.log(${length} scripts loaded!);
  });
});

React

Давайте представим что у нас есть приложение чата, у которого есть три компонента:

  • <MessageList>

  • <MessageInput>

  • <EmojiPicker> (использует emoji-mart весом 98кб - минифицированный и gzip'нутый)

Часто бывает так, что все эти компоненты загружаются при стартовой загрузке страницы:

import MessageList from './MessageList';
import MessageInput from './MessageInput';
import EmojiPicker from './EmojiPicker';

const Channel = () => {
  ...
  return (
    <div>
      <MessageList />
      <MessageInput />
      {emojiPickerOpen && <EmojiPicker />}
    </div>
  );
};

Можно декомпозировать такую загрузку с помощью code-splitting.

Метод React.lazy позволяет легко сделать code-splitting на компонентом уровне с помощью динамического импорта. Функция React.lazy предоставляет легкую возможность разделить компоненты в приложении на отдельные чанки. С помощью Suspense можно добавить состояние загрузки, пока грузится EmojiPicker:

import React, { lazy, Suspense } from 'react';
import MessageList from './MessageList';
import MessageInput from './MessageInput';

const EmojiPicker = lazy(
  () => import('./EmojiPicker')
);
const Channel = () => {
  ...
  return (
    <div>
      <MessageList />
      <MessageInput />
      {emojiPickerOpen && (
        <Suspense fallback={<div>Loading...</div>}>
          <EmojiPicker />
        </Suspense>
      )}
    </div>
  );
};

Мы можем пойти дальше и загружать код для EmojiPicker только при щелчке значка эмодзи в <MessageInput>, а не сразу при загрузке приложения :

import React, { useState, createElement } from 'react';
import MessageList from './MessageList';
import MessageInput from './MessageInput';
import ErrorBoundary from './ErrorBoundary';

const Channel = () => {
  const [emojiPickerEl, setEmojiPickerEl] = useState(null);
  const openEmojiPicker = () => {
    import(/* webpackChunkName: "emoji-picker" */ './EmojiPicker')
      .then(module => module.default)
      .then(emojiPicker => {
        setEmojiPickerEl(createElement(emojiPicker));
      });
  };
  const closeEmojiPickerHandler = () => {
    setEmojiPickerEl(null);
  };
  return (
    <ErrorBoundary>
      <div>
        <MessageList />
        <MessageInput onClick={openEmojiPicker} />
        {emojiPickerEl}
      </div>
    </ErrorBoundary>
  );
};

Vue

На vue есть несколько вариантов реализации этого паттерна. Один из них это динамически импортировать EmojiPicker. Когда потребуется его отрендерить vue динамически подгрузит необходимый чанк.

С помощью v-if="show" мы можем управлять отображением компонента EmojiPicker по клику на кнопке:

<template>
  <div>
    <button @click="show = true">Load Emoji Picker</button>
    <div v-if="show">
      <emojipicker></emojipicker>
    </div>
  </div>
</template>

<script>
export default {
  data: () => ({ show: false }),
  components: {
    Emojipicker: () => import('./Emojipicker')
  }
};
</script>

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

Продолжение следует...

Источник: https://habr.com/ru/post/557762/


Интересные статьи

Интересные статьи

Привет, Хаброжители! Паттерны проектирования — удобный прием программирования для решения рутинных задач разработки ПО. Грамотное использование паттернов позволяет добиться соответствия ...
Часть первая тут. Долг в отношениях очень похож на технический долг. Он представляет собой багаж эмоций, накопленных основателями, в процессе принятия решений. Эти решения могут ка...
В индустрии сложилось устойчивое мнение, что Service Locator является анти-паттерном. Из wiki: "Стоит заметить, что в некотором случае локатор служб фактически является а...
Привет, Хабр! Представляю вашему вниманию перевод статьи «Database: Anti-Patterns» автора Sergey Kozlov. Если вы храните данные, это критически важная часть вашего приложения. Можно легко и бы...
Одной из «киллер-фич» 12й версии Битрикса была объявлена возможность отдавать статические файлы из CDN, тем самым увеличивая скорость работы сайта. Попробуем оценить практический выигрыш от использова...