Часть 1.
Итак, в конце прошлой части мы оставили нового пользователя наедине
со единственным JS-файлом, не включающем ничего лишнего. Стал ли при этом
пользователь счастливее? Ничуть. Наоборот, в среднем
пользователь1 стал более несчастным, чем раньше, а причина этому —
увеличившееся время загрузки страницы.
Время загрузки ресурса через HTTP складывается из следующих основных элементов:
Итак, в конце прошлой части мы оставили нового пользователя наедине
со единственным JS-файлом, не включающем ничего лишнего. Стал ли при этом
пользователь счастливее? Ничуть. Наоборот, в среднем
пользователь1 стал более несчастным, чем раньше, а причина этому —
увеличившееся время загрузки страницы.
Приятная теория
… вы находитесь на воздушном шаре!…
Время загрузки ресурса через HTTP складывается из следующих основных элементов:
- время отсылки запроса на сервер T1 — для большинства2 запросов величина практически постоянная;
время формирования ответа сервера — для статических ресурсов, которые мы сейчас и рассматриваем,
пренебрежимо мало;
время получения ответа сервера T2, в свою очередь состоящее из постоянной для сервера сетевой задержки L и
времени получения ответа R, прямо пропорциональному размеру ресурса.
В свою очередь, время загрузки страницы будет состоять из времени загруки HTML-кода и всех
внешних ресурсов: изображений, CSS и JS файлов. Основная проблема в том, что
CSS и JS-файлы грузятся последовательно3. В этом случае общение с сервером выглядит так:
- запросили страницу - получили HTML - запросили ресурс A: T1 - получили ресурс A: L + R(A) - запросили ресурс B: T1 - получили ресурс B: L + R(B) - запросили ресурс C: T1 - получили ресурс C: L + R(C)
Общие временные затраты при этом составят 3(T1+L) + R(A+B+C)
Объединяя файлы, мы уменьшаем количество запросов на сервер:
- запросили страницу - получили HTML - запросили ресурс A+B+C: T1 - получили ресурс A+B+C: L + R(A + B + C)
Очевидна экономия в 2(T1 + L).
Для 20ти ресурсов эта экономия составит уже 19(T1 + L). Если взять достаточно типовые сейчас для
домашнего / офисного интернета значения скорости в 256 кбит/с и пинга ~20–30 мс, получим экономию
в 950 мс — одну секунду загрузки страницы. У людей же, пользующихся мобильным или
спутниковым интернетом с пингом более 300 мс, разницы времён загрузки страниц составит
6–7 секунд.
На первый взгляд, теория говорит, что загрузка страниц должна стать быстрее. В чём же она разошлась
с практикой?
Суровая реальность
Хотели как лучше, а получилось как всегда.
Пусть у нашего сайта есть три страницы P1, P2 и P3, поочерёдно запрашиваемые новым пользователем.
P1 использует ресурсы A, B и C, P2 — A, С и D, а P3 — A, С, E и F.
Если ресурсы не объединять, получаем следующее:
- P1 — тратим время на загрузку A, B и C
- P2 — тратим время на загрузку только D
- P3 — тратим время на загрузку E и F
Если мы слили воедино абсолютно все
JS-модули сайта, получаем:
- P1 — тратим время на загрузку (A+B+C+D+E+F)
- P2 — внешние ресурсы не требуются
- P3 — внешние ресурсы не требуются
Результатом становится увеличение времени загрузки самой первой страницы,
на которую попадает пользователь. При типовых значениях скорости/пинка мы начинаем прогрывать уже
при дополнительном объеме загрузки в 2–3 килобайта.
Если мы объединили только модули, необходимые для текущей страницы, получаем следующее:
- P1 — тратим время на загрузку (A+B+C)
- P2 — тратим время на загрузку (A+C+D)
- P3 — тратим время на загрузку (A+С+E+F)
Каждая отдельно взятая страница при пустом кеше будет загружаться быстрее, но все они вместе —
медленнее, чем в исходном случае.
Получаем, что слепое использование модного сейчас объединения ресурсов часто только ухудшает
жизнь пользователя.
Решение
— можно ли проглотить бильярдный шар?
— можно, но, как правило, не нужно.
Конечно же, выход из сложившегося положения есть4. В большинстве случаев
для получения реального выигрыша достаточно выделить «ядро» — набор модулей, используемых на всех
(или по крайней мере на часто загружаемых) страницах сайта. Например, в нашем примере достаточно выделить
в ядро ресурсы A и B, чтобы получить преимущество:
- P1 — тратим время на загрузку (A + B) и C
- P2 — тратим время на загрузку D
- P3 — тратим время на загрузку (E + F)
Вдумчивый читатель сейчас возмутится и спросит: «А что, если ядра нет? Или ядро получается слишком
маленьким?». Спешу заверить — это легко5 решается вручную выделением 2–3 независимых
групп со своими собственными ядрами. При желании задачу разбиения можно формализовать и
получить точное машинное решение — но это обычно не нужно; руководствуясь простейшим правилом — чем
больше ядро, тем лучше, можно добиться вполне приличного результата.
Как говорят на Хабре: «Каков же месседж этой статьи?» Ограничимся жалкими тремя пунктами:
- бездумное следование модным веяниям приведёт к бесполезной работе;
- модные веяние часто несут в себе неплохие идеи;
- если вы любите своих пользователей — объединяйте ресурсы с умом.
1 с пустым кэшем, конечно.
2 подразумевается, что большая часть запросов — это GET-запросы с разумной длиной URL.
Длина такого запроса примерно постоянна, и составляет ~450–600 байт.
3 по крайней мере, пока они находятся на одном хосте.
4 и, что приятно видеть из комментов, используется.
5 «У нас есть такие приборы — но мы вам про них не расскажем». Придётся поверить на слово.