Pré-carregar módulos

Sérgio Gomes

Publicado em 23 de novembro de 2024

O desenvolvimento baseado em módulos oferece algumas vantagens reais em termos de cacheabilidade, ajudando a reduzir o número de bytes que você precisa enviar aos usuários. A granularidade mais fina do código também ajuda com a história de carregamento, permitindo que você priorize o código crítico no aplicativo.

No entanto, as dependências de módulo introduzem um problema de carregamento, já que o navegador precisa esperar que um módulo seja carregado antes de descobrir quais são as dependências. Uma maneira de contornar isso é pré-carregar as dependências para que o navegador saiba de todos os arquivos com antecedência e possa manter a conexão ocupada.

<link rel="preload"> é uma maneira de solicitar recursos de forma declarativa com antecedência, antes que o navegador precise deles.

<head>
  <link rel="preload" as="style" href="critical-styles.css">
  <link rel="preload" as="font" crossorigin type="font/woff2" href="myfont.woff2">
</head>

Isso funciona muito bem com recursos como fontes, que geralmente ficam ocultas dentro de arquivos CSS, às vezes em vários níveis. Nessa situação, o navegador teria que esperar várias viagens de ida e volta antes de descobrir que precisa buscar um arquivo de fonte grande, quando poderia ter usado esse tempo para iniciar o download e aproveitar toda a largura de banda da conexão.

<link rel="preload"> e o cabeçalho HTTP equivalente oferecem uma maneira simples e declarativa de informar o navegador imediatamente sobre arquivos críticos que serão necessários como parte da navegação atual. Quando o navegador detecta o pré-carregamento, ele inicia um download de alta prioridade para o recurso. Assim, quando ele for necessário, ele já estará buscado ou parcialmente disponível. No entanto, isso não funciona para módulos.

É aqui que as coisas ficam complicadas. Há vários modos de credenciais para recursos. Para conseguir um acerto de cache, eles precisam corresponder. Caso contrário, você vai buscar o recurso duas vezes. Não é preciso dizer que a busca dupla é ruim, porque desperdiça a largura de banda do usuário e faz com que ele espere mais tempo sem motivo.

Para as tags <script> e <link>, é possível definir o modo de credenciais com o atributo crossorigin. No entanto, um <script type="module"> sem o atributo crossorigin indica um modo de credenciais de omit, que não existe para <link rel="preload">. Isso significa que você precisa mudar o atributo crossorigin no <script> e no <link> para um dos outros valores. Talvez não seja fácil fazer isso se o que você está tentando pré-carregar for uma dependência de outros módulos.

Além disso, buscar o arquivo é apenas a primeira etapa para executar o código. Primeiro, o navegador precisa analisar e compilar o arquivo. O ideal é que isso aconteça com antecedência, para que, quando o módulo for necessário, o código esteja pronto para execução. No entanto, o V8 (mecanismo JavaScript do Chrome) analisa e compila módulos de maneira diferente de outros JavaScripts. <link rel="preload"> não oferece nenhuma maneira de indicar que o arquivo carregado é um módulo. Portanto, tudo o que o navegador pode fazer é carregar o arquivo e colocá-lo no cache. Depois que o script é carregado usando uma tag <script type="module"> (ou é carregado por outro módulo), o navegador analisa e compila o código como um módulo JavaScript.

Em resumo, sim. Com um tipo link específico para pré-carregar módulos, podemos escrever HTML simples sem se preocupar com o modo de credenciais que estamos usando. Os padrões simplesmente funcionam.

<head>
  <link rel="modulepreload" href="super-critical-stuff.mjs">
</head>
[...]
<script type="module" src="super-critical-stuff.mjs">

E como o navegador agora sabe que o que você está carregando é um módulo, ele pode ser inteligente e analisar e compilar o módulo assim que ele terminar de buscar, em vez de esperar até que ele tente ser executado.

Compatibilidade com navegadores

  • Chrome: 66.
  • Edge: ≤79.
  • Firefox: 115.
  • Safari: 17.

Origem

Mas e as dependências dos módulos?

Que bom que você perguntou! Este artigo não abordou uma questão importante: a recursão.

A especificação <link rel="modulepreload"> permite o carregamento opcional não apenas do módulo solicitado, mas também de toda a árvore de dependências. Os navegadores não precisam fazer isso, mas podem.

Qual seria a melhor solução entre navegadores para pré-carregar um módulo e a árvore de dependências dele, já que você vai precisar da árvore de dependências completa para executar o app?

Os navegadores que optam por pré-carregar dependências recursivamente precisam ter uma eliminação de duplicação de módulos robusta. Portanto, em geral, a prática recomendada é declarar o módulo e a lista plana das dependências e confiar que o navegador não vai buscar o mesmo módulo duas vezes.

<head>
  <!-- dog.js imports dog-head.js, which in turn imports
       dog-head-mouth.js, which imports dog-head-mouth-tongue.js. -->
  <link rel="modulepreload" href="dog-head-mouth-tongue.mjs">
  <link rel="modulepreload" href="dog-head-mouth.mjs">
  <link rel="modulepreload" href="dog-head.mjs">
  <link rel="modulepreload" href="dog.mjs">
</head>

O pré-carregamento de módulos ajuda no desempenho?

O pré-carregamento pode ajudar a maximizar o uso da largura de banda, informando ao navegador o que ele precisa buscar para que não fique preso sem nada para fazer durante esses longos retornos. Se você estiver testando módulos e tiver problemas de desempenho devido a árvores de dependência profundas, criar uma lista simples de pré-carregamentos pode ajudar.

No entanto, a performance do módulo ainda está em desenvolvimento. Portanto, observe de perto o que está acontecendo no seu aplicativo com as ferramentas para desenvolvedores e considere agrupar o aplicativo em vários blocos enquanto isso. Há muitos trabalhos de módulo em andamento no Chrome, então estamos chegando mais perto de dar aos agrupadores o descanso merecido.