26 / 36
Что такое Resource Timing API?
Полный ответ
Каждый загруженный ресурс создаёт запись PerformanceResourceTiming:
JavaScript
// Получить все загруженные ресурсы
const resources = performance.getEntriesByType('resource');
for (const res of resources) {
console.log({
name: res.name, // URL ресурса
type: res.initiatorType, // "script", "css", "img", "fetch", "xmlhttprequest"
duration: res.duration, // общее время загрузки (мс)
transferSize: res.transferSize, // размер по сети (0 = из кеша)
protocol: res.nextHopProtocol, // "h2", "h3", "http/1.1"
});
}
Детальная разбивка тайминга ресурса:
JavaScript
function analyzeResource(entry) {
// Если ресурс из кеша, сетевые тайминги будут 0
const fromCache = entry.transferSize === 0;
if (fromCache) {
return { name: entry.name, source: 'cache', duration: entry.duration };
}
return {
name: entry.name,
source: 'network',
dns: entry.domainLookupEnd - entry.domainLookupStart,
tcp: entry.connectEnd - entry.connectStart,
tls: entry.secureConnectionStart > 0
? entry.connectEnd - entry.secureConnectionStart
: 0,
ttfb: entry.responseStart - entry.requestStart,
download: entry.responseEnd - entry.responseStart,
total: entry.duration,
size: entry.transferSize,
};
}
const resources = performance.getEntriesByType('resource');
const analysis = resources.map(analyzeResource);
console.table(analysis);
Наблюдение через PerformanceObserver:
JavaScript
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Предупреждение о медленных ресурсах
if (entry.duration > 1000) {
console.warn(
`Медленный ресурс (${entry.duration.toFixed(0)} ms): ${entry.name}`
);
}
// Предупреждение о больших ресурсах
if (entry.transferSize > 500_000) {
console.warn(
`Тяжёлый ресурс (${(entry.transferSize / 1024).toFixed(0)} KB): ${entry.name}`
);
}
}
});
observer.observe({ type: 'resource', buffered: true });
Ограничения для кросс-доменных ресурсов:
JavaScript
// Для ресурсов с другого домена тайминги по умолчанию обнулены
const cdnResource = resources.find((r) => r.name.includes('cdn.example.com'));
// Без Timing-Allow-Origin все сетевые тайминги будут 0:
console.log(cdnResource.requestStart); // 0
console.log(cdnResource.responseStart); // 0
// duration и startTime доступны, но детали скрыты
// Сервер должен отправить заголовок:
// Timing-Allow-Origin: *
// или
// Timing-Allow-Origin: https:// your-site.com
📌 Реальные кейсы
Поиск узких мест загрузки:
JavaScript
function findBottlenecks() {
const resources = performance.getEntriesByType('resource');
// Группировка по типу
const byType = new Map();
for (const res of resources) {
const type = res.initiatorType;
if (!byType.has(type)) byType.set(type, []);
byType.get(type).push(res);
}
for (const [type, entries] of byType) {
const totalSize = entries.reduce((sum, e) => sum + e.transferSize, 0);
const avgDuration = entries.reduce((sum, e) => sum + e.duration, 0) / entries.length;
const fromCache = entries.filter((e) => e.transferSize === 0).length;
console.log(`${type}: ${entries.length} ресурсов`);
console.log(` Общий размер: ${(totalSize / 1024).toFixed(0)} KB`);
console.log(` Среднее время: ${avgDuration.toFixed(0)} ms`);
console.log(` Из кеша: ${fromCache}/${entries.length}`);
}
// Топ-5 самых медленных
const slowest = [...resources]
.sort((a, b) => b.duration - a.duration)
.slice(0, 5);
console.log('
Топ-5 медленных ресурсов:');
for (const res of slowest) {
console.log(` ${res.duration.toFixed(0)} ms -- ${res.name.split('/').pop()}`);
}
}
window.addEventListener('load', () => setTimeout(findBottlenecks, 100));
Лимит буфера ресурсов:
JavaScript
// По умолчанию браузер хранит 250 записей ресурсов
// Для страниц с большим количеством ресурсов буфер переполняется
// Увеличение буфера
performance.setResourceTimingBufferSize(500);
// Обработка переполнения
performance.addEventListener('resourcetimingbufferfull', () => {
// Сохранить текущие записи
const entries = performance.getEntriesByType('resource');
sendToAnalytics(entries);
// Очистить буфер
performance.clearResourceTimings();
});
Резюме
Resource Timing API дает тайминги каждого ресурса на странице. Ключевые поля: duration, transferSize, initiatorType. Для кросс-доменных ресурсов нужен заголовок Timing-Allow-Origin. Буфер по умолчанию -- 250 записей, при необходимости его нужно увеличить или периодически очищать.