Precargar módulos

Sérgio Gomes

Fecha de publicación: 23 de noviembre de 2024

El desarrollo basado en módulos ofrece algunas ventajas reales en términos de capacidad de almacenamiento en caché, lo que te ayuda a reducir la cantidad de bytes que debes enviar a tus usuarios. El nivel de detalle más fino del código también ayuda con la historia de carga, ya que te permite priorizar el código crítico de tu aplicación.

Sin embargo, las dependencias de módulos introducen un problema de carga, ya que el navegador debe esperar a que se cargue un módulo antes de descubrir cuáles son sus dependencias. Una forma de evitar esto es precargar las dependencias, de modo que el navegador conozca todos los archivos con anticipación y pueda mantener la conexión ocupada.

<link rel="preload"> es una forma de solicitar recursos de forma declarativa con anticipación, antes de que el navegador los necesite.

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

Esto funciona muy bien con recursos como las fuentes, que a menudo se ocultan dentro de los archivos CSS, a veces en varios niveles de profundidad. En esa situación, el navegador tendría que esperar varias idas y vueltas antes de descubrir que necesita recuperar un archivo de fuente grande, cuando podría haber usado ese tiempo para iniciar la descarga y aprovechar el ancho de banda completo de la conexión.

<link rel="preload"> y su equivalente de encabezado HTTP proporcionan una forma simple y declarativa de informar al navegador de inmediato sobre los archivos críticos que se necesitarán como parte de la navegación actual. Cuando el navegador ve la carga previa, inicia una descarga de alta prioridad para el recurso, de modo que, cuando realmente se necesite, ya se haya recuperado o esté parcialmente disponible. Sin embargo, no funciona para los módulos.

Aquí es donde las cosas se complican. Existen varios modos de credenciales para los recursos y, para obtener un acierto de caché, deben coincidir. De lo contrario, terminarás recuperando el recurso dos veces. No hace falta decir que la recuperación doble es mala, ya que desperdicia el ancho de banda del usuario y lo hace esperar más tiempo sin motivo.

En el caso de las etiquetas <script> y <link>, puedes establecer el modo de credenciales con el atributo crossorigin. Sin embargo, resulta que un <script type="module"> sin atributo crossorigin indica un modo de credenciales de omit, que no existe para <link rel="preload">. Esto significa que tendrías que cambiar el atributo crossorigin en <script> y <link> a uno de los otros valores, y es posible que no tengas una forma sencilla de hacerlo si lo que intentas precargar es una dependencia de otros módulos.

Además, recuperar el archivo es solo el primer paso para ejecutar el código. Primero, el navegador debe analizarlo y compilarlo. Lo ideal es que esto también suceda con anticipación, de modo que, cuando se necesite el módulo, el código esté listo para ejecutarse. Sin embargo, V8 (el motor JavaScript de Chrome) analiza y compila módulos de manera diferente a otros JavaScript. <link rel="preload"> no proporciona ninguna forma de indicar que el archivo que se carga es un módulo, por lo que todo lo que puede hacer el navegador es cargar el archivo y ponerlo en la caché. Una vez que se carga la secuencia de comandos con una etiqueta <script type="module"> (o la carga otro módulo), el navegador analiza y compila el código como un módulo de JavaScript.

En pocas palabras, sí. Si tenemos un tipo link específico para la carga previa de módulos, podemos escribir HTML simple sin preocuparnos por el modo de credenciales que usamos. Los valores predeterminados simplemente funcionan.

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

Y como el navegador ahora sabe que lo que estás cargando previamente es un módulo, puede ser inteligente y analizar y compilar el módulo en cuanto termine de recuperarlo, en lugar de esperar hasta que intente ejecutarse.

Navegadores compatibles

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

Origen

Pero ¿qué sucede con las dependencias de los módulos?

Me alegra que lo preguntes. Sin embargo, hay algo que no se abordó en este artículo: la recursividad.

En realidad, la especificación de <link rel="modulepreload"> permite cargar de forma opcional no solo el módulo solicitado, sino también todo su árbol de dependencias. Los navegadores no tienen que hacerlo, pero pueden hacerlo.

Entonces, ¿cuál sería la mejor solución multinavegador para precargar un módulo y su árbol de dependencias, ya que necesitarás el árbol de dependencias completo para ejecutar la app?

Los navegadores que eligen precargar dependencias de forma recursiva deben tener una anulación de duplicación sólida de los módulos, por lo que, en general, la práctica recomendada sería declarar el módulo y la lista plana de sus dependencias, y confiar en que el navegador no recupere el mismo módulo dos veces.

<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>

¿La carga previa de módulos ayuda al rendimiento?

La carga previa puede ayudar a maximizar el uso del ancho de banda, ya que le indica al navegador qué debe recuperar para que no se quede sin nada que hacer durante esos largos recorridos de ida y vuelta. Si experimentas con módulos y tienes problemas de rendimiento debido a árboles de dependencias profundos, crear una lista plana de cargas previas puede ser de ayuda.

Dicho esto, aún se está trabajando en el rendimiento del módulo, así que asegúrate de observar con atención lo que sucede en tu aplicación con las herramientas para desarrolladores y, mientras tanto, considera agrupar tu aplicación en varios segmentos. Sin embargo, hay mucho trabajo en curso con los módulos en Chrome, por lo que estamos cada vez más cerca de darles a los empaquetadores el descanso que se merecen.