Суть Читай реальные React/JS-сниппеты разбивки — границу dynamic import, раскладку lazy+Suspense и обработчик prefetch-on-hover — и выбирай поведение или самый высокорычажный фикс.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 13 min
Баги разбивки живут в том, где стоит граница и когда запрашивается чанк. Читай каждый сниппет, прослеживай тайминг запросов, который браузер реально выполняет, и выбирай фикс, который senior сделает первым.
Цель
Потренируй чтение точек разбивки так, как ты их дебажишь в проде: найди, где реально стоит граница dynamic import, увидь, когда Suspense триггерит загрузку, и заметь waterfall, который упредил бы hint.
Сниппет 1 — где стоит граница
// A: импорт на верхнем уровне модуля — бандлится в родительский чанкimport { HeavyEditor } from "./HeavyEditor";function EditPanel({ open }) { if (!open) return null; return <HeavyEditor />;}// B: dynamic import внутри гейтаconst HeavyEditor = React.lazy(() => import("./HeavyEditor"));function EditPanel({ open }) { if (!open) return null; return ( <Suspense fallback={<Spinner />}> <HeavyEditor /> </Suspense> );}
Викторина
Completed
Редактор весит 200 KB и открывается только по клику на Edit. Что реально отгружает каждая версия и какая здесь правильная?
Heads-up Tree-shaking убирает неиспользуемые экспорты на этапе сборки, а не код, достижимый по runtime-условию. Статический верхнеуровневый импорт всегда бандлится в родительский чанк; только dynamic import() создаёт точку разбивки.
Heads-up React.lazy — ровно инструмент для тяжёлого, не-на-первой-отрисовке, гейтнутого взаимодействием виджета. Анти-паттерн — lazy-загрузка мелкого или над-сгибом, а не отсрочка редактора на 200 KB за кликом.
Heads-up Условный рендер управляет тем, монтируется ли компонент, а не тем, скачивается ли его код. Код приходит в том чанке, в который был забандлен — только import() переносит его в отдельный чанк.
На телефоне с RTT 150 ms в каком порядке загружаются чанки Dashboard и Chart и почему это важно?
Heads-up Объявление lazy-компонентов вместе не запрашивает их вместе. Внутренний чанк обнаруживается только после рендера внешнего, поэтому загрузки последовательны — это и есть waterfall.
Heads-up Fallback'и Suspense управляют тем, что рендерится во время ожидания, а не порядком загрузки. Родительский чанк всегда обнаруживается и грузится первым; вложенность не разворачивает зависимость.
Heads-up Оба здесь на пути начального рендера, поэтому оба запрашиваются при загрузке. Проблема не в том, грузятся ли они, а в том, что они грузятся один за другим.
Что делает обработчик onMouseEnter и какой failure mode, если его убрать?
Heads-up import() кэширует промис модуля; второй вызов резолвится из того же in-flight или завершённого запроса. Вызов на hover прогревает кэш, чтобы клик не платил за round trip.
Heads-up import() остаётся точкой разбивки — Settings остаётся отдельным чанком. Обработчик лишь меняет, когда этот чанк запрашивается (на hover), не сливая его в родительский.
Heads-up Обработчик запускает загрузку и сразу возвращается; он не блокирует. Это дешёвый спекулятивный prefetch, выравнивающий waterfall на момент клика.
Итог
Три вещи решают поведение разбивки в коде: статический верхнеуровневый импорт всегда бандлится в родительский чанк, поэтому только import() создаёт точку разбивки — и runtime-условие этого не меняет. Вложенные границы React.lazy обнаруживаются последовательно, поэтому каждый уровень добавляет round trip на канале с высоким RTT. А запуск import() заранее (на hover или idle) прогревает кэш чанка, чтобы итоговое монтирование пропустило waterfall и спиннер. Читай, где стоит граница и когда триггерится загрузка, затем сдвигай триггер раньше, а не сливай чанк обратно.