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 <link rel="prefetch"> — 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 <link rel="prefetch"> no <head> 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 <link rel="prefetch"> 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
}
});
});
Comments