12k
All articles

Cómo corregir errores de TypeScript con las nuevas API del navegador

Corrige errores de TypeScript en nuevas API del navegador con modern-web-types o una declaración propia. Comprende el desfase de los tipos DOM y comprueba la compatibilidad en ejecución.

OpenReplay Team
OpenReplay Team
Cómo corregir errores de TypeScript con las nuevas API del navegador

TypeScript muestra el error “Property does not exist on type” con algunas API nuevas del navegador porque sus tipos DOM integrados solo incluyen las API que admiten al menos dos motores de navegador. Puedes solucionarlo sustituyendo la biblioteca DOM por una más completa o declarando tú mismo el miembro que falta.

Llamas a una API que MDN documenta y que Chrome ya ejecuta, y el compilador te dice que no existe. Tiparla como any hace desaparecer la línea roja, pero pierdes la comprobación de tipos en ese código.

Este artículo explica por qué los tipos DOM de TypeScript van por detrás de los navegadores y, a continuación, presenta dos soluciones con seguridad de tipos y la comprobación en tiempo de ejecución que sigue siendo necesaria con cualquiera de ellas.

Puntos clave

  • El archivo lib.dom.d.ts de TypeScript se genera con microsoft/TypeScript-DOM-lib-generator, que excluye cualquier API compatible con menos de dos de los principales motores de navegador.
  • modern-web-types rebaja ese umbral a un único motor estable. Instálalo con npm install --save-dev @typescript/lib-dom@npm:modern-web-types.
  • En TypeScript 6 o posterior, el paquete solo surte efecto con "libReplacement": true en compilerOptions. Las versiones de TypeScript 4.5 a 5.x no necesitan ninguna configuración adicional.
  • Para añadir tú mismo un miembro que falta, usa interface Window en un archivo .d.ts global. Un alias type no se fusiona.
  • Disponer de tipos para una API no significa que el navegador la admita, por lo que las API de un solo motor siguen necesitando una comprobación como 'fetchLater' in window.

¿Qué significa “Property does not exist on type”?

El error TS2339 de TypeScript, “Property does not exist on type”, aparece cuando tu código llama a una API real del navegador que los tipos DOM incluidos en TypeScript no declaran. Tu propio código no ha añadido una variable global como window.myThing. Es el navegador quien proporciona este método, pero los tipos de TypeScript no lo conocen.

window.fetchLater('/analytics', { method: 'POST', activateAfter: 60_000 });
// error TS2339: Property 'fetchLater' does not exist on type 'Window & typeof globalThis'.

fetchLater() pone en cola una solicitud que el navegador envía cuando el usuario abandona la página (al descargarse o pasar a la bfcache) o cuando vence el tiempo de espera opcional activateAfter, si esto ocurre antes. El anuncio de modern-web-types de Philip Walton también menciona las view transitions con ámbito de elemento y la propiedad scripts de Long Animation Frame como ejemplos de API afectadas por el mismo error.

¿Por qué los tipos DOM de TypeScript van por detrás de los navegadores?

Los tipos DOM de TypeScript van por detrás de los navegadores de forma intencionada. El README de TypeScript-DOM-lib-generator establece como requisito de inclusión el soporte en dos o más de los principales motores —Gecko (Firefox), Blink (Chrome/Edge) y WebKit (Safari)—, de modo que solo entran las API que cuentan con un consenso real entre fabricantes. Todos los navegadores basados en Chromium funcionan sobre Blink, así que cuentan como un único motor.

En la práctica, esto significa lo siguiente: una API que solo se ha lanzado en Blink puede ejecutarse en Chrome, Edge y cualquier otro navegador Chromium, y aun así TypeScript dirá que no existe. La entrada de web-features para fetchLater solo indica compatibilidad en navegadores basados en Chromium, por lo que no cumple la regla de los dos motores.

El error “Property does not exist on type” refleja la política de inclusión de TypeScript. No indica si el navegador que ejecuta tu código admite la API.

Solución 1: sustituir lib.dom por modern-web-types

modern-web-types es un paquete de Philip Walton que reemplaza las bibliotecas DOM y WebWorker de TypeScript sin requerir otros cambios en tu código. Como explica en su artículo de presentación, se genera con el propio generador de TypeScript y con datos de w3c/webref, pero incluye cualquier API que se haya lanzado en un motor de navegador estable.

Instálalo con el alias @typescript/lib-dom, que es el nombre que TypeScript busca al resolver su biblioteca DOM:

npm install --save-dev @typescript/lib-dom@npm:modern-web-types

En TypeScript 6 o posterior, instalar el alias no tiene ningún efecto hasta que también actives el reemplazo de bibliotecas:

{
  "compilerOptions": {
    "libReplacement": true
  }
}

En las versiones de TypeScript 4.5 a 5.x, omite esta opción. TypeScript 4.5 introdujo la búsqueda de @typescript/lib-* y la aplicaba automáticamente. TypeScript 5.8 la puso tras el flag libReplacement, pero lo dejó activado por defecto, y TypeScript 6 lo desactivó, como explica el pull request de modern-web-types que recomienda el reemplazo de bibliotecas. Según el anuncio de TypeScript 7.0, la versión 7.0 adopta los valores predeterminados de la 6.0, incluido libReplacement: false, por lo que la 7.0 también necesita el flag.

Proyectos con workers

El código de los workers utiliza un punto de entrada independiente, que se añade mediante types. No incluyas "WebWorker" en lib, ya que el punto de entrada lo sustituye:

{
  "compilerOptions": {
    "types": ["modern-web-types/webworker"],
    "lib": ["ESNext"]
  }
}

En TypeScript 5.x, el simple hecho de definir types impide que TypeScript incluya automáticamente todos los paquetes @types/* de node_modules. En TypeScript 6 y posteriores, types ya tiene [] como valor predeterminado, así que no se incluye nada automáticamente en ningún caso. En ambas situaciones, si dependes de paquetes como @types/node, añádelos al mismo array. El README también enumera los puntos de entrada serviceworker, sharedworker y audioworklet.

Solución 2: declarar la API que falta mediante fusión de interfaces

Si solo necesitas una API, basta con un pequeño archivo de declaraciones. Declara una interfaz con el mismo nombre que la integrada y TypeScript fusionará las declaraciones. Tiene que ser una interface. Un alias type Window = { ... } falla con un error de identificador duplicado, porque los alias de tipo no se fusionan.

La firma que aparece a continuación sigue la documentación de MDN: fetchLater() acepta los mismos argumentos que fetch() más la opción activateAfter, y devuelve un FetchLaterResult cuyo booleano activated indica si la solicitud ya se ha enviado.

// src/types/fetch-later.d.ts
interface DeferredRequestInit extends RequestInit {
  activateAfter?: number;
}

interface FetchLaterResult {
  readonly activated: boolean;
}

interface Window {
  fetchLater(input: RequestInfo | URL, init?: DeferredRequestInit): FetchLaterResult;
}

Esto funciona sin un bloque declare global porque un archivo .d.ts sin import ni export de nivel superior es un script global. Hay tres reglas para que siga funcionando:

  • Mantén el archivo como no módulo. Si añades un import o export de nivel superior, el archivo se convierte en un módulo. Las interfaces dejan de fusionarse globalmente y volverías a necesitar declare global.
  • Asegúrate de que tsconfig lo incluya. El archivo debe coincidir con tus patrones de include.
  • Elimínalo cuando TypeScript incorpore la API. En cuanto lib.dom declare los mismos miembros, tus copias serán, en el mejor de los casos, redundantes y, en el peor, podrán entrar en conflicto.

Esta declaración solo tipa window.fetchLater(...). Llamar a la función global fetchLater(...) sin prefijo también requiere una declaración de función global, así que utiliza siempre window.fetchLater en tu código.

¿Sigue siendo necesaria la detección de funcionalidades?

Sí. Los tipos de TypeScript describen la forma de una API, pero no indican qué navegadores la incluyen. Ambas soluciones hacen que el compilador acepte fetchLater, pero los navegadores que no la admiten seguirán lanzando una excepción en tiempo de ejecución, así que protege la llamada:

if ('fetchLater' in window) {
  try {
    window.fetchLater('/analytics', { method: 'POST', activateAfter: 60_000 });
  } catch {
    // quota exceeded: fall back or drop the beacon
  }
} else {
  // fall back, e.g. send the data immediately
}

El bloque try está ahí porque MDN recomienda capturar QuotaExceededError en prácticamente todas las llamadas a fetchLater(). Trata las API de un solo motor como mejora progresiva y comprueba la compatibilidad con tus navegadores objetivo en la página de fetchLater de MDN.

¿Qué solución deberías usar?

EnfoqueConfiguraciónCoberturaCuando TypeScript se ponga al día
modern-web-typesUna instalación y un flag en TS 6+Todas las API de un solo motorNo hay nada que eliminar
.d.ts escrito a manoUn pequeño archivo por APISolo lo que escribasEliminar el archivo
as any / @ts-ignoreNingunaNingunaOculta errores reales indefinidamente

Usa modern-web-types si tu proyecto utiliza varias API nuevas o adopta habitualmente funcionalidades antes de que lleguen a otros motores. Usa un .d.ts escrito a mano si solo necesitas uno o dos miembros y prefieres no sustituir toda la biblioteca DOM. Evita as any y @ts-ignore: desactivan la comprobación de toda la expresión o línea y acabarán ocultando errores reales. Imponer esta norma con una configuración de TypeScript ESLint y las reglas @typescript-eslint/no-explicit-any y @typescript-eslint/ban-ts-comment evita que se acumulen soluciones provisionales sueltas.

Conclusión

El error “Property does not exist on type” de TypeScript con las API nuevas del navegador se debe a la regla de los dos motores de TypeScript, no a un fallo en tu código ni a la ausencia de "dom". Sustituye lib.dom por modern-web-types si usas API nuevas con regularidad, o fusiona una única interfaz en Window para un caso puntual, y mantén siempre una comprobación en tiempo de ejecución en cada llamada. Empieza por revisar tu versión de TypeScript: si es la 6 o posterior, añade "libReplacement": true al instalar el paquete.

Preguntas frecuentes

¿Instalar @types/web soluciona la falta de tipos para las API nuevas del navegador?

Solo para las API que ya cumplen la regla de los dos motores. @types/web es la misma biblioteca DOM que se distribuye con TypeScript. Se genera con microsoft/TypeScript-DOM-lib-generator y se publica en npm, por lo que puede ofrecer tipos DOM más recientes antes de la siguiente versión de TypeScript. Aplica la misma política de inclusión, así que una API exclusiva de Chromium como fetchLater sigue sin aparecer. Se instala mediante el mismo alias @typescript/lib-dom en TypeScript 4.5 y posteriores y, en TypeScript 6 o posterior, también requiere que libReplacement esté en true.

¿Añadir DOM o ESNext a la opción lib soluciona el error?

No. La opción lib solo determina qué archivos de declaraciones incluidos en TypeScript se cargan. El archivo DOM incluido se genera según la regla de los dos motores, por lo que añadir 'DOM', 'DOM.Iterable' o 'ESNext' no puede incorporar una API que ese archivo excluye. ESNext añade funcionalidades del lenguaje JavaScript, no API del navegador. Para corregir el error, sustituye la biblioteca DOM por modern-web-types o declara el miembro que falta en un archivo .d.ts global.

¿Cómo declaro un método que falta en Element o HTMLElement en lugar de en Window?

Fusiónalo en la interfaz en la que la especificación define el miembro. En un archivo .d.ts global sin import ni export de nivel superior, declara interface Element con el nuevo método. HTMLElement, SVGElement y las demás interfaces de elementos extienden Element, por lo que todas lo heredan. El método startViewTransition() con ámbito de elemento se define en Element, así que declararlo solo en HTMLElement deja los elementos SVG sin tipar. Toma la firma exacta de MDN o de la especificación.

Open-source session replay

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.