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">
<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.
Por que <link rel="preload">
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.
O <link rel="modulepreload">
é apenas <link rel="preload">
para módulos?
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.
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.