Files
progcode/editorial/agent-rewrites/020.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

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": 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>&lt;head&gt;\n &lt;link rel=\"stylesheet\" href=\"/app.css\"&gt;\n &lt;script src=\"/vendor.js\" defer&gt;&lt;/script&gt;\n &lt;script src=\"/analytics.js\" async&gt;&lt;/script&gt;\n&lt;/head&gt;\n&lt;body&gt;\n &lt;main id=\"catalog\"&gt;&lt;/main&gt;\n &lt;script src=\"/catalog.js\" defer&gt;&lt;/script&gt;\n&lt;/body&gt;</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) =&gt; entry.startTime &lt; domEnd)\n .map((entry) =&gt; ({\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>"
}