8 lines
18 KiB
JSON
8 lines
18 KiB
JSON
{
|
||
"index": 20,
|
||
"slug": "editorial-2027-06-mechanism-performance-capstone",
|
||
"title": "Waterfall без иллюзий: как доказать, что ресурс задерживает первый экран",
|
||
"excerpt": "Длинная полоса в waterfall не равна причине задержки. Разбираем зависимость ресурса, инициатора и потребителя, а затем проверяем одно изменение на учебной странице.",
|
||
"contentHtml": "<p>Пользователь видит пустой или неполный первый экран. В DevTools рядом с ним растягивается полоса шрифта или изображения. Команда объявляет этот ресурс виновником и меняет порядок загрузки. Через релиз экран не ускоряется, зато появляется лишний preload, вспышка нестилизованного текста или гонка между скриптами. Цена ошибки — не только потерянные миллисекунды. Вы меняете контракт загрузки, не доказав, что страницу действительно задерживал этот запрос.</p>\n<p>Waterfall показывает время сетевой работы. Он не показывает причинность сам по себе. Ресурс влияет на экран только тогда, когда его результат нужен конкретной зависимости: parser ждёт script, CSS нужен для построения стилей, layout ждёт шрифт, а компонент ждёт данные. Поэтому тезис статьи простой: ищите не самую длинную полосу, а цепочку «инициатор → ресурс → потребитель → наблюдаемый эффект».</p>\n<h2>Что именно нужно доказать</h2>\n<p>У каждой записи ресурса есть временная и причинная часть. <code>startTime</code> говорит, когда запрос начал работу, а <code>responseEnd</code> — когда браузер получил последний байт. <code>initiatorType</code> помогает понять, кто начал запрос: parser, script, css, fetch или другой источник. Эти поля описывают наблюдение. Они ещё не отвечают, ждал ли результат первый экран.</p>\n<p>Для причинности добавьте три вопроса. Какой элемент или код потребляет ответ? В какой момент он потребляет его? Что изменится, если ответ придёт позже или не придёт вовсе? Если на последний вопрос нет проверяемого ответа, запись остаётся кандидатом. Её нельзя называть узким местом.</p>\n<div class=\"table-scroll\"><table><caption>Симптом → причина → проверка → действие</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>CSS заканчивается поздно, первый экран пустой</td><td>Стили нужны до первого layout</td><td>Сопоставить тег link, DOM и момент первого визуального результата</td><td>Уменьшить критический CSS или разделить его, затем проверить layout shift</td></tr><tr><td>Script начинается рано и долго выполняется</td><td>Parser или главный поток ждёт выполнение</td><td>Проверить атрибуты, initiator и long task</td><td>Применить defer или разбить работу только после проверки зависимостей</td></tr><tr><td>Шрифт имеет длинную полосу, но контент уже виден</td><td>Шрифт не нужен первому экрану или заменяется fallback</td><td>Сравнить момент ответа шрифта с визуальным результатом и layout</td><td>Не добавлять preload; проверить font-display и потребителя</td></tr><tr><td>JSON заканчивается поздно, карточки пустые</td><td>Компонент ждёт fetch</td><td>Найти вызов, состояние ожидания и время отображения данных</td><td>Оптимизировать запрос или skeleton, не меняя случайные сетевые приоритеты</td></tr><tr><td>Одна запись отсутствует в отчёте</td><td>Нет поддержки поля, кросс-доменное ограничение или буфер очищен</td><td>Проверить браузер, Timing-Allow-Origin и момент чтения</td><td>Пометить «нет данных» и выбрать другой сигнал</td></tr></tbody></table></div>\n<figure><img src=\"/assets/editorial/2027/performance-capstone-2027-evidence-boundary-matrix.svg\" alt=\"Матрица проверки ресурса: инициатор, время, статус блокировки, потребитель и действие\" loading=\"lazy\" /><figcaption>Иллюстрация связывает строку waterfall с проверкой зависимости. Размер ресурса — только один из входов, а не итоговый вердикт.</figcaption></figure>\n<h2>Модель загрузки на конкретном примере</h2>\n<p>Рассмотрим страницу с таким HTML:</p>\n<pre><code><head>\n <link rel=\"stylesheet\" href=\"/app.css\">\n <script src=\"/vendor.js\" defer></script>\n <script src=\"/analytics.js\" async></script>\n</head>\n<body>\n <main id=\"catalog\"></main>\n <script src=\"/catalog.js\" defer></script>\n</body></code></pre>\n<p><code>app.css</code> может влиять на первый layout. <code>vendor.js</code> и <code>catalog.js</code> загружаются параллельно с разбором HTML, но выполняются после разбора документа и сохраняют порядок между собой. <code>analytics.js</code> выполняется, когда загрузится, поэтому не должен зависеть от глобального объекта, который создаёт другой script. Его длинная полоса не объясняет задержку каталога, если аналитика не участвует в рендере.</p>\n<p>Учебный наблюдатель ниже читает доступные записи после загрузки страницы. Он не меняет сеть и не утверждает, что каждая ранняя запись блокирует экран. Пример предназначен для локальной или тестовой страницы. Он показывает границу данных: если поле не поддерживается, результат будет <code>unknown</code>, а не выдуманный статус.</p>\n<pre><code>const navigation = performance.getEntriesByType('navigation')[0];\nconst domEnd = navigation?.domContentLoadedEventEnd ?? Infinity;\n\nconst resources = performance\n .getEntriesByType('resource')\n .filter((entry) => entry.startTime < domEnd)\n .map((entry) => ({\n name: new URL(entry.name).pathname,\n initiator: entry.initiatorType || 'unknown',\n start: Math.round(entry.startTime),\n end: Math.round(entry.responseEnd),\n duration: Math.round(entry.duration),\n blocking: entry.renderBlockingStatus ?? 'unknown',\n }));\n\nconsole.table(resources);</code></pre>\n<p>Фильтр ограничивает список ресурсами, которые начали работу до <code>DOMContentLoaded</code>. Это удобная граница для первичного поиска, но не доказательство готовности экрана. DOM мог закончить разбор, пока изображение, шрифт или отрисовка ещё продолжаются. Для визуального симптома нужен отдельный браузерный прогон и понятный элемент, который считается первым полезным результатом.</p>\n<p>Дальше найдите инициатор в HTML или исходном коде. Для <code>parser</code> проверьте тег. Для <code>script</code> найдите вызов fetch, импорт или создание элемента. Для <code>css</code> проверьте правило и используемый шрифт. Затем назовите потребителя. Формулировка «CSS заканчивается поздно» слишком слабая. Формулировка «карточки не получают стили до первого layout, потому что link указывает на полный файл» уже задаёт проверяемое действие.</p>\n<h2>Порядок проверки</h2>\n<ol><li>Зафиксируйте URL, браузер, viewport, сеть, режим кэша и состояние авторизации. Без этих условий две записи waterfall нельзя честно сравнить.</li><li>Опишите симптом словами пользователя: пустой первый экран, поздние карточки, скачок текста или задержка интерактивности. Запишите момент и элемент, а не только общую длительность.</li><li>Снимите navigation entry и resource entries в одном прогоне. Сохраните исходные значения, включая URL, инициатор, время начала, конец ответа и доступный статус блокировки.</li><li>Отберите ранние записи по типу и времени. Для каждой найдите конкретный тег, вызов или CSS-правило, которое запустило запрос.</li><li>Назовите потребителя и проверьте его зависимость. Удалите ресурс, отложите его в учебной копии или замените ответом-заглушкой. Если симптом не меняется, гипотеза не подтверждена.</li><li>Измените один фактор: defer, разделение CSS, порядок запроса или код потребителя. Не смешивайте изменение сети с изменением рендера.</li><li>Повторите прогон в тех же условиях. Сравните визуальный критерий, long tasks и нужный участок waterfall. Сокращение отдельной полосы без изменения симптома не считается успехом.</li></ol>\n<h2>Когда популярные исправления вредят</h2>\n<p><code>preload</code> запускает запрос раньше. Это полезно для действительно критичного ресурса, но лишний preload конкурирует с HTML, CSS и данными. Укажите правильный <code>as</code> и проверьте, что документ использует ответ. Иначе браузер предупреждает о неиспользованной загрузке, а критический путь становится шире.</p>\n<p><code>async</code> освобождает parser, но отдаёт порядок выполнения сети. Он подходит для независимого кода. Если script читает объект, который создаёт другой script, или меняет DOM до инициализации компонента, появится гонка. <code>defer</code> сохраняет порядок отложенных скриптов, но не уменьшает размер файла и не доказывает, что работа главного потока стала короче.</p>\n<p>Разделение CSS снижает ранний объём только при точной границе. Ошибка даёт FOUC, неверный порядок правил или layout shift. Отложенный шрифт может убрать блокировку, но изменить переносы строк и высоту блока. Каждое исправление нужно оценивать по тому же потребителю, который породил исходный симптом.</p>\n<p>Отрицательный путь важен. Если waterfall показывает поздний запрос, но первый экран от него не зависит, остановите оптимизацию этой строки. Если данные недоступны из-за политики браузера, не подставляйте ноль. Если замер меняет состояние кэша, не сравнивайте его с холодным запуском. Честный вывод «причина не доказана» экономит больше времени, чем уверенная правка не того слоя.</p>\n<h2>Ограничения и критерий готовности</h2>\n<p>Resource Timing зависит от браузера и политики доступа. Кросс-доменные значения могут быть скрыты без <code>Timing-Allow-Origin</code>. <code>renderBlockingStatus</code> может отсутствовать. <code>DOMContentLoaded</code> не равен FCP или LCP, а локальная сеть не воспроизводит мобильное устройство. Записи Performance API не описывают серверную очередь, весь главный поток и пользовательский опыт одной цифрой.</p>\n<p>Учебный код не даёт production-результатов. Его задача — показать форму проверки. Для реального решения нужны повторяемые прогоны в целевых браузерах, сохранённые условия и выбранный визуальный критерий. Не называйте ресурс блокирующим только потому, что он большой, ранний или заметный на скриншоте DevTools.</p>\n<p>Работа готова, когда для заявленного симптома есть исходная запись и повторяемый критерий, у спорного ресурса найден инициатор и потребитель, одно изменение прошло в тех же условиях, а отрицательный путь тоже описан. После изменения должен измениться именно наблюдаемый симптом или заранее выбранная метрика. Если меняется только длина полосы, расследование не закончено.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://www.w3.org/TR/2026/WD-navigation-timing-2-20260225/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C Navigation Timing Level 2</a> — рабочий черновик от 25 февраля 2026 года. Определяет навигационные временные метки; не сообщает значения метрик конкретного браузера и не заменяет полевой сбор.</li><li><a href=\"https://www.w3.org/TR/2026/CRD-resource-timing-20260420/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C Resource Timing</a> — Candidate Recommendation Draft от 20 апреля 2026 года. Определяет записи ресурсов и сетевые временные поля; не доказывает причинность задержки и не описывает серверную архитектуру.</li><li><a href=\"https://www.w3.org/TR/2025/CRD-performance-timeline-20250521/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C Performance Timeline</a> — Candidate Recommendation Draft от 21 мая 2025 года. Определяет PerformanceEntry и способы чтения записей; не задаёт пороги качества и не гарантирует одинаковый набор данных во всех движках.</li></ul>"
|
||
}
|