À medida que os aplicativos da web continuam a crescer em complexidade e tamanho, também aumentam as preocupações com o desempenho. Os desenvolvedores geralmente resolvem isso adotando a renderização do lado do servidor (SSR) para descarregar alguns dos processos de renderização do cliente.

No entanto, o desempenho do site ainda pode ser afetado, mesmo quando a renderização de HTML ocorre no servidor. Embora o HTML seja entregue de maneira rápida e amigável com o SEO, o processo de hidratação-tornando o aplicativo interativo do lado do cliente-pode ser caro. Por sua vez, métricas como Time to Interactive (TTI) e Estimated Input Latency (EIL) podem despencar para aplicativos com HTML complexo e profundamente aninhado.

Agora, você pode resolver isso com técnicas como divisão de código, ou carregar as partes vitais do aplicativo imediatamente enquanto atrasa a entrega do código e hidrata os outros componentes. Isso pode melhorar suas métricas, mas ainda perderá tempo de carregamento em componentes que o usuário nunca vê ou interage.

É aí que entra a hidratação preguiçosa. Vamos ver o que é, como funciona e como implementá-lo em Vue 3 .

Hidratação parcial vs. hidratação preguiçosa

Para entender as variantes da hidratação e como elas funcionam, primeiro você precisa se familiarizar com a hidratação parcial.

Como o nome indica, na hidratação parcial, você hidrata apenas algumas partes do seu aplicativo. Isso é útil ao implementar a chamada “arquitetura de ilhas” , em que diferentes seções do aplicativo são consideradas entidades separadas. Isso faz com que cada seção do aplicativo funcione independentemente das outras, o que permite que eles se hidratem separadamente.

Vamos pensar sobre como a hidratação parcial e a arquitetura das ilhas se aplicariam a um site como um blog. Você pode hidratar as partes interativas, como a barra de ferramentas e a seção de comentários, mas deixar outras partes, como o próprio conteúdo, completamente estáticas. Tal abordagem melhora o desempenho e a UX do seu site, e nenhum recurso é desperdiçado em conteúdo estático, tornando as partes interativas hidratadas mais rapidamente.

A hidratação preguiçosa se baseia no conceito de hidratação parcial e os leva ainda mais longe. O conceito é semelhante na implementação de qualquer estrutura que tenha SSR, hidratação básica e componentes assíncronos já incluídos.

Em vez de apenas ser capaz de decidir quais partes do aplicativo da web devem ser hidratadas, você também pode decidir quando isso deveria acontecer. Por exemplo, você pode hidratar o componente apenas quando estiver ocioso, quando estiver na janela de visualização ou em resposta a vários outros gatilhos, como resolução de promessa ou interação do usuário.

Isso leva a economia de recursos e otimizações de desempenho a outro nível. Você não precisa mais hidratar componentes que o usuário nunca verá ou interagirá, tornando o TTI quase instantâneo!

Hidratação preguiçosa com Vue

O Vue 2 tinha uma biblioteca excelente e bastante popular chamada vue-lazy-hydration . Ele fornece um componente LazyHydrate sem renderização e vários invólucros de função manual, como hydrateWhenVisible, para envolver os componentes que você deseja hidratar lentamente. Também permite que você se hidrate em diferentes condições, como:

quando o navegador está ocioso (com requestIdleCallback) quando o componente está na janela de visualização (com IntersectionObserver) na interação do usuário (clique, mouseover, etc.) com um manual gatilho (Promise, boolean switch, etc.) nunca (para componentes estáticos, somente SSR)

Infelizmente, no momento da publicação, esta, nem qualquer outra biblioteca proeminente de hidratação preguiçosa não suporta Vue 3. Com isso dito , o suporte vue-lazy-hydration para Vue 3 está em desenvolvimento e parece que há um plano para lançar após Nuxt 3 é lançado.

Isso nos deixa com continue usando Vue 2 para hidratação preguiçosa ou para im implementar nosso próprio mecanismo, que é o que faremos neste artigo.

Implementando hidratação preguiçosa no Vue 3

Com estruturas de IU como o Vue que têm SSR embutido e suporte de hidratação , implementar a hidratação lenta é bastante fácil.

Você precisará de um invólucro ou componente sem renderização que renderize automaticamente seu componente no servidor enquanto usa a renderização condicional no lado do cliente para atrasar a hidratação até que certas condições sejam atendidas.

Decidi basear nossa implementação do Vue 3 lazy hydration em react-lazy-hidratação . Seu código é mais simples do que o do vue-lazy-hydration e é surpreendentemente mais traduzível, com React Hooks convertendo bem com a API de composição Vue.

Declaração de componentes e adereços

Começamos com uma base Componente Vue 3, com inclusão TypeScript adicional e uma função de utilitário isBrowser para verificar se os globals do navegador estão disponíveis.

Nosso wrapper de hidratação lenta incluirá funcionalidade semelhante ao que as bibliotecas mencionadas anteriormente fornecem. Para isso, teremos que aceitar um conjunto bastante amplo de props de configuração.

//… export default defineComponent ({props: {ssrOnly: Boolean, whenIdle: Boolean, whenVisible: [Boolean, Object] as PropType , didHydrate: Function as PropType ()=> void>, promessa: Object as PropType >, on: [Array, String] as PropType (keyof HTMLElementEventMap) [] | keyof HTMLElementEventMap>,},//…});//…

Com os adereços acima, ofereceremos suporte a componentes estáticos apenas SSR, bem como hidratantes quando o navegador estiver ocioso, o componente estiver visível ou depois que a promessa fornecida for resolvida.

Além disso, o on suportará a hidratação na interação do usuário, enquanto o didHydrate permitirá um retorno de chamada após a hidratação do componente.

Função de configuração

Na configuração, primeiro inicializamos alguns valores requeridos.

//… export default defineComponent ({//… setup () {const noOptions=! props.ssrOnly &&! props.whenIdle &&! props.whenVisible &&! props.on ?. comprimento &&! props.promise; const wrapper=ref (null); const hydrated=ref (noOptions ||! isBrowser ()); const hydrate=()=> {hydrated.value=true;}; },});//…

Usaremos um modelo de invólucro ref para acessar o elemento de invólucro e um ref hidratado para manter o valor booleano reativo, que determina o estado atual de hidratação.

Observe como nós inicialize o ref hidratado. Quando não há opções definidas, o componente será hidratado imediatamente por padrão. Caso contrário, a hidratação será atrasada no lado do cliente durante o SSR.

hidratar é apenas uma função auxiliar unilateral para definir hidratado como verdadeiro.

Registro de retorno de chamada de hidratação

Em seguida, começamos a criar a lógica, com um retorno de chamada onMounted e um único efeito de observação.

//… onMounted (()=> {if (wrapper.value &&! wrapper. value.hasChildNodes ()) {hidrato ();}}); assistir (hidratado, (hidratar)=> {if (hidratar && props.didHydrate) props.didHydrate ();}, {imediato: verdadeiro});//…

No retorno de chamada onMounted, verificamos se o elemento possui algum filho. Caso contrário, podemos hidratar imediatamente.

O efeito watch lida com o retorno de chamada didHydrate. Observe a opção imediata-é importante para quando a hidratação não é atrasada, tanto durante o SSR quanto quando nenhuma opção é fornecida.

Definindo o efeito do relógio principal

Agora, vamos entrar no efeito principal do relógio que tratará de todas as opções e definirá a referência hidratada apropriadamente.

//… watch ([()=> adereços, invólucro, hidratado], ([{on, promessa, ssrOnly, whenIdle, whenVisible }, invólucro, hidratado], _, onInvalidate)=> {if (ssrOnly || hidratado) {return;} const cleanupFns: VoidFunction []=[]; const cleanup=()=> {cleanupFns.forEach ((fn)=> {fn ();});}; se (promessa) {promessa.então (hidratar, hidratar);}}, {imediato: verdadeiro});//…

O efeito irá desencadear mudanças nos props, bem como no wrapper e no hydrate refs.

Primeiro, verificamos se o componente se destina a renderizar apenas no lado do servidor ou se já foi hidratado. Fazemos isso porque, em qualquer um desses casos, não há necessidade de avaliar mais o efeito, então podemos retornar da função.

Se o processo continuar, inicializamos a função de limpeza para quando o efeito for invalidada, e lidar com a hidratação preguiçosa Promise-based.

Hidratação baseada na visibilidade

A seguir, ainda dentro do efeito, tratamos da hidratação baseada na visibilidade. Se o IntersectionObserver for suportado, nós o inicializamos, passando as opções padrão ou fornecidas. Caso contrário, hidratamos imediatamente.

//… if (whenVisible) {if (wrapper && typeof IntersectionObserver!==”undefined”) {const observerOptions=typeof whenVisible===”object”? whenVisible: {rootMargin:”250px”,}; const io=new IntersectionObserver ((entradas)=> {entradas.forEach ((entrada)=> {if (entry.isIntersecting || entry.intersectionRatio> 0) {hydrate ();}});}, observerOptions); io.observe (wrapper); cleanupFns.push (()=> {io.disconnect ();}); } else {return hydrate (); }}//…

Observe o retorno de chamada de limpeza para desconectar a instância IntersectionObserver do elemento wrapper.

Hidratação baseada em ociosidade do navegador

Seguimos uma estrutura semelhante para o navegador hidratação baseada em ociosidade, desta vez com requestIdleCallback e cancelIdleCallback.

if (whenIdle) {if (typeof window.requestIdleCallback!==”undefined”) {const idleCallbackId=window.requestIdleCallback (hydrate, {timeout: 500,} ); cleanupFns.push (()=> {window.cancelIdleCallback (idleCallbackId);}); } else {const id=setTimeout (hidrato, 2000); cleanupFns.push (()=> {clearTimeout (id);}); }}

A compatibilidade de requestIdleCallback entre navegadores é inferior a 80 por cento , notavelmente sem suporte do Safari em iOS e macOS, então teremos que implementar um fallback com setTimeout, atrasando a hidratação e empurrando-a para a fila assíncrona.

Se você estiver usando TypeScript, deve notar que, atualmente, você venceu’t encontre requestIdleCallback na biblioteca padrão. Para uma digitação adequada, você precisará instalar @ types/requestidlecallback .

Hidratação baseada na interação do usuário

Por último, lidamos com a hidratação baseada em eventos do usuário. Aqui, as coisas são relativamente simples, pois apenas percorremos os eventos e definimos os ouvintes de eventos de acordo.

if (on) {const events=([] as Array ). Concat (on); events.forEach ((event)=> {wrapper?.addEventListener (event, hydrate, {once: true, passive: true,}); cleanupFns.push (()=> {wrapper?.removeEventListener (event, hydrate, { });});}); } onInvalidate (limpeza);

Depois disso, lembre-se de chamar onInvalidate para registrar a função de limpeza, e o efeito está pronto!

Finalizando o modelo

Para finalizar o componente, retorne os refs necessários no modelo da função de configuração.

//… export default defineComponent ({//… setup () {//… return {wrapper, hydrated,};},});//…

Então, no modelo, renderize o envoltório

, atribua refs e, condicionalmente, renderize o componente para hidratação lenta.

Usando nosso componente de hidratação preguiçosa

Com nosso componente de hidratação preguiçosa pronto, é hora de testá-lo!

Estruturando nosso aplicativo Vue 3 SSR

Primeiro, você’Precisará configurar seu ambiente que seja SSR-ou Estático Gerador de site (SSG) pronto . Tecnicamente, qualquer coisa com HTML pré-renderizado e Vue 3 com hidratação habilitada deve funcionar, mas sua milhagem pode variar.

Como nenhum dos Nuxt.js nem Gridsome ainda é compatível com Vue 3, sua melhor aposta seria algo como vite-plugin-ssr . Essa solução permitirá que você aproveite a excelente experiência de desenvolvimento que o Vite oferece enquanto implementa SSR sem muitos problemas.

Você pode criar um novo aplicativo vite-plugin-ssr com o seguinte comando:

npm init vite-plugin-ssr @ latest

Em seguida, configure o componente de hidratação lenta, com o guia acima ou em este GitHub Gist .

Com isso no lugar, vá para qualquer página disponível, envolva um componente interativo dentro de e brinque com ele!

Esta página é:

  • Renderizada em HTML.
  • Interativa.

Use opções diferentes, veja quando o componente é interativo, verifique quando está hidratado com o retorno de chamada didHydrate e muito mais!

Combinando hidratação preguiçosa com componentes assíncronos

Para melhorar ainda mais as métricas de TTI e os tempos de carregamento do seu aplicativo, você pode combinar hidratação preguiçosa com componentes assíncronos . Isso dividirá seu aplicativo em pedaços menores, prontos para serem carregados sob demanda. Com isso, seus componentes hidratados preguiçosos só serão carregados quando a hidratação acontecer.

import {defineAsyncComponent} de”vue”; importar LazyHydrate de”./_components/LazyHydrate.vue”; exportar {componentes padrão: {Contador: defineAsyncComponent ({loader: ()=> import (“./_ components/Counter.vue”),}), LazyHydrate,},};

Lembre-se de que você deve ter cuidado com essa abordagem, pois a busca dinâmica de componentes pode criar um atraso perceptível para o usuário. Nesse caso, você terá que ser seletivo sobre quais componentes adiar e precisará implementar conteúdo de fallback, como carregadores para quando o código for buscado e analisado.

No entanto, mesmo com tudo isso para Considere que os componentes assíncronos hidratados preguiçosos ainda podem ter um grande potencial para melhorar drasticamente o desempenho de aplicativos grandes e complexos, especialmente aqueles que dependem fortemente de elementos como gráficos interativos ou diálogos ocultos.

Resultado

Então aí está-hidratação preguiçosa explicada e implementada no Vue 3! Com o componente implementado nesta postagem, você pode otimizar seu aplicativo SSR/SSG, melhorar seu desempenho, capacidade de resposta e experiência do usuário.

Para obter o código completo do componente , consulte este GitHub Gist . Sinta-se à vontade para experimentar. Se você tiver alguma ideia de melhoria, deixe-me saber no GitHub.

Certifique-se de seguir as atualizações sobre vue-preguiçoso-hidratação. Diz-se que a próxima versão tira proveito das novas APIs de 3 nós do Vue e, portanto, é provável que tenha mais desempenho ou forneça mais recursos do que a simples implementação deste post.