Prefetch
Montar una micro-app implica una ida y vuelta por la red. El prefetch adelanta esa ida y vuelta a un momento en el que el usuario no está esperando: mientras pasa el ratón por el enlace, mientras la sección entra en pantalla, mientras el hilo principal está en reposo.
No se ejecuta nada. El prefetch solo precalienta la caché HTTP con la URL del
módulo de la app, para que el mount() posterior lo encuentre en local.
import { prefetch, prefetchAll } from 'wu-framework';// o wu.prefetch / wu.prefetchAllwu.prefetch()
Sección titulada «wu.prefetch()»prefetch(appNames: string | string[], options?: { on?: 'immediate' | 'hover' | 'visible' | 'idle'; target?: string | Element; eagerness?: 'conservative' | 'moderate' | 'eager';}): Promise<void> | (() => void)| Opción | Por defecto | Significado |
|---|---|---|
on |
'immediate' |
Cuándo dispararlo. |
target |
— | Elemento o selector. Obligatorio para hover y visible. |
eagerness |
'moderate' |
Se pasa a la Speculation Rules API cuando está disponible. Se ignora en caso contrario. |
El tipo de retorno depende del disparador
Sección titulada «El tipo de retorno depende del disparador»Esto es deliberado y es lo que hay que hacer bien:
| Disparador | Devuelve |
|---|---|
'immediate' |
Promise<void> — hazle await |
'hover', 'visible', 'idle' |
Una función de limpieza — llámala para cancelar |
// Inmediato: devuelve una Promiseawait wu.prefetch('cart');
// Diferido: devuelve una función de limpiezaconst cancel = wu.prefetch('cart', { on: 'hover', target: '#cart-link' });// más tarde, p. ej. al desmontar el componentecancel();prefetch() no es una función async a propósito. Marcarla como async
envolvería en una Promise la función de limpieza de las ramas diferidas, y el
patrón documentado const off = wu.prefetch(...) / off() fallaría con “off is
not a function”. La rama inmediata sigue devolviendo una Promise desde dentro,
así que await sigue funcionando.
Caso límite que conviene señalar. Un disparador desconocido registra
[WuPrefetch] Unknown trigger "<x>", using immediate y cae al inmediato, lo que
significa que devuelve una Promise, no una función. Una errata en on:
convierte tu cancel() en un TypeError.
Los disparadores en detalle
Sección titulada «Los disparadores en detalle»immediate — se dispara ya.
await wu.prefetch(['profile', 'settings']);hover — escucha mouseenter y focusin sobre el destino (ambos
pasivos, así que quien usa teclado también lo obtiene). Se dispara una vez y
luego elimina él mismo ambos listeners.
const off = wu.prefetch('cart', { on: 'hover', target: '#cart-link' });Si el destino no se puede resolver, avisa con
[WuPrefetch] hover trigger requires a target element or selector y devuelve una
función no-op: nunca se hace prefetch de nada. Un selector que no coincide con
nada cuenta como no resuelto.
visible — un IntersectionObserver con rootMargin: '200px', de modo que
el prefetch empieza 200 px antes de que el elemento entre realmente en el
viewport. Se dispara una vez y luego se desconecta.
const off = wu.prefetch('reviews', { on: 'visible', target: '#reviews-section' });Un destino sin resolver se comporta como en hover. En un entorno sin
IntersectionObserver, hace el prefetch de inmediato y devuelve una limpieza
no-op.
idle — requestIdleCallback con un timeout de 3000 ms, cayendo a
setTimeout(fn, 2000) donde no esté soportado. La función de limpieza cancela la
callback pendiente.
const off = wu.prefetch(['profile', 'settings'], { on: 'idle' });Qué se descarga realmente
Sección titulada «Qué se descarga realmente»Solo la URL del módulo ES de la app, resuelta con la misma lógica de
resolución de rutas que usa mount(). Ni el CSS, ni el manifiesto, ni ningún
otro recurso. El módulo nunca se importa, ni se evalúa, ni tiene ocasión de
llamar a wu.define().
Tres estrategias; gana la primera soportada:
- Speculation Rules API — un
<script type="speculationrules">en el head, respetandoeagerness. El elemento script se reemplaza por completo en cada actualización, como exige la especificación. <link rel="modulepreload"><link rel="prefetch" as="script">
Una app se omite, en silencio o con un aviso, cuando ya se le hizo prefetch, ya
está montada, ya está definida, o no está registrada
([WuPrefetch] App "<name>" not registered, cannot prefetch). Si una URL no se
puede resolver, se avisa y se omite: los fallos de prefetch nunca lanzan y nunca
rompen la página.
wu.prefetchAll()
Sección titulada «wu.prefetchAll()»prefetchAll(options?: PrefetchOptions): Promise<void | (() => void)>Hace prefetch de todas las apps registradas que no estén montadas y a las que no se les haya hecho ya.
// Después de que el camino crítico haya renderizadoawait wu.prefetchAll({ on: 'idle' });Caso límite: una asimetría con prefetch(). prefetchAll() sí es una
función async, así que con un disparador diferido la función de limpieza vuelve
envuelta en una Promise:
const off = wu.prefetchAll({ on: 'idle' }); // Promise<Function>, no Functionoff(); // TypeError
const realOff = await wu.prefetchAll({ on: 'idle' }); // esto sí funcionarealOff();Si no hay nada de lo que hacer prefetch, registra
[WuPrefetch] No apps to prefetch y resuelve undefined.
getStats()
Sección titulada «getStats()»getStats(): { prefetched: string[]; activeObservers: number; activeListeners: number; speculationRulesSupported: boolean; modulePreloadSupported: boolean; speculationRules: object;}const s = wu.prefetcher.getStats();console.log(s.prefetched); // qué se ha precalentadoconsole.log(s.speculationRulesSupported);prefetched contiene una mezcla de nombres de app y URLs resueltas: es el
conjunto de deduplicación, no una lista limpia. Además es la única forma de
comprobar si algo tiene ya prefetch; no existe isPrefetched().
Limitaciones honestas
Sección titulada «Limitaciones honestas»- No hay método
cancel(). La función de limpieza de cada llamada es la única forma de cancelar un prefetch diferido. Consérvala. - No hay
isPrefetched(). UsagetStats().prefetched.includes(name), teniendo en cuenta que el conjunto mezcla nombres y URLs. - Solo el módulo. Las hojas de estilo, las fuentes y las imágenes que necesita la app no se precargan. En una app cargada de CSS, la ganancia es menor de lo que esperarías.
- El tipo de retorno varía según el disparador, y una errata en el disparador lo cambia en silencio.
prefetchAll()envuelve su retorno en una Promise yprefetch()no.- Prerender no está implementado. La estructura interna de speculation rules
reserva una lista
prerender, pero nada escribe en ella. - El prefetch es una sugerencia, no una garantía. El navegador puede ignorarlo bajo presión de memoria, en una conexión con tarifa medida, o con el ahorro de datos activado.
- Cuesta ancho de banda. Hacer prefetch de todo en una conexión móvil para ahorrar unos cientos de milisegundos suele ser el intercambio equivocado. Haz prefetch de lo que el usuario esté a punto de abrir de forma plausible.
