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

8 lines
21 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 показывает время сетевой работы, но не причинность. Ресурс влияет на экран только тогда, когда его результат нужен конкретной зависимости: обычный внешний script может приостановить разбор HTML, рендер может ждать таблицу стилей, layout — шрифт, а компонент — данные. Поэтому ищите не самую длинную полосу, а цепочку «инициатор → ресурс → потребитель → наблюдаемый эффект».</p>\n<h2>Причина начинается с потребителя</h2>\n<p>У записи ресурса есть временная и причинная часть. <code>startTime</code> показывает начало работы запроса, а <code>responseEnd</code> — момент, когда браузер получил последний байт. <code>initiatorType</code> описывает тип инициатора: например, <code>link</code> или <code>img</code> для HTML-элемента, <code>css</code> для URL из CSS, <code>script</code> для загрузки скрипта и <code>fetch</code> для Fetch API. Эти поля фиксируют наблюдение, но не сообщают, ждал ли результат первый экран.</p>\n<p>Для причинности ответьте на три вопроса. Какой элемент или код потребляет ответ? В какой момент он потребляет его? Что изменится, если ответ придёт позже или не придёт вовсе? Если на третий вопрос нет проверяемого ответа, запись остаётся кандидатом. Её нельзя называть узким местом.</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>Стили нужны до первого визуального результата</td><td>Сопоставить тег link, DOM и момент появления элемента</td><td>Уменьшить критический CSS или разделить его, затем проверить layout shift</td></tr><tr><td>Script начинается рано и долго выполняется</td><td>Разбор или главный поток ждёт выполнение</td><td>Проверить атрибуты, инициатор и 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>Оптимизировать запрос или состояние загрузки</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> может задержать отображение стилизованного содержимого. <code>vendor.js</code> и <code>catalog.js</code> загружаются во время разбора HTML, а классические скрипты с <code>defer</code> выполняются после разбора и сохраняют порядок между собой. <code>analytics.js</code> с <code>async</code> выполняется после загрузки независимо от порядка других скриптов. Поэтому он подходит для независимой аналитики, но не для кода, которому нужен ещё не созданный глобальный объект.</p>\n<p>У этой схемы есть два разных пути. CSS и отложенные скрипты могут влиять на то, когда компонент получит DOM и стили. Аналитика может иметь длинную сетевую полосу и не участвовать в рендере вовсе. Если убрать её из учебной копии и первый экран не изменится, полоса была коррелятом, а не причиной.</p>\n<p>Проверяйте не только загрузку, но и работу главного потока. Быстрый ответ сервера не помогает, если после него выполняется длинная синхронная задача. И наоборот, поздний ресурс может не менять картинку, если компонент уже показал полезный fallback. В обоих случаях ответ находится на границе ресурса и его потребителя.</p>\n<h2>Минимальный воспроизводимый замер</h2>\n<p>Учебный наблюдатель ниже запускается в консоли локальной или тестовой страницы после загрузки. Он не меняет сеть и не утверждает, что каждая ранняя запись блокирует экран. Полный <code>url</code> сохраняется намеренно: удаление query-параметров может смешать две разные версии ответа.</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 url: entry.name,\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\nconst firstContentfulPaint = performance\n .getEntriesByType('paint')\n .find((entry) =&gt; entry.name === 'first-contentful-paint');\n\nconsole.table(resources);\nconsole.table({\n domContentLoaded: navigation?.domContentLoadedEventEnd ?? 'unknown',\n firstContentfulPaint: firstContentfulPaint?.startTime ?? 'unknown',\n});</code></pre>\n<p>Фильтр ограничивает список ресурсами, которые начали работу до <code>DOMContentLoaded</code>. Это удобная граница для первичного поиска, но не доказательство готовности экрана: DOM мог закончить разбор, пока изображение, шрифт или отрисовка ещё продолжаются. <code>first-contentful-paint</code> фиксирует первое отображение текста или изображения, но не подтверждает, что пользователь увидел нужный блок. Если запись недоступна, сохраняйте <code>unknown</code> и измеряйте выбранный элемент браузерным сценарием.</p>\n<p>Дальше найдите инициатор в HTML или исходном коде. Для записи, инициированной HTML-элементом, проверьте тег. Для <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 и выбранный paint-сигнал в одном прогоне. Сохраните исходные значения, URL, инициатор, время и доступный статус блокировки.</li><li>Для ранней записи найдите конкретный тег, вызов или CSS-правило, которое запустило запрос. Затем назовите потребителя и его зависимость от ответа.</li><li>Отложите ресурс, замените ответ заглушкой или удалите его только в учебной копии. Если симптом не меняется, гипотеза не подтверждена; не исправляйте production по одной полосе.</li><li>Измените один фактор: <code>defer</code>, разделение CSS, порядок запроса или код потребителя. Не смешивайте изменение сети с изменением рендера.</li><li>Повторите прогон в тех же условиях. Сравните визуальный критерий, first contentful paint, long tasks и нужный участок waterfall. Сокращение отдельной полосы без изменения симптома не считается успехом.</li></ol>\n<p>Полезен и отрицательный контроль. Если вы проверяете гипотезу о шрифте, повторите прогон с тем же CSS, но с локальным fallback. Если проверяете JSON, оставьте тот же ответ и задержите только его доставку. Контроль должен менять ровно один фактор; иначе вы не узнаете, что именно повлияло на результат.</p>\n<h2>Почему популярные исправления дают обратный эффект</h2>\n<p><code>preload</code> запускает запрос раньше, но не делает ресурс автоматически критичным. Лишняя предварительная загрузка конкурирует с HTML, CSS и данными. Укажите правильный <code>as</code>, проверьте совпадение URL и убедитесь, что документ действительно использует ответ. Иначе браузер скачает ресурс, который не изменит экран.</p>\n<p><code>async</code> не гарантирует порядок выполнения. Это преимущество для независимого кода и источник гонок для зависимого. <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> и paint-записи могут отсутствовать в конкретной реализации. <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/navigation-timing-2/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C Navigation Timing Level 2</a> — интерфейс навигационных временных меток, включая <code>domContentLoadedEventEnd</code>. Это рабочий черновик: он описывает API, но не сообщает значения метрик конкретного сайта.</li><li><a href=\"https://www.w3.org/TR/resource-timing/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C Resource Timing</a> — записи ресурсов, <code>startTime</code>, <code>responseEnd</code>, <code>initiatorType</code> и <code>renderBlockingStatus</code>. Спецификация не доказывает причинность задержки и ограничивает часть полей политикой доступа.</li><li><a href=\"https://html.spec.whatwg.org/multipage/scripting.html#the-script-element\" target=\"_blank\" rel=\"noopener noreferrer\">WHATWG HTML Standard: script element</a> — нормативная модель обычных, <code>async</code> и <code>defer</code>-скриптов. Реальное влияние зависит от зависимостей кода и работы главного потока.</li><li><a href=\"https://html.spec.whatwg.org/multipage/links.html#link-type-preload\" target=\"_blank\" rel=\"noopener noreferrer\">WHATWG HTML Standard: preload</a> — правила предварительной загрузки и назначения ресурса через <code>as</code>. Спецификация не гарантирует выигрыш без проверки потребителя и конкуренции за сеть.</li><li><a href=\"https://www.w3.org/TR/paint-timing/\" target=\"_blank\" rel=\"noopener noreferrer\">W3C Paint Timing</a> — определения записей <code>first-paint</code> и <code>first-contentful-paint</code>. Это ориентир начала отображения, а не доказательство готовности конкретного блока или всего первого экрана.</li></ul>"
}