document.addEventListener('DOMContentLoaded', function () { var DEBUG = false; // true = exibe logs no console var DEPTH = 2; // profundidade do prefetch (1 = só links da página atual, 2 = links dos links, etc.) var DOMAIN = window.location.origin; var prefetched = new Set([window.location.href]); // Mapeamento de seletores para o valor correto do atributo 'as' // usado no — informa ao browser o tipo do recurso var RESOURCE_SELECTORS = [ { selector: 'link[rel="stylesheet"][href]', as: 'style' }, { selector: 'script[src]', as: 'script' }, { selector: 'img[src]', as: 'image' }, ]; // ------------------------------------------------------------------------- // Injeta um no
para páginas internas (same-origin) // O browser trata com prioridade Idle, não compete com recursos ativos, // e respeita corretamente os headers de cache HTTP. // ------------------------------------------------------------------------- function prefetchOne(url) { if (prefetched.has(url)) return; prefetched.add(url); var link = document.createElement('link'); link.rel = 'prefetch'; link.href = url; link.as = 'document'; document.head.appendChild(link); if (DEBUG) console.log('[prefetch:internal] ' + url); } // ------------------------------------------------------------------------- // Injeta um para recursos externos (CSS, fontes, scripts) // Sem o atributo 'as' para recursos cujo tipo o browser não consegue inferir // (ex: Google Fonts), ou com 'as' correto quando identificado. // ------------------------------------------------------------------------- function prefetchResource(url, as) { if (prefetched.has(url)) return; prefetched.add(url); var link = document.createElement('link'); link.rel = 'prefetch'; link.href = url; if (as) link.as = as; document.head.appendChild(link); if (DEBUG) console.log('[prefetch:resource] ' + url); } // ------------------------------------------------------------------------- // Percorre o DOM de uma página já parseada e faz prefetch de todos os // recursos estáticos encontrados (CSS, scripts, imagens, fontes externas) // ------------------------------------------------------------------------- function prefetchResources(doc, baseUrl) { RESOURCE_SELECTORS.forEach(function (def) { doc.querySelectorAll(def.selector).forEach(function (el) { try { var attr = el.href || el.src; var resolved = new URL(attr, baseUrl).href; prefetchResource(resolved, def.as); } catch (_) {} }); }); } // ------------------------------------------------------------------------- // Busca o HTML de uma URL, extrai links internos (recursivo até DEPTH) // e faz prefetch de todos os recursos estáticos da página. // Usa DOMParser para evitar execução de scripts do HTML remoto. // ------------------------------------------------------------------------- function prefetchDeep(url, depth) { if (depth === 0 || prefetched.has(url)) return; prefetchOne(url); if (depth <= 1) return; fetch(url) .then(function (response) { if (!response.ok) throw new Error('Status ' + response.status); return response.text(); }) .then(function (html) { var parser = new DOMParser(); var doc = parser.parseFromString(html, 'text/html'); // Recursos estáticos da página (CSS, fontes, scripts, imagens) prefetchResources(doc, url); // Links internos — continua a recursão doc.querySelectorAll('a[href]').forEach(function (a) { try { var resolved = new URL(a.getAttribute('href'), url).href; if (resolved.startsWith(DOMAIN)) { prefetchDeep(resolved, depth - 1); } } catch (_) { // href inválido (javascript:, mailto: etc.) — ignora } }); }) .catch(function (error) { if (DEBUG) console.warn('[prefetch] falha em ' + url, error); }); } // ------------------------------------------------------------------------- // Ponto de entrada: percorre todos os links da página atual // ------------------------------------------------------------------------- document.querySelectorAll('a[href]').forEach(function (a) { try { var resolved = new URL(a.getAttribute('href'), DOMAIN).href; if (resolved.startsWith(DOMAIN)) { prefetchDeep(resolved, DEPTH); } } catch (_) { // ignora hrefs inválidos } }); });