DarkRiDDeR13 мин

Медиа без ложных метрик: где заканчивается component contract и начинается браузерное наблюдение

FrontendПроизводительность

После добавления оптимизации медиа часто возникает странный спор: один разработчик показывает unit test, другой — trace, а оба называют это «готовым LCP fix». Проблема такой подмены в том, что решение нельзя ни подтвердить, ни откатить: не ясно, что именно изменилось — markup, размеры, очередь, декодирование или условия запуска. При следующем изменении команда сравнит несравнимые результаты. Цена ошибки — потратить время на «ускорение», которое не меняет путь первого экрана.

Причина в том, что у этих фактов разные владельцы. Компонент владеет описанием slot и может требовать geometry. Приложение может назначить намерение важности. Браузер выполняет загрузку, decode и layout; измерительный код наблюдает его результат при конкретных условиях. Учебный контракт ниже не притворяется браузером: он хранит declared transitions и принимает внешний record только как supplied data.

Пять слоёв вместо одного флага loaded

Состояния, которые нельзя заменить одним boolean
СлойВладелецНаблюдаемый фактЧего факт не доказывает
Markupкомпонентslot описан src и altресурс запрошен или показан
Geometrycomponent API и layout contractесть положительные width и heightреальный box и отсутствие shift
Load intentприложениеslot помечен critical/deferred candidateприоритет scheduler и время ответа
Declared load/decodeучебная fixtureлокальный порядок transitionsсетевой load или decode API
External observationотдельный browser прогонв record есть значение и условияпричину изменения без анализа trace

Почему load и decode надо разделить даже в учебном коде

Слово loaded удобно, но оно склеивает минимум два вопроса: ресурс уже доступен по пути загрузки и он готов для отображения в конкретном контексте. Настоящие платформенные алгоритмы сложнее этой заметки. Поэтому model не пытается их воспроизвести: markLoad принимает только teaching token, а markDecode требует ранее объявленный load. Их разделение полезно не для симуляции браузера, а чтобы review увидел неверный порядок переходов.

В реальном коде граница будет другой: события, Promise или framework adapter. Нельзя заменить их вызовом fixture и сказать, что браузер прошёл decode. Но можно заранее договориться, что логика UI не покажет «готово» только потому, что создан descriptor. Это небольшой, проверяемый контракт: описание slot не равно завершённой работе платформы.

# Запускает только учебную in-memory модель Node.js.
node web/scripts/upgrade-2022-08.mjs --verify-fixture

# PASS fixture: 14/14 assertions
# Нет DOM, CSS layout, сети, decode(), PerformanceObserver, LCP или CLS.
Диаграмма состояний media slot: markup и geometry независимы; planned load с label critical/deferred не является транспортом; declared load ведёт к declared decode; лишь затем можно сохранить external observation record. Боковая область исключает DOM, scheduler, LCP и CLS из учебной модели.
Разделение переходов оставляет место для настоящего browser-слоя, не превращая fixture в его подделку.

Геометрия: проверяем вход, не рисуем несуществующий layout

Функция reserveGeometry допускает только положительные целые width и height. Это намеренно узкое правило. Оно помогает найти данные, где component API вообще не знает ожидаемый ratio. Но функция не вычисляет CSS box, не учитывает aspect-ratio, grid, container query и не знает, чем закончится рендер. Поэтому assertion называется geometryReserved, а не noLayoutShift.

Такое именование снижает риск плохой аналитики. Если тест говорит «CLS fixed», его потом используют как аргумент против реального наблюдения. Если тест говорит «в contract есть declared geometry», он остаётся честным: дальше можно провести page-level проверку. В архитектуре это полезнее красивого флага, потому что граница ответственности указана прямо в имени.

Наблюдение — вход другого контура

В модели recordExternalObservation принимает record с kind и числом. Она не создаёт PerformanceObserver и не называет число LCP. Его source явно равен external-observation-supplied-by-caller. Это заставляет следующий слой указать происхождение: какой браузер, какой URL, какие условия кэша, какой viewport и какой trace или запись лежит рядом. Без этих условий число остаётся неподписанным сигналом.

FPWD LCP полезен именно этим различием: API наблюдает кандидатов в документе, а не принимает обещание компонента. В документе есть input, viewport, paint и условия, которых нет в plain JavaScript object. Поэтому даже правильная component schema не равна измерению. Она лишь делает измерение проще интерпретировать: reviewer понимает, какой slot был объявлен critical и какая geometry ожидалась.

Неверный priority: как искать, не придумывая scheduler

Когда команда говорит «картинка грузится не с тем приоритетом», сначала нужно заменить оценку на вопрос. Какая разметка описывает ресурс? Почему продукт считает slot ранним или отложенным? Какие другие ресурсы конкурируют в конкретном прогоне? Что зафиксировано в trace? Три первых вопроса относятся к коду и contract, четвёртый — к браузерному наблюдению. Ни один label в object не является ответом на все четыре.

Если наблюдение расходится с intent, rollback должен быть обратимым. Верните прежний mapping resource-to-slot или прежнюю стратегию выбора variant, сохраните report и сформулируйте новую гипотезу. Не меняйте одновременно source, CSS, CDN и priority hint: тогда следующий trace не позволит установить причину. Один осмысленный эксперимент важнее серии атрибутов, добавленных в надежде на быстрый эффект.

Маршрут: симптом → причина → проверка → действие

  1. Симптом. Зафиксируйте один конфликт: slot объявлен ранним, но в browser report ресурс виден иначе; либо geometry есть в данных, но страница меняет место.
  2. Причина. Проверьте, не заменены ли markup, geometry, load и observation флагом loaded или одной «performance» метрикой.
  3. Проверка модели. Убедитесь, что переход decode до load отклоняется, а readiness никогда не утверждает real metric measured.
  4. Проверка браузера. Отдельно сохраните среду запуска и trace. Укажите, какой вывод сделан из наблюдения, а какой остаётся гипотезой.
  5. Действие. Назовите owner каждого слоя: component API, page composition, platform adapter, performance observer/report.
  6. Откат. Откатывайте ровно один mapping или attribute-намерение, затем повторяйте тот же контролируемый сценарий. Не откатывайте «до хорошей цифры» без условия сравнения.

Что можно использовать в production после модели

Из fixture в production переносится не token и не число record. Переносится дисциплина: source media, geometry и intent должны иметь явную точку владения; browser observation должен быть подписан условиями; assertion должен называться ровно по тому факту, который проверяет. Это помогает и без Performance API: code review сразу видит, где component API допускает неизвестную высоту или где deferred slot неожиданно участвует в первом сценарии.

HTML 5.2 описывает img, но не создаёт за продукт policy для всех вариантов изображений. LCP FPWD 2022 года — Working Draft, а не обещание одинакового результата для каждого документа. Эти статусы важнее рекламного языка: архитектурная заметка должна оставить место для браузерной разницы, версий и измерения, а не выдавать учебную схему за finished optimisation.

Ограничение и следующий шаг

Контракт не покрывает lazy loading, preload, responsive selection, background-image, animated formats, decoding errors, cross-origin timing, caching и ограничения устройств. Он также не строит accessibility tree и не проверяет alt в screen reader. Добавлять любое из этих свойств стоит только как новый layer со своим input и отрицательными assertions.

Следующий шаг — выбрать один page-level media slot и оформить короткий evidence packet: скрин разметки/данных, ожидаемая geometry, rationale intent и протокол browser observation. Если после этого нужна правка, сделайте её одной: например, исправьте geometry contract или выбор source для этого slot. Затем повторите именно описанный сценарий. Так у оптимизации будет причина, проверка и безопасный путь назад.

Проверяемые источники

  • Largest Contentful Paint, W3C First Public Working Draft от 24 мая 2022 года — датированный первичный документ. Он определяет API наблюдения candidate largest contentful paint и отдельно перечисляет его ограничения; fixture ниже этот API не вызывает и LCP не измеряет.
  • HTML 5.2, W3C Recommendation от 14 декабря 2017 года — неизменяемая нормативная версия, доступная в августе 2022 года. Она задаёт элемент img и его атрибуты; проектные labels, операции модели и выводы о метриках в ней не определены.