O material, cuja tradução publicamos hoje, é dedicado à história de otimização da nova versão do
Slack Desktop Client, um dos recursos centrais da aceleração do carregamento.

Antecedentes
No início do trabalho na nova versão do cliente de desktop Slack, foi criado um protótipo, chamado de "botas rápidas". O objetivo deste protótipo, como você pode imaginar, era acelerar o download o máximo possível. Usando o arquivo HTML do cache da CDN, os dados de armazenamento Redux armazenados antecipadamente e o responsável pelo serviço, conseguimos carregar a versão light do cliente em menos de um segundo (naquela época, o tempo de download usual para usuários com 1-2 espaços de trabalho era de aproximadamente 5 segundos ) O trabalhador do serviço foi o centro dessa aceleração. Além disso, ele abriu o caminho para oportunidades que frequentemente pedíamos aos usuários do Slack para implementar. Estamos falando do modo offline do cliente. O protótipo nos permitiu ver literalmente com um olho o que o cliente refeito pode ser capaz. Com base nas tecnologias acima, começamos a processar o cliente Slack, imaginando o resultado aproximado e concentrando-nos em acelerar o carregamento e implementar o modo de operação do programa offline. Vamos falar sobre como o núcleo do cliente atualizado funciona.
O que é um trabalhador de serviço?
O Service Worker é essencialmente um objeto proxy poderoso para solicitações de rede, que permite ao desenvolvedor, usando uma pequena quantidade de código JavaScript, controlar como o navegador lida com solicitações HTTP individuais. Os funcionários do serviço suportam uma API de cache avançada e flexível, projetada para que os objetos Request sejam usados como chaves e os objetos Response, como valores. Os funcionários de serviços, como os funcionários da Web, executam em seus próprios processos, fora do principal thread de execução de código JavaScript de qualquer janela do navegador.
O Service Worker é um seguidor do
cache de
aplicativos , que agora está obsoleto. Era um conjunto de APIs representadas pela interface do
AppCache
, usada para criar sites que implementam recursos offline. Ao trabalhar com o
AppCache
, foi usado um arquivo de manifesto estático que descreve os arquivos que o desenvolvedor gostaria de armazenar em cache para uso offline. Em geral, os recursos do
AppCache
limitados a isso. Esse mecanismo era simples, mas não flexível, o que não dava ao desenvolvedor controle especial sobre o cache. No W3C, isso foi levado em consideração ao desenvolver
a especificação Service Worker . Como resultado, os funcionários do serviço permitem que o desenvolvedor gerencie muitos detalhes sobre cada sessão de interação de rede realizada por um aplicativo ou site da Web.
Quando começamos a trabalhar com essa tecnologia, o Chrome era o único navegador que a suporta, mas sabíamos que não havia muito tempo para esperar um amplo suporte para os funcionários de serviço. Agora, essa tecnologia está em
toda parte , é suportada por todos os principais navegadores.
Como usamos trabalhadores de serviço
Quando um usuário inicia um novo cliente Slack pela primeira vez, baixamos um conjunto completo de recursos (HTML, JavaScript, CSS, fontes e sons) e os colocamos no cache do prestador de serviços. Além disso, criamos uma cópia do repositório Redux localizado na memória e gravamos essa cópia no banco de dados IndexedDB. Quando o programa for lançado na próxima vez, verificamos a presença dos caches correspondentes. Se estiverem, nós os usamos ao baixar o aplicativo. Se o usuário estiver conectado à Internet, faremos o download dos dados mais recentes após o lançamento do aplicativo. Caso contrário, o cliente permanece operacional.
Para distinguir entre as duas opções acima para carregar o cliente, fornecemos os nomes: download quente (quente) e frio (frio). A inicialização a frio de um cliente ocorre com mais freqüência quando o usuário inicia o programa pela primeira vez. Nessa situação, não há recursos armazenados em cache ou dados Redux armazenados. Com uma inicialização a quente, temos tudo o que você precisa para executar o cliente Slack no computador do usuário. Observe que a maioria dos recursos binários (imagens, PDFs, vídeos etc.) são processados usando o cache do navegador (esses recursos são controlados por cabeçalhos de cache regulares). Um trabalhador de serviço não deve processá-los de uma maneira especial para que possamos trabalhar com eles offline.
A escolha entre carregamento quente e frioCiclo de vida do trabalhador de serviço
Os funcionários de serviço podem lidar com três eventos do ciclo de vida. Estes são
instalar ,
buscar e
ativar . A seguir, falaremos sobre como respondemos a cada um desses eventos, mas primeiro precisamos falar sobre o download e o registro do próprio prestador de serviços. Seu ciclo de vida depende de como o navegador lida com as atualizações de arquivos do trabalhador de serviço. Aqui está o que você pode ler sobre isso no
MDN : “A instalação é feita se o arquivo baixado for reconhecido como novo. Pode ser um arquivo que difere do existente (a diferença nos arquivos é determinada comparando-os por byte) ou um arquivo de serviço de serviço que foi encontrado pela primeira vez pelo navegador na página que está sendo processada.
Cada vez que atualizamos o arquivo JavaScript, CSS ou HTML correspondente, ele passa pelo plug-in personalizado do Webpack, que cria um manifesto com uma descrição dos arquivos correspondentes com hashes exclusivos (
aqui está um exemplo abreviado de um arquivo de manifesto). Esse manifesto está incorporado no código do trabalhador do serviço, o que faz com que o trabalhador do serviço seja atualizado na próxima inicialização. Além disso, isso é feito mesmo quando a implementação do trabalhador do serviço não muda.
EventEvento de evento
Sempre que um trabalhador de serviço é atualizado, obtemos um evento de
install
. Em resposta, examinamos os arquivos cujas descrições estão contidas no manifesto incorporado ao trabalhador do serviço, carregamos cada um deles e os colocamos no bloco de cache correspondente. O armazenamento de arquivos é organizado usando a nova API de
cache , que faz parte da especificação do Service Worker. Essa API armazena objetos de
Response
usando objetos de
Request
como chaves. Como resultado, verifica-se que o armazenamento é incrivelmente simples. Vai bem com a forma como os eventos do trabalhador de serviço recebem solicitações e retornam respostas.
As chaves para armazenar em cache os blocos são atribuídas com base no tempo de implantação da solução. O registro de data e hora é incorporado no código HTML; como resultado, ele pode ser enviado, como parte do nome do arquivo, na solicitação de download de cada recurso. O cache separado de recursos de cada implantação é importante para evitar o compartilhamento de recursos incompatíveis. Graças a isso, podemos ter certeza de que o arquivo HTML baixado inicialmente baixará apenas recursos compatíveis, e isso é verdade quando são baixados pela rede e quando são baixados do cache.
FEvento de evento
Depois que o trabalhador do serviço for registrado, ele começará a processar todas as solicitações de rede pertencentes à mesma fonte. Um desenvolvedor não pode fazer com que algumas solicitações sejam processadas por um trabalhador de serviço, enquanto outras não. Mas o desenvolvedor tem controle total sobre o que exatamente precisa ser feito com as solicitações recebidas pelo trabalhador do serviço.
Ao processar uma solicitação, primeiro a examinamos. Se o que é solicitado está presente no manifesto e está no cache, retornamos a resposta à solicitação retirando os dados do cache. Se o cache não tiver o que você precisa, retornamos uma solicitação de rede real que acessa o recurso de rede real como se o trabalhador do serviço não estivesse envolvido nesse processo. Aqui está uma versão simplificada do nosso manipulador de eventos de
fetch
:
self.addEventListener('fetch', (e) => { if (assetManifest.includes(e.request.url) { e.respondWith( caches .open(cacheKey) .then(cache => cache.match(e.request)) .then(response => { if (response) return response; return fetch(e.request); }); ); } else { e.respondWith(fetch(e.request)); } });
Na realidade, esse código contém muito mais lógica específica do Slack, mas o núcleo do nosso manipulador é tão simples quanto neste exemplo.
Ao analisar interações de rede, as respostas retornadas pelo responsável pelo serviço podem ser reconhecidas pela marca ServiceWorker na coluna que indica a quantidade de dadosActivateEvento ativado
O evento de
activate
é gerado após a instalação bem-sucedida de um trabalhador de serviço novo ou atualizado. Nós o usamos para analisar recursos em cache e invalidar blocos de cache com mais de 7 dias. Essa é uma boa prática de manter o sistema em ordem e, além disso, permite garantir que recursos muito antigos não sejam usados ao carregar o cliente.
Código do cliente atrasado na versão mais recente
Você deve ter notado que nossa implementação implica que qualquer pessoa que inicie o cliente Slack após a primeira inicialização do cliente receberá não os recursos mais recentes, mas armazenados em cache, carregados durante o registro anterior do trabalhador do serviço. Na implementação original do cliente, tentamos atualizar o trabalhador do serviço após cada download. No entanto, um usuário típico do Slack pode, por exemplo, baixar um programa apenas uma vez por dia, de manhã. Isso pode levar ao fato de que ele trabalhará constantemente com um cliente cujo código para o dia todo está atrasado em relação à versão mais recente (lançamos novas versões várias vezes ao dia).
Ao contrário de um site típico, que, ao visitar, sai rapidamente, o cliente Slack no computador do usuário fica aberto por horas e fica aberto. Como resultado, nosso código tem uma vida útil bastante longa, o que exige que usemos abordagens especiais para manter sua relevância.
Ao mesmo tempo, nos esforçamos para garantir que os usuários trabalhem com as versões mais recentes do código, para que recebam os recursos mais recentes, as correções de bugs e as melhorias de desempenho. Logo após o lançamento de um novo cliente, implementamos um mecanismo que nos permite diminuir a distância entre o que os usuários estão trabalhando e o que lançamos. Se, após a última atualização, uma nova versão do sistema foi implantada, carregaremos novos recursos que serão usados na próxima vez que o cliente inicializar. Se nada de novo puder ser encontrado, nada será carregado. Depois que essa alteração foi feita no cliente, o tempo médio de vida dos recursos com os quais o cliente foi carregado foi reduzido pela metade.
Novas versões do código são baixadas regularmente, mas ao baixar o programa, apenas a versão mais recente é usadaSincronização de sinalizadores de novos recursos
Com a ajuda de sinalizadores de novos recursos (Sinalizadores de recursos), marcamos na base de código o trabalho em que ainda não foi concluído. Isso nos permite incluir novos recursos no código antes de seus lançamentos públicos. Essa abordagem reduz o risco de erros na produção devido ao fato de que novos recursos podem ser testados livremente junto com o restante do aplicativo, fazendo isso muito antes de o trabalho neles ser concluído.
Os novos recursos do Slack geralmente são lançados quando eles fazem alterações nas APIs correspondentes. Antes de começarmos a usar trabalhadores de serviço, tínhamos a garantia de que novos recursos e alterações na API sempre seriam sincronizados. Mas depois que começamos a usar o cache, que pode não conter a versão mais recente do código, verificou-se que o cliente pode estar em uma situação em que o código não está sincronizado com os recursos de back-end. Para lidar com esse problema, armazenamos em cache não apenas recursos, mas também algumas respostas da API.
O fato de os funcionários do serviço processarem absolutamente todas as solicitações de rede simplificou a solução. A cada atualização do operador de serviço, entre outras coisas, executamos solicitações de API, armazenando em cache as respostas no mesmo bloco de cache que os recursos correspondentes. Isso conecta recursos e funções experimentais aos recursos certos - potencialmente obsoletos, mas garantidos para serem consistentes entre si.
Na verdade, isso é apenas a ponta do iceberg de oportunidades disponíveis para o desenvolvedor, graças aos funcionários de serviço. Um problema que não pôde ser resolvido usando o mecanismo
AppCache
, ou que exigiria que os mecanismos cliente e servidor fossem resolvidos, é simples e naturalmente resolvido usando os trabalhadores de serviço e a API de cache.
Sumário
O responsável pelo serviço acelerou o carregamento do cliente Slack organizando o armazenamento local de recursos que estão prontos para uso na próxima vez em que o cliente inicializar. A rede - a principal fonte de atrasos e ambiguidades que nossos usuários podem encontrar, agora praticamente não tem efeito na situação. Nós, por assim dizer, removemos da equação. E se você pode remover a rede da equação, é possível implementar a funcionalidade offline no projeto. Nosso suporte ao modo offline é muito direto no momento. O usuário pode baixar o cliente e ler mensagens das conversas baixadas. O sistema ao mesmo tempo se prepara para marcas de sincronização nas mensagens lidas. Mas agora temos uma base para a futura implementação de mecanismos mais avançados.
Após muitos meses de desenvolvimento, experimentação e otimização, aprendemos muito sobre como os profissionais de serviço trabalham na prática. Além disso, verificou-se que essa tecnologia é adequada para projetos de grande escala. Em menos de um mês desde a liberação pública do cliente com um funcionário de serviço, atendemos com sucesso dezenas de milhões de solicitações diárias de milhões de funcionários de serviço instalados. Isso levou a uma redução de cerca de 50% no tempo de carregamento de novos clientes, em comparação com os antigos, e ao fato de que o carregamento a quente é cerca de 25% mais rápido que o frio.
Da esquerda para a direita: carregamento de um cliente antigo, carregamento a frio de um novo cliente, carregamento a quente de um novo cliente (quanto mais baixo o indicador, melhor)Caros leitores! Você usa trabalhadores de serviço em seus projetos?
