O pré-carregamento de navegação permite que você aproveite o tempo de inicialização do service worker fazendo solicitações em paralelo.
Resumo
- Em algumas situações, o tempo de inicialização do service worker pode atrasar uma resposta da rede.
- Disponível nos três principais mecanismos de navegador, o pré-carregamento de navegação corrige isso, permitindo que você faça a solicitação em paralelo com a inicialização do service worker.
- Você pode distinguir solicitações de pré-carregamento de navegações normais usando um cabeçalho e exibir conteúdos diferentes.
O problema
Quando você navega para um site que usa um service worker para processar eventos de busca, o navegador pede uma resposta ao service worker. Isso envolve a inicialização do service worker (se ele ainda não estiver em execução) e o envio do evento busca.
O tempo de inicialização depende do dispositivo e das condições. Geralmente, é em torno de 50 ms. Em dispositivos móveis, a duração é de mais de 250 ms. Em casos extremos (dispositivos lentos, CPU em perigo), ela pode ser maior que 500 ms. No entanto, como o service worker fica ativo por um tempo determinado pelo navegador entre os eventos, você só recebe esse atraso ocasionalmente, como quando o usuário navega para seu site de uma nova guia ou de outro site.
Se você estiver respondendo a partir do cache, o tempo de inicialização não será um problema, já que o benefício de ignorar a rede é maior do que o atraso de inicialização. Mas se você estiver respondendo usando a rede...
A solicitação de rede é atrasada pela inicialização do service worker.
Continuamos a reduzir o tempo de inicialização usando o armazenamento em cache de código no V8, ignorando service workers que não têm um evento de busca, iniciando service workers de forma especulativa e outras otimizações. No entanto, o tempo de inicialização sempre será maior que zero.
O Facebook nos chamou a atenção para o impacto desse problema e solicitou uma maneira de realizar solicitações de navegação em paralelo:
Pré-carregamento de navegação ao resgate
O pré-carregamento de navegação é um recurso que permite dizer: "Quando o usuário fizer uma solicitação de navegação GET, inicie a solicitação de rede enquanto o service worker estiver sendo inicializado".
O atraso na inicialização ainda ocorre, mas não bloqueia a solicitação de rede, então o usuário recebe o conteúdo antes.
Aqui está um vídeo disso em ação, em que o service worker recebe um atraso de inicialização deliberado de 500 ms usando um while-loop:
Confira a demonstração. Para aproveitar os benefícios do pré-carregamento de navegação, você precisa de um navegador compatível.
Ativar pré-carregamento de navegação
addEventListener('activate', event => {
event.waitUntil(async function() {
// Feature-detect
if (self.registration.navigationPreload) {
// Enable navigation preloads!
await self.registration.navigationPreload.enable();
}
}());
});
Você pode chamar navigationPreload.enable()
sempre que quiser ou desativá-lo com navigationPreload.disable()
. No entanto, como seu evento fetch
precisa usá-lo, é melhor ativá-lo e desativá-lo no evento activate
do service worker.
Como usar a resposta pré-carregada
Agora, o navegador vai executar pré-carregamentos para navegações, mas você ainda precisa usar a resposta:
addEventListener('fetch', event => {
event.respondWith(async function() {
// Respond from the cache if we can
const cachedResponse = await caches.match(event.request);
if (cachedResponse) return cachedResponse;
// Else, use the preloaded response, if it's there
const response = await event.preloadResponse;
if (response) return response;
// Else try the network.
return fetch(event.request);
}());
});
event.preloadResponse
é uma promessa que será resolvida com uma resposta se:
- O pré-carregamento de navegação está ativado.
- A solicitação é
GET
. - A solicitação é uma solicitação de navegação gerada pelos navegadores ao carregar páginas, incluindo iframes.
Caso contrário, event.preloadResponse
ainda estará lá, mas será resolvido com undefined
.
Respostas personalizadas para pré-carregamentos
Se sua página precisar de dados da rede, a maneira mais rápida de fazer isso é solicitá-los ao service worker e criar uma única resposta de streaming contendo partes do cache e partes da rede.
Digamos que queiramos exibir um artigo:
addEventListener('fetch', event => {
const url = new URL(event.request.url);
const includeURL = new URL(url);
includeURL.pathname += 'include';
if (isArticleURL(url)) {
event.respondWith(async function() {
// We're going to build a single request from multiple parts.
const parts = [
// The top of the page.
caches.match('/article-top.include'),
// The primary content
fetch(includeURL)
// A fallback if the network fails.
.catch(() => caches.match('/article-offline.include')),
// The bottom of the page
caches.match('/article-bottom.include')
];
// Merge them all together.
const {done, response} = await mergeResponses(parts);
// Wait until the stream is complete.
event.waitUntil(done);
// Return the merged response.
return response;
}());
}
});
Acima, mergeResponses
é uma pequena função que mescla os streams de cada solicitação. Isso significa que podemos exibir o cabeçalho armazenado em cache enquanto o conteúdo da rede é transmitido.
É mais rápido que o "shell do app" modelo, já que a solicitação de rede é feita junto com a solicitação da página, e o conteúdo pode ser transmitido sem grandes invasões.
No entanto, a solicitação de includeURL
será atrasada pelo tempo de inicialização do service worker. Podemos usar o pré-carregamento de navegação para corrigir isso também, mas nesse caso não queremos pré-carregar a página inteira, queremos pré-carregar uma inclusão.
Para isso, um cabeçalho é enviado com cada solicitação de pré-carregamento:
Service-Worker-Navigation-Preload: true
O servidor pode usar isso para enviar conteúdos diferentes para solicitações de pré-carregamento de navegação do que faria para uma solicitação de navegação normal. Lembre-se de adicionar um cabeçalho Vary: Service-Worker-Navigation-Preload
para que os caches saibam que suas respostas são diferentes.
Agora, podemos usar a solicitação de pré-carregamento:
// Try to use the preload
const networkContent = Promise.resolve(event.preloadResponse)
// Else do a normal fetch
.then(r => r || fetch(includeURL))
// A fallback if the network fails.
.catch(() => caches.match('/article-offline.include'));
const parts = [
caches.match('/article-top.include'),
networkContent,
caches.match('/article-bottom')
];
Alterar o cabeçalho
Por padrão, o valor do cabeçalho Service-Worker-Navigation-Preload
é true
, mas é possível defini-lo como você quiser:
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.setHeaderValue(newValue);
}).then(() => {
console.log('Done!');
});
Você pode, por exemplo, defini-lo como o ID da última postagem armazenada localmente em cache, para que o servidor retorne apenas dados mais recentes.
Como descobrir o estado
É possível pesquisar o estado do pré-carregamento de navegação usando getState
:
navigator.serviceWorker.ready.then(registration => {
return registration.navigationPreload.getState();
}).then(state => {
console.log(state.enabled); // boolean
console.log(state.headerValue); // string
});
Agradecemos a Matt Falkenhagen e Tsuyoshi Horo por seu trabalho neste recurso e à ajuda com este artigo. Agradecemos muito a todos os envolvidos no esforço de padronização