Ленивая загрузка - это принцип, который должен быть известен большинству frontend разработчиков. Однако, этот механизм обманчиво прост, и его освоение является гораздо более комплексной задачей, чем кажется многим. Если вы уже используете Lazy Loading, у вас все равно могут быть серьезные пробелы в знаниях. Но даже если вы считаете, что знаете про ленивую загрузку абсолютно все, освежить память не будет лишним.
Содержание серии
Часть 1: Чем полезна ленивая загрузка || English version
В этой статье мы рассмотрим основы ленивой загрузки:
Что это такое и почему это важно;
Как мы можем использовать ленивую загрузку в наших проектах;
А также какие части кода следует загружать лениво.
Однако начиная с части 2, мы рассмотрим более сложные темы:
Как бандлеры (например, Webpack) анализируют файлы исходного кода, строят деревья зависимостей и генерируют файлы для сборки.
Как мы можем уменьшить размер и количество загружаемых файлов, правильно настроив структуру файлов и правильно используя статические импорты.
-
Как мы должны загружать вендор файлы и настраивать группы кэша Webpack.
А также что "ленивая загрузка" и "кэширование" имеют общего, и как сделать проекты максимально кэшируемыми.
-
Как мы можем использовать стратегии предварительной загрузки, включая:
Какие магические комментарии Webpack'а мы можем использовать;
Что такое спекулятивная или ручная выборка и как ее использовать;
И какие сторонние или наши собственные решения могут быть использованы для этого.
А также как мы можем запросить данные с сервера, не дожидаясь загрузки наших статических файлов.
Польза Lazy Loading
Итак, начнем. Что такое "Lazy Loading"?
Представьте себе веб-приложение: чем больше оно становится, тем больше времени требуется браузеру, чтобы загрузить все статические файлы. И чем дольше оно загружается, тем сильнее его пользователи могут негодовать. Мы, как фронтендеры, несем ответственность за то, чтобы наши пользователи испытывали как можно меньше дискомфорта при использовании сайта. По крайней мере в вопросах производительности.
А "Ленивая загрузка" может быть самой простой стратегией, которую мы можем применить, чтобы ускорить загрузку нашего приложения, если мы рассматриваем FE-only механизмы. Основная идея этой стратегии проста: если нам не нужно использовать фрагмент кода немедленно, мы не должны его загружать. И мне очень нравится это определение, но мы вернемся к нему позже. А пока давайте рассмотрим простейший пример применения ленивой загрузки в приложении.
Загружайте свои страницы лениво
Предположим, что у нас есть простое приложение с 3мя страницами. И на данный момент, оно скомпилировано в один файл JavaScript. В данной серии в качества примера мы будем рассматривать следующее пет-приложение:
import { HashRouter, Routes, Route, Link } from 'react-router-dom';
import React, { Suspense } from 'react';
import Chapter1 from './pages/chapter-1/Chapter1';
import Chapter2 from './pages/chapter-2/Chapter2';
import Title from './pages/title/Title';
export const App = () => (
<HashRouter>
<nav className="navigation">
<ul>
<li><Link to="/">Title</Link></li>
<li><Link to="/chapter-1">Chapter 1</Link></li>
<li><Link to="/chapter-2">Chapter 2</Link></li>
</ul>
</nav>
<Suspense fallback="Loading...">
<div className="book-grid">
<Routes>
<Route path="/" element={<Title />} />
<Route path="/chapter-1" element={<Chapter1 />} />
<Route path="/chapter-2" element={<Chapter2 />} />
</Routes>
</div>
</Suspense>
</HashRouter>
);
Когда мы открываем такой сайт, загрузка единственного JavaScript файла может занять около 580ms.

Мы хотели бы уменьшить время загрузки, но как этого добиться? Мы можем начать с того, чтобы загружать страницы нашего приложения лениво. Это означает, что пока пользователь не захочет открыть определенную страницу, код для ее отображения не будет скачиваться. И этого можно достичь довольно легко. Вместо статического импорта страниц Chapter1 и Chapter2 нам следует импортировать их с помощью динамического импорта. Плюс, т.к. мы используем React, мы еще должны обернуть этот импорт с помощью HOC'а React.lazy:
import { HashRouter, Routes, Route, Link } from 'react-router-dom';
import React, { Suspense } from 'react';
import Title from './pages/title/Title';
const Chapter2 = React.lazy(
() => import(/* webpackChunkName: "Chapter2" */ './pages/chapter-2/Chapter2')
);
const Chapter1 = React.lazy(
() => import(/* webpackChunkName: "Chapter1" */ './pages/chapter-1/Chapter1')
);
export const App = () => (
<HashRouter>
<nav className="navigation">
<ul>
<li><Link to="/">Title</Link></li>
<li><Link to="/chapter-1">Chapter 1</Link></li>
<li><Link to="/chapter-2">Chapter 2</Link></li>
</ul>
</nav>
<Suspense fallback="Loading main...">
<div className="book-grid">
<Routes>
<Route path="/" element={<Title />} />
<Route path="/chapter-1" element={<Chapter1 />} />
<Route path="/chapter-2" element={<Chapter2 />} />
</Routes>
</div>
</Suspense>
</HashRouter>
);
И вуаля - теперь изначальная загрузка приложения занимает всего 270ms. Нам удалось значительно сократить время загрузки.

Единственная цель ленивой загрузки - это стремление отложить загрузку наших файлов, запрашивая их только тогда, когда они действительно необходимы. В приведенном выше случае код страниц "Chapter 1" и "Chapter 2" будет загружен только тогда, когда эти страницы будут открыты.
И одно из главных преимуществ использования ленивой загрузки заключается в том, что она позволяет нам сократить начальное время загрузки нашего приложения. И есть несколько причин, по которым мы должны уделять большое внимание "начальному" времени загрузки:
Initial loading time - это самая важная задержка с точки зрения пользователя. Пока мы не загрузим исходные файлы, пользователи не смогут взаимодействовать с нашим веб-сайтом. А если мы говорим о Client Rendered Applications, пользователи даже не смогут увидеть какой-либо контент, пока мы не загрузим исходные файлы.
Изначально загружаемые файлы теряют кэш гораздо чаще, чем другие файлы, и поэтому оптимизация кэширования для них не так эффективна, как для других файлов. Мы рассмотрим основную причину этой проблемы в части 3.
Однако этот механизм также помогает сократить и общее время загрузки. Если мы хотим открыть определенную страницу с самого начала, скажем, "Chapter 1", это все равно займет меньше времени (~470ms), чем до применения отложенной загрузки (~580ms).

Поэтому подыдожив, можно сказать, что отложенная загрузка - это беспроигрышный подход к оптимизации, который может уменьшить время загрузки и улучшить UX веб-приложения.
Чтобы дать вам представление о том, насколько полезной может быть отложенная загрузка в реальных приложениях, я расскажу вам о моем реальном проекте, над которым я сейчас работаю. Мое приложение предназначено для отображения большого количества разнообразных данных: в нем есть таблицы, диаграммы, графики, видео-плееры и даже редакторы кода. Таким образом, он использует довольно много UI Kit и других NPM пакетов. Если я сложу размер всех сгенерированных файлов этого приложения, общий размер моего проекта превысит 22 МБ. Без какой-либо отложенной загрузки пользователям потребовалось бы загружать все 22 МБ изначально. Но если мы начнем лениво загружать страницы, пользователям потребуется загружать всего 0,5-6 МБ файлов для отображения каждой из страниц. Для моего проекта ленивая загрузка страниц способна привести к снижению времени загрузки статических файлов на 77-97%.
И это все. Концепция действительно довольно проста, но дьявол кроется в деталях. В этой серии мы рассмотрим гораздо более комплексные подтемы ленивой загрузки. Но а пока мы готовы сформулировать первое правило на нашем пути к тому, чтобы сделать приложение бесконечно ленивым:
? В большинстве случаев вам стоит по умолчанию использовать ленивую загрузку для всех страниц приложения. Независимо от того, сколько страниц в проекте и насколько велики эти страницы в своем размере.
Ленивая загрузка нужна не только для страниц
Однако, если вы используете ленивую загрузку исключительно для страниц, как в примере выше, вы не получаете всех преимуществ этого механизма. Мы, безусловно, можем добиться большего успеха. Давайте повторим определение еще раз.
? Если нам не нужно использовать фрагмент кода немедленно, мы не должны его загружать
Страница действительно является хорошим примером React компонента, который не требуется отображать немедленно. Но что еще можно загружать лениво? Ответ прост: что угодно!
Другие компоненты также могут загружаться лениво. Модальные окна, боковые панели, разделы вкладок или pop-up элементы. В подавляющем большинстве случаев нам не нужно отображать их сразу. И что мы делаем, когда нам не нужно использовать код немедленно? Мы не загружаем его. Концепция идеальной реализации ленивой загрузки проста: если компонент не является крошечным и тривиальным, он должен загружаться лениво.
Есть несколько причин использовать отложенную загрузку не только для страниц:
Каждый из этих компонентов может обладать уникальной логикой. Модальное окно или боковая панель могут содержать десятки уникальных валидаторов полей формы. Раздел с вкладками может быть таким же "содержательным", как и полноценная страница. И все они могут содержать встроенные data-URI и/или SVG-изображения, свои собственные стили, свои собственные вспомогательные методы, а иногда они могут даже иметь уникальные NPM зависимости.
-
Другая проблема заключается в том, что таких компонентов может быть много: десятки, а иногда и сотни.
И иногда они могут быть вложенными. Например, на странице могут быть разделы с вкладками, и один из них может содержать свои собственные разделы с вкладками, а каждая из них может открывать модальное окно, а то окно может открыть еще одно окно... Ну, вы поняли.
Конечно, далеко не все приложения имеют такой уровень вложенности компонентов. Но количество таких компонентов само по себе тоже является проблемой. Да, одно модальное окно, если его не загружать лениво, не будет влиять на производительность. Но десяток может начать, а сотня точно даст о себе знать.
Чтобы дать вам представление о реальном применении, я расскажу о двух своих последних проектах:
В моей предыдущей компании вложенности было не много. Однако около 20% размера исходных файлов предназначалось для исключительно отображения модальных окон. И это не было бизнес-требованием, просто ввиду архитектуры приложения все существующие модальные окна загружались во время первоначальной загрузки. Начав загружать их лениво, нам удалось сократить на 20% время начальной загрузки.
Однако в моем текущем проекте уровень вложенности невероятен. На одной странице могут быть скрыты буквально десятки потенциально ленивых компонентов. Ранее, размер одной из самых посещаемых страниц моего приложения составлял 3,5 МБ. Но когда я начал лениво загружать все подобные компоненты, размер этой страницы уменьшился до 1,1 МБ - что составило 68% улучшение.
Но, опять же, мы можем пойти дальше. Преимущество ленивой загрузки в том, что мы не ограничены только загрузкой компонентов. Мы также можем лениво загружать вспомогательные методы, классы и даже NPM пакеты.
Например, в моем проекте, если у пользователя есть роль администратора, он может выполнять некоторую логику из консоли инструментов разработки, чтобы упростить тестирование. И эта логика загружается, только если роль активна.
Или, если только для определенной кнопки требуется использовать функцию из библиотеки NPM, она загружается только при нажатии на кнопку.
Итак, если вы когда-нибудь думали, что отложенная загрузка связана только с загрузкой страниц, вам стоит рассмотреть дополнительные способы применения lazy loading. А теперь правило:
? Ленивая загрузка должна использоваться не только для страниц. Вы можете лениво загружать модальные окна, боковые панели, pop-up элементы, разделы вкладок и даже вспомогательные методы и NPM пакеты.
Конец
На этом все. Спасибо, что присоединились ко мне в нашем стремлении сделать наши веб-приложения бесконечно ленивыми. Если у вас есть какие-либо вопросы, не стесняйтесь задавать их в комментариях.
И, чтобы подвести итог этой статье, давайте перечислим правила, которые мы узнали сегодня:
? В большинстве случаев вам стоит по умолчанию использовать ленивую загрузку для всех страниц приложения. Независимо от того, сколько страниц в проекте и насколько велики эти страницы в своем размере.
? Ленивая загрузка должна использоваться не только для страниц. Вы можете лениво загружать модальные окна, боковые панели, pop-up элементы, разделы вкладок и даже вспомогательные методы и NPM пакеты.
Но это было только начало. Вы можете прочитать следующую статью, чтобы погрузиться еще глубже:
Как сделать ваше приложение бесконечно ленивым – Часть 2: Графики зависимостей
Вот мои ссылки в социальных сетях: LinkedIn, Telegram, GitHub. До встречи ☺️
OlegZH
Встречный вопрос: а зачем нужно загружать код? И, вообще, зачем нужен какой-то значительный трафик? Что такого большого на странице?