12k
All articles

TypeScript-Fehler bei neuen Browser-APIs beheben

Beheben Sie TypeScript-Fehler bei neuen Browser-APIs mit modern-web-types oder eigenen Deklarationen. Erfahren Sie, warum DOM-Typen hinterherhinken und wie Sie die Laufzeitunterstützung prüfen.

OpenReplay Team
OpenReplay Team
TypeScript-Fehler bei neuen Browser-APIs beheben

TypeScript meldet bei manchen neuen Browser-APIs „Property does not exist on type“, weil die mitgelieferten DOM-Typen nur APIs enthalten, die von mindestens zwei Browser-Engines unterstützt werden. Sie beheben das, indem Sie eine umfassendere DOM-Bibliothek einbinden oder den fehlenden Member selbst deklarieren.

Sie rufen eine API auf, die bei MDN dokumentiert ist und in Chrome bereits läuft, und der Compiler behauptet, sie existiere nicht. Wenn Sie sie als any typisieren, verschwindet die rote Unterstreichung, allerdings verlieren Sie damit die Typprüfung für diesen Code.

Dieser Artikel erklärt, warum die DOM-Typen von TypeScript den Browsern hinterherhinken, stellt zwei typsichere Lösungen vor und zeigt die Laufzeitprüfung, die Sie bei beiden weiterhin benötigen.

Das Wichtigste in Kürze

  • Die Datei lib.dom.d.ts von TypeScript wird von microsoft/TypeScript-DOM-lib-generator erzeugt. Dieser lässt jede API weg, die von weniger als zwei großen Browser-Engines unterstützt wird.
  • modern-web-types senkt diese Hürde auf eine stabile Engine. Installation: npm install --save-dev @typescript/lib-dom@npm:modern-web-types.
  • Ab TypeScript 6 greift das Paket nur mit "libReplacement": true in den compilerOptions. TypeScript 4.5 bis 5.x benötigt keine zusätzliche Einstellung.
  • Um einen einzelnen fehlenden Member selbst hinzuzufügen, verwenden Sie interface Window in einer globalen .d.ts-Datei. Ein type-Alias wird nicht zusammengeführt.
  • Dass Typen für eine API vorhanden sind, heißt nicht, dass der Browser sie unterstützt. APIs, die nur in einer Engine verfügbar sind, brauchen daher weiterhin eine Prüfung wie 'fetchLater' in window.

Was bedeutet „Property does not exist on type“?

Der TypeScript-Fehler TS2339, „Property does not exist on type“, tritt auf, wenn Ihr Code eine echte Browser-API aufruft, die in den mitgelieferten DOM-Typen von TypeScript nicht deklariert ist. Ihr eigener Code hat dabei kein globales Objekt wie window.myThing hinzugefügt. Der Browser stellt diese Methode bereit, nur die TypeScript-Typen kennen sie nicht.

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

fetchLater() stellt einen Request in eine Warteschlange, den der Browser absendet, wenn die Nutzerin oder der Nutzer die Seite verlässt (die Seite wird entladen oder wandert in den bfcache), oder wenn das optionale activateAfter-Timeout abläuft, falls dies zuerst eintritt. Philip Walton nennt in seiner Ankündigung von modern-web-types außerdem elementbezogene View Transitions und die Eigenschaft scripts von Long Animation Frames als Beispiele für APIs, die vom selben Fehler betroffen sind.

Warum hinken die DOM-Typen von TypeScript den Browsern hinterher?

Die DOM-Typen von TypeScript hinken den Browsern absichtlich hinterher. Die README von TypeScript-DOM-lib-generator legt als Aufnahmekriterium fest, dass mindestens zwei der großen Engines – Gecko (Firefox), Blink (Chrome/Edge) und WebKit (Safari) – eine API unterstützen müssen. So gelangen nur APIs hinein, über die herstellerübergreifend tatsächlich Einigkeit besteht. Alle Chromium-basierten Browser laufen auf Blink und zählen daher zusammen als eine Engine.

In der Praxis bedeutet das: Eine API, die bisher nur in Blink ausgeliefert wurde, kann in Chrome, Edge und jedem anderen Chromium-Browser laufen, und TypeScript behauptet trotzdem, dass sie nicht existiert. Der web-features-Eintrag für fetchLater führt ausschließlich Chromium-basierte Browser als unterstützend auf, daher erfüllt die API die Zwei-Engine-Regel nicht.

Der Fehler „Property does not exist on type“ spiegelt also die Aufnahmerichtlinie von TypeScript wider. Er sagt nichts darüber aus, ob der Browser, in dem Ihr Code läuft, die API unterstützt.

Lösung 1: lib.dom durch modern-web-types ersetzen

modern-web-types ist ein Paket von Philip Walton, das die DOM- und WebWorker-Bibliotheken von TypeScript austauscht, ohne dass Sie Ihren Code anderweitig ändern müssen. Wie er in seinem Ankündigungsbeitrag erläutert, wird es mit dem TypeScript-eigenen Generator und Daten aus w3c/webref erstellt, nimmt aber jede API auf, die in einer stabilen Browser-Engine ausgeliefert wurde.

Installieren Sie es unter dem Alias @typescript/lib-dom. Nach diesem Namen sucht TypeScript, wenn es seine DOM-Bibliothek auflöst:

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

Ab TypeScript 6 bewirkt die Installation des Alias nichts, solange Sie nicht zusätzlich Lib Replacement aktivieren:

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

Bei TypeScript 4.5 bis 5.x lassen Sie diese Einstellung weg. TypeScript 4.5 führte die Suche nach @typescript/lib-* ein und wandte sie automatisch an. TypeScript 5.8 stellte sie hinter das Flag libReplacement, ließ dieses aber standardmäßig aktiviert, und TypeScript 6 schaltete es ab, wie der Pull Request zu modern-web-types, der Lib Replacement empfiehlt, erklärt. Laut der Ankündigung von TypeScript 7.0 übernimmt 7.0 die Standardwerte von 6.0, einschließlich libReplacement: false, sodass auch 7.0 das Flag benötigt.

Worker-Projekte

Worker-Code verwendet einen separaten Einstiegspunkt, der über types eingebunden wird. Lassen Sie "WebWorker" in lib weg, da der Einstiegspunkt es ersetzt:

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

Unter TypeScript 5.x führt bereits das Setzen von types dazu, dass TypeScript nicht mehr automatisch alle @types/*-Pakete aus node_modules einbindet. Ab TypeScript 6 ist types ohnehin standardmäßig [], sodass in keinem Fall etwas automatisch eingebunden wird. Wenn Sie also auf Pakete wie @types/node angewiesen sind, fügen Sie diese in beiden Fällen demselben Array hinzu. Die README führt außerdem die Einstiegspunkte serviceworker, sharedworker und audioworklet auf.

Lösung 2: Die fehlende API per Interface Merging deklarieren

Wenn Sie nur eine einzelne API benötigen, genügt eine kleine Deklarationsdatei. Deklarieren Sie ein Interface mit demselben Namen wie das eingebaute, und TypeScript führt die Deklarationen zusammen. Es muss sich dabei um ein interface handeln. Ein Alias wie type Window = { ... } schlägt mit einem Duplicate-Identifier-Fehler fehl, weil Typ-Aliase nicht zusammengeführt werden.

Die folgende Signatur orientiert sich an MDN: fetchLater() akzeptiert dieselben Argumente wie fetch() plus eine Option activateAfter und gibt ein FetchLaterResult zurück, dessen boolesche Eigenschaft activated angibt, ob der Request bereits abgesendet wurde.

// 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;
}

Das funktioniert ohne declare global-Wrapper, weil eine .d.ts-Datei ohne import oder export auf oberster Ebene ein globales Skript ist. Drei Regeln sorgen dafür, dass es so bleibt:

  • Die Datei darf kein Modul werden. Wenn Sie ein import oder export auf oberster Ebene hinzufügen, wird die Datei zu einem Modul. Die Interfaces werden dann nicht mehr global zusammengeführt, und Sie bräuchten wieder declare global.
  • Die tsconfig muss die Datei erfassen. Die Datei muss zu Ihren include-Mustern passen.
  • Löschen Sie die Datei, sobald TypeScript die API ausliefert. Wenn lib.dom dieselben Member deklariert, sind Ihre Kopien bestenfalls überflüssig und können schlimmstenfalls Konflikte verursachen.

Diese Deklaration typisiert ausschließlich window.fetchLater(...). Für den Aufruf der globalen Funktion fetchLater(...) ohne Präfix wäre zusätzlich eine globale Funktionsdeklaration nötig. Verwenden Sie in Ihrem Code daher konsequent window.fetchLater.

Brauchen Sie trotzdem noch Feature Detection?

Ja. TypeScript-Typen beschreiben, wie eine API aussieht. Sie sagen nichts darüber aus, welche Browser sie bereitstellen. Beide Lösungen bringen den Compiler dazu, fetchLater zu akzeptieren, doch Browser ohne diese API werfen zur Laufzeit trotzdem einen Fehler. Sichern Sie den Aufruf daher ab:

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
}

Der try-Block ist vorhanden, weil MDN empfiehlt, QuotaExceededError bei nahezu jedem Aufruf von fetchLater() abzufangen. Behandeln Sie APIs, die nur eine Engine unterstützt, als Progressive Enhancement, und prüfen Sie die Unterstützung für Ihre eigenen Ziel-Browser auf der fetchLater-Seite von MDN.

Welche Lösung sollten Sie verwenden?

AnsatzEinrichtungAbdeckungWenn TypeScript nachzieht
modern-web-typesEine Installation, ein Flag ab TS 6Jede API, die eine Engine unterstütztNichts zu entfernen
Handgeschriebene .d.tsEine kleine Datei pro APINur das, was Sie selbst schreibenDatei löschen
as any / @ts-ignoreKeineKeineVerdeckt echte Fehler auf unbestimmte Zeit

Verwenden Sie modern-web-types, wenn Ihr Projekt mehrere neue APIs nutzt oder regelmäßig Features einsetzt, bevor andere Engines sie unterstützen. Verwenden Sie eine handgeschriebene .d.ts, wenn Sie nur einen oder zwei Member benötigen und nicht die gesamte DOM-Bibliothek austauschen möchten. Verzichten Sie auf as any und @ts-ignore: Sie deaktivieren die Prüfung für den gesamten Ausdruck bzw. die gesamte Zeile und verdecken später echte Fehler. Wenn Sie dies mit einem TypeScript-ESLint-Setup und den Regeln @typescript-eslint/no-explicit-any und @typescript-eslint/ban-ts-comment durchsetzen, verhindern Sie, dass sich vereinzelte Workarounds ansammeln.

Fazit

Der TypeScript-Fehler „Property does not exist on type“ bei neuen Browser-APIs geht auf die Zwei-Engine-Regel von TypeScript zurück, nicht auf einen Fehler in Ihrem Code oder einen fehlenden "dom"-Eintrag. Ersetzen Sie lib.dom durch modern-web-types, wenn Sie regelmäßig neue APIs nutzen, oder führen Sie für Einzelfälle ein einzelnes Interface mit Window zusammen – und sichern Sie jeden Aufruf mit einer Laufzeitprüfung ab. Prüfen Sie zunächst Ihre TypeScript-Version: Bei Version 6 oder neuer fügen Sie bei der Installation des Pakets "libReplacement": true hinzu.

FAQs

Behebt die Installation von @types/web fehlende Typen für neue Browser-APIs?

Nur für APIs, die die Zwei-Engine-Regel bereits erfüllen. @types/web ist dieselbe DOM-Bibliothek, die in TypeScript enthalten ist. Sie wird von microsoft/TypeScript-DOM-lib-generator erzeugt und auf npm veröffentlicht und kann daher neuere DOM-Typen vor dem nächsten TypeScript-Release bereitstellen. Sie folgt jedoch derselben Aufnahmerichtlinie, sodass eine reine Chromium-API wie fetchLater weiterhin fehlt. Ab TypeScript 4.5 wird sie über denselben Alias @typescript/lib-dom installiert, und ab TypeScript 6 muss zusätzlich libReplacement auf true gesetzt werden.

Behebt das Hinzufügen von DOM oder ESNext zur Option lib den Fehler?

Nein. Die Option lib legt lediglich fest, welche der mitgelieferten Deklarationsdateien von TypeScript geladen werden. Die mitgelieferte DOM-Datei wird nach der Zwei-Engine-Regel erzeugt, daher kann das Hinzufügen von 'DOM', 'DOM.Iterable' oder 'ESNext' keine API hinzufügen, die in dieser Datei fehlt. ESNext ergänzt JavaScript-Sprachfeatures, keine Browser-APIs. Um den Fehler zu beheben, ersetzen Sie die DOM-Bibliothek durch modern-web-types oder deklarieren den fehlenden Member in einer globalen .d.ts-Datei.

Wie deklariere ich eine fehlende Methode auf Element oder HTMLElement statt auf Window?

Führen Sie sie mit dem Interface zusammen, auf dem die Spezifikation den Member definiert. Deklarieren Sie in einer globalen .d.ts-Datei ohne import oder export auf oberster Ebene interface Element mit der neuen Methode. HTMLElement, SVGElement und die anderen Element-Interfaces erweitern Element und erben die Methode daher alle. Das elementbezogene startViewTransition() ist auf Element definiert; wenn Sie es nur auf HTMLElement deklarieren, bleiben SVG-Elemente untypisiert. Übernehmen Sie die exakte Signatur von MDN oder aus der Spezifikation.

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.