Files
progcode/editorial/agent-rewrites/301.json
T

8 lines
18 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 301,
"slug": "editorial-2019-08-field-frontend-performance",
"title": "Пустой первый экран при быстром HTML: как найти задержку в цепочке загрузки",
"excerpt": "Сервер отвечает быстро, но пользователь видит пустой каркас. Разбираем, как отделить позднее обнаружение ресурса, работу JavaScript, CSS и decode изображения и проверить исправление без выдуманных production-цифр.",
"contentHtml": "<p>Сервер отвечает за 180 мс, HTML уже пришёл, но пользователь всё ещё видит фон и пустой каркас. Карточка товара появляется позже: вместе с названием, ценой и изображением. Команда сжимает hero, уменьшает бандл или ставит preload, но экран почти не меняется. Цена ошибки — не только потерянные миллисекунды. Каждый случай запускает новую случайную правку, усложняет загрузочный путь и оставляет следующему разработчику неверную причину.</p>\n<p>Тезис простой: быстрый ответ HTML не равен быстрому полезному экрану. Нужно найти первую зависимость, которая удерживает именно этот экран. Ресурс может поздно обнаружиться, ждать CSS, ждать свободный main thread, пройти decode или быть скрыт классом загрузки. Эти причины похожи в браузере, но требуют разных проверок и действий.</p>\n<h2>Сначала определите полезный экран</h2>\n<p>Назовите результат до открытия DevTools. В учебном примере полезный экран содержит название товара, цену и hero рядом с кнопкой заказа. Spinner и пустой skeleton не считаются результатом: они показывают состояние приложения, но не дают пользователю нужной информации. Зафиксируйте DOM-признаки: видимый <code>[data-product-card]</code>, непустой <code>[data-product-title]</code>, цену и готовое изображение.</p>\n<p>Зафиксируйте один URL, viewport, вариант сборки и режим кэша. Повторите Reload и сохраните Network, screenshot и запись Performance. Лабораторные числа ниже — учебный пример. Они не описывают реальный сайт, устройство или production-выборку.</p>\n<h2>Механизм: экран ждёт зависимые границы</h2>\n<p>Браузер получает документ, строит DOM, находит стили и скрипты, выполняет JavaScript, рассчитывает геометрию, декодирует изображения и рисует кадр. Часть работы идёт параллельно. Но полезный элемент появляется только после своих зависимостей. Если URL hero появляется после запуска приложения, сжатие готового файла не исправит позднее обнаружение. Если запрос заканчивается рано, но main thread занят синхронным bootstrap, сеть не является первой причиной. Если изображение готово, но класс <code>is-loading</code> скрывает карточку, ищите условие рендера.</p>\n<p>Разделите путь на владельцев: документ и сеть, CSS, JavaScript на main thread, DOM и layout, изображение от запроса до paint. Не суммируйте полосы автоматически. Они могут перекрываться. Ищите разрыв между фактом «ресурс готов» и фактом «полезный элемент виден».</p>\n<table><thead><tr><th scope=\"col\">Полоса</th><th scope=\"col\">Данные учебного профиля</th><th scope=\"col\">Можно утверждать</th><th scope=\"col\">Нельзя утверждать</th></tr></thead><tbody><tr><td>Документ и сеть</td><td>180 мс, 18 КБ</td><td>Ответ документа выделен отдельной границей</td><td>Что сервер любого сайта отвечает за 180 мс</td></tr><tr><td>JavaScript</td><td>165 мс, 96 КБ</td><td>Есть отдельная работа parse и execute</td><td>Что любой такой bundle блокирует ровно 165 мс</td></tr><tr><td>CSS</td><td>90 мс, 24 КБ</td><td>Стили имеют собственный этап готовности</td><td>Что stylesheet всегда блокирует весь интервал</td></tr><tr><td>Hero</td><td>45 мс, 72 КБ</td><td>У изображения есть путь request, decode и paint</td><td>Что responseEnd равен моменту видимости</td></tr></tbody></table>\n<p>Таблица задаёт язык, но не даёт диагноза. Фраза «hero поздний» должна означать конкретный факт: запрос стартовал после <code>app.js</code>, decode закончился после нужного кадра или изображение готово, но DOM его скрывает.</p>\n<h2>Наблюдаемый пример: отделяем ресурс от рендера</h2>\n<p>Проверка выполняется после Reload в локальном учебном профиле. Она помогает увидеть состояние, но не заменяет trace. Поздняя ручная проверка не доказывает, что состояние было таким во время первого paint.</p>\n<pre><code>const hero = document.querySelector('[data-product-hero]');\nconst card = document.querySelector('[data-product-card]');\nconst title = document.querySelector('[data-product-title]');\nconst price = document.querySelector('[data-product-price]');\nconst css = document.querySelector('link[href*=app.css]');\nconst rect = card?.getBoundingClientRect();\nconst inViewport = rect &amp;&amp; rect.width &gt; 0 &amp;&amp; rect.height &gt; 0 &amp;&amp; rect.bottom &gt; 0 &amp;&amp; rect.right &gt; 0 &amp;&amp; rect.top &lt; innerHeight &amp;&amp; rect.left &lt; innerWidth;\nconsole.table({ heroSrc: hero?.currentSrc || '', heroComplete: hero?.complete || false, heroNaturalWidth: hero?.naturalWidth || 0, stylesheetReady: Boolean(css?.sheet), cardHidden: card?.classList.contains('is-loading') || false, inViewport: Boolean(inViewport) });\nif (hero?.complete &amp;&amp; hero.naturalWidth &gt; 0 &amp;&amp; title?.textContent?.trim() &amp;&amp; price?.textContent?.trim() &amp;&amp; !card?.classList.contains('is-loading') &amp;&amp; inViewport) {\n performance.mark('product-card-visible');\n}</code></pre>\n<p><code>img.complete</code> показывает, что загрузка завершилась, в том числе с ошибкой, поэтому его проверяют вместе с <code>naturalWidth</code>. Прямоугольник подтверждает геометрическую видимость в момент ручной проверки, но не заменяет screenshot и trace первого paint. <code>css.sheet</code> показывает наличие объекта таблицы стилей, но не её стоимость для layout. User Timing ставит именованную границу только после явно заданного условия; её нужно сопоставить с trace.</p>\n<p>Если <code>heroSrc</code> пуст, ищите источник URL: HTML, состояние приложения, CSS или API. Если URL появился после bootstrap, причина — позднее обнаружение. Если URL был в HTML и запрос начался рано, смотрите main thread и paint. Если изображение готово, а <code>cardHidden</code> равен <code>true</code>, проверяйте переход состояния и отрицательный путь.</p>\n<h2>Симптомы и минимальные проверки</h2>\n<table><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>Hero начинается после app.js</td><td>URL создаёт приложение</td><td>Сравнить initiator и момент появления URL</td><td>Передать критичный URL в HTML или изменить контракт данных; preload применять только после подтверждения</td></tr><tr><td>Hero готов, но экран ждёт длинный scripting</td><td>Bootstrap выполняет некритичную работу</td><td>Открыть Bottom-up и Call Tree до screenshot</td><td>Отложить второстепенный виджет, разбить синхронную работу, оставить fallback</td></tr><tr><td>CSS завершается поздно</td><td>Стиль найден поздно или конкурирует за сеть</td><td>Проверить <code>link</code>, <code>@import</code> и trace</td><td>Убрать import-цепочку после проверки визуального результата</td></tr><tr><td>Изображение готово, но виден skeleton</td><td>DOM или класс состояния скрывает карточку</td><td>Сопоставить атрибуты, переход состояния и screenshot</td><td>Разделить готовность данных и рекомендаций; не скрывать цену и заказ</td></tr><tr><td>Сеть быстрая, кадр поздний</td><td>Decode, layout или paint ждут main thread</td><td>Сопоставить responseEnd, decode, layout и кадр</td><td>Сократить лишние чтения и записи layout; проверить размер изображения</td></tr></tbody></table>\n<h2>Как читать запись без догадок</h2>\n<p>Начинайте с screenshot и DOM, а не с самого большого файла. От полезного элемента идите назад: какой URL, стиль, состояние и код нужны, чтобы он стал видимым. Затем идите от документа вперёд: когда стартовали CSS, app.js и hero. Так вес ресурса не подменяет его место в критическом пути.</p>\n<p>В Chrome DevTools откройте Performance и запишите один Reload. В Network проверьте initiator hero и порядок запросов. В main thread найдите длинные задачи до screenshot. При наличии source map сопоставьте функцию с исходным модулем. При отсутствии source map не придумывайте имя компонента: запишите скомпилированный ресурс и отдельный вопрос на восстановление соответствия.</p>\n<p>Сделайте один обратимый эксперимент. Например, отключите второстепенный виджет локальным флагом и повторите тот же Reload. Если screenshot сдвинулся, измерьте исчезнувшую работу и проверьте карточку без виджета. Если экран не изменился, верните эксперимент и исключите гипотезу. Не удаляйте половину bootstrap и не называйте результат оптимизацией без контрольного сравнения.</p>\n<figure><img src='/assets/editorial/2019/frontend-performance-diagnosis-2019.svg' alt='Диагностика первого экрана: сеть, JavaScript, CSS, decode и paint'><figcaption>Диагностика начинается с полезного screenshot. Каждая ветвь получает свою проверку. Иллюстрация показывает учебную модель и не является trace конкретного продукта.</figcaption></figure>\n<h2>Учебный фикс с отрицательным путём</h2>\n<p>Предположим, trace подтвердил: рекомендации синхронно строятся до карточки, хотя цена и заказ от них не зависят. Учебный вариант переносит рекомендации после показа основной карточки. Он не заявляет производственный результат и не задаёт универсальный API.</p>\n<pre><code>showProductCard({ title, price, hero });\nrequestAnimationFrame(() =&gt; performance.mark('product-card-frame'));\nloadRecommendationsLater().then(renderRecommendations).catch(() =&gt; { renderRecommendationsFallback(); });</code></pre>\n<p>После изменения проверяют не только исчезновение scripting. Карточка должна показать цену, кнопку и изображение без рекомендаций. Ошибка второстепенного запроса не должна скрыть основной товар. Если перенос меняет порядок аналитики, focus, доступность или layout, это новый контракт. Его проверяют на медленной сети и при отказе запроса.</p>\n<h2>Порядок действий</h2>\n<ol><li>Опишите полезный первый экран и проверяемые DOM-признаки.</li><li>Зафиксируйте URL, viewport, сборку, кэш и повторяемый Reload.</li><li>Сохраните Network, screenshot и Performance trace до изменения.</li><li>Проверьте момент обнаружения CSS, JavaScript и hero, затем main thread, decode, layout и paint.</li><li>Назовите один разрыв: позднее обнаружение, scripting, стиль, состояние DOM или изображение.</li><li>Проведите один узкий обратимый эксперимент с ожидаемым сдвигом.</li><li>Повторите сценарий и проверьте полезный экран, fallback, ошибку и визуальную стабильность.</li><li>Запишите лабораторный вывод с условиями. Полевые числа добавляйте только после отдельного сбора данных.</li></ol>\n<h2>Ограничения и критерий готовности</h2>\n<p>Учебные значения не являются измерением production. Один trace не описывает все устройства, сети и состояния кэша. Размер бандла не равен времени блокировки, <code>responseEnd</code> не равен paint, а пользовательская метрика не появляется от одного <code>performance.mark</code>. Если причина не доказана, честный результат — список исключённых гипотез и следующий точный вопрос.</p>\n<p>Правка готова, когда один сценарий повторяется до и после изменения, полезный экран проходит DOM- и screenshot-критерий, подтверждена конкретная причина, а отрицательный путь не скрывает основную функцию. В отчёте есть условия, trace, изменённая граница и предел вывода. Формулировка «в лабораторном Reload карточка стала видимой раньше после переноса второстепенной работы» проверяема. Формулировка «всем пользователям стало быстрее» требует другой выборки.</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href='https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Navigation_timing' target='_blank' rel='noopener'>MDN: Navigation timing</a> — описание Navigation Timing и Performance Entry API.</li><li><a href='https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming' target='_blank' rel='noopener'>MDN: PerformanceResourceTiming</a> — временные и размерные поля ресурсов и ограничение cross-origin.</li><li><a href='https://developer.chrome.com/docs/devtools/performance' target='_blank' rel='noopener'>Chrome for Developers: Analyze runtime performance</a> — работа с Performance panel и runtime trace.</li></ul>\n"
}