Веб-производительность и параллельная vs. waterfall загрузка
Ранее я уже писал о том, нужны ли нам ещё инструменты для сборки.
С HTTP/2 можно одновременно загружать множество небольших CSS- и JS-файлов (раньше их было не более двух за раз), что лишает нас некоторых преимуществ объединения всего в один файл.
На прошлой неделе Брайан Дэвид и Брайан ЛеРу провели на Mastodon интересную беседу об инструментах сборки и пакетировании ES-модулей, и я подумал, что сейчас самое время поговорить об этом немного подробнее.
С HTTP/1 браузеры могут загружать до двух файлов на домен одновременно. Если они сталкиваются с файлом JavaScript, они прекращают загрузку других файлов до тех пор, пока JS-файл не будет загружен, скомпилирован и обработан.
Это создаёт узкие места, где при загрузке большого количества небольших файлов "процесс рукопожатия" HTTP увеличивает задержку. Объединение множества маленьких файлов в один большой позволяло избежать этой проблемы.
С HTTP/2 браузеры будут загружать множество файлов одновременно. В результате один большой файл может оказаться хуже, чем несколько маленьких, поскольку все эти маленькие файлы могут быть загружены одновременно.
Значит ли это, что пакетирование бессмысленно? Именно это и обсуждали Брайаны.
Допустим, у вас есть файл ES-модуля, который выглядит следующим образом…
import {add, subtract} from './calculator.js';
import {get, getAll} from './dom.js';
import {getData, setData} from './api.js';
// Выполняется некий код...
Будет ли здесь выгода от пакетирования? Как всегда, все зависит от ситуации!
Если calculator.js
, dom.js
и api.js
включают в себя функции, отличные от импортируемых, то пакетирование уменьшит объем передаваемого JavaScript за счёт процесса, называемого "встряхиванием дерева" (tree shaking).
Большинство современных бандлеров включают только те функции, которые импортируются и используются при создании JS-файла пакета. При использовании нативных для браузера ES-модулей загружается весь импортируемый файл, даже если вы используете только одну или две функции из него.
Если любой из импортируемых файлов имеет собственный вложенный import
файлов, то это то, что Брайан Дэвид называет "водопадной загрузкой" (waterfall download).
// api.js
import {endpoint} from './urls.js';
import {parseData} from './helpers.js';
// Выполнить код...
export {getData, setData};
Браузер загружает основной файл JavaScript, компилирует его, обрабатывает и замечает, что он импортирует ещё три файла. Он загружает, компилирует и обрабатывает их. Затем он замечает, что api.js
имеет несколько собственных импортов. Он загружает их, компилирует, обрабатывает и т.д.
Процесс компиляция/обработка/повторение может вносить значительные задержки. Объединение файлов избавляет от этих дополнительных действий и может значительно ускорить работу большого приложения.
Если импортируемые файлы не содержат других функций, которые вы не используете, и если они не имеют собственных импортов, то в основном вы просто параллельно загружаете несколько файлов, и все.
В этом случае бандлер не так уж и полезен. Но, по моему опыту, большинство кодовых баз устроены не так.