WebAssembly-Funktionserkennung

Hier erfährst du, wie du die neuesten WebAssembly-Funktionen verwenden und gleichzeitig Nutzer in allen Browsern unterstützen kannst.

WebAssembly 1.0 wurde vor vier Jahren veröffentlicht, aber damit ist die Entwicklung noch nicht beendet. Neue Funktionen werden über den Prozess zur Angebotsstandardisierung hinzugefügt. Wie bei neuen Funktionen im Web können auch deren Implementierungsreihenfolge und -fristen je nach Suchmaschine erheblich variieren. Wenn Sie diese neuen Funktionen verwenden möchten, müssen Sie dafür sorgen, dass keiner Ihrer Nutzer ausgeschlossen wird. In diesem Artikel erfahren Sie, wie Sie dies erreichen können.

Bei einigen neuen Funktionen wird die Codegröße verbessert, indem neue Anweisungen für gängige Vorgänge hinzugefügt werden. Einige bieten leistungsstarke Grundfunktionen, während andere die Entwicklererfahrung und die Integration mit dem Rest des Webs verbessern.

Die vollständige Liste der Vorschläge und ihre jeweiligen Phasen finden Sie im offiziellen Repository. Sie können ihren Implementierungsstatus in Engines auf der offiziellen Seite für die Roadmap für neue Funktionen einsehen.

Damit Nutzer aller Browser Ihre Anwendung verwenden können, müssen Sie überlegen, welche Funktionen Sie nutzen möchten. Teilen Sie sie dann je nach Browserunterstützung in Gruppen auf. Kompilieren Sie dann Ihre Codebasis für jede dieser Gruppen separat. Auf der Browserseite müssen Sie dann unterstützte Funktionen ermitteln und das entsprechende JavaScript- und Wasm-Bundle laden.

Funktionen zum Auswählen und Gruppieren

Gehen wir die Schritte durch, indem wir einen beliebigen Feature-Set als Beispiel auswählen. Nehmen wir an, ich möchte in meiner Mediathek aus Gründen der Größe und Leistung SIMD, Threads und die Ausnahmebehandlung verwenden. Es werden folgende Browser unterstützt:

Eine Tabelle, in der die Browserunterstützung der ausgewählten Funktionen dargestellt ist.
Sehen Sie sich diese Feature-Tabelle auf webassembly.org/roadmap an.

Sie können Browser in die folgenden Kohorten unterteilen, um sicherzustellen, dass alle Nutzer optimal profitieren:

  • Chrome-basierte Browser: Threads, SIMD und Ausnahmebehandlung werden unterstützt.
  • Firefox: Thread und SIMD werden unterstützt, Ausnahmebehandlung nicht.
  • Safari: Threads werden unterstützt, SIMD und Ausnahmebehandlung nicht.
  • Bei anderen Browsern wird nur die grundlegende WebAssembly-Unterstützung angenommen.

Diese Aufschlüsselung ist nach Featureunterstützung in der neuesten Version des jeweiligen Browsers unterteilt. Moderne Browser sind zeitlos und werden automatisch aktualisiert, sodass Sie sich in den meisten Fällen nur um die neueste Version kümmern müssen. Solange Sie jedoch die WebAssembly-Baseline als Fallback-Kohorte verwenden, können Sie auch Nutzern mit veralteten Browsern eine funktionierende Anwendung zur Verfügung stellen.

Kompilierung für verschiedene Feature-Sets

WebAssembly hat keine integrierte Methode, um unterstützte Funktionen während der Laufzeit zu erkennen. Daher müssen alle Anweisungen im Modul auf dem Ziel unterstützt werden. Aus diesem Grund müssen Sie den Quellcode für jeden dieser verschiedenen Feature-Sets separat in Wasm kompilieren.

Toolchain und Build-System unterscheiden sich. Wie Sie diese Funktionen optimieren können, erfahren Sie in der Dokumentation Ihres eigenen Compilers. Der Einfachheit halber verwende ich im folgenden Beispiel eine C++-Bibliothek mit nur einer Datei und zeige Ihnen, wie sie mit Emscripten kompiliert wird.

Ich verwende SIMD über SSE2-Emulation, Threads über die Unterstützung der Pthreads-Bibliothek und wähle zwischen Wasm-Ausnahmebehandlung und der Fallback-JavaScript-Implementierung aus:

# First bundle: threads + SIMD + Wasm exceptions
$ emcc main.cpp -o main.threads-simd-exceptions.mjs -pthread -msimd128 -msse2 -fwasm-exceptions
# Second bundle: threads + SIMD + JS exceptions fallback
$ emcc main.cpp -o main.threads-simd.mjs -pthread -msimd128 -msse2 -fexceptions
# Third bundle: threads + JS exception fallback
$ emcc main.cpp -o main.threads.mjs -pthread -fexceptions
# Fourth bundle: basic Wasm with JS exceptions fallback
$ emcc main.cpp -o main.basic.mjs -fexceptions

Der C++-Code selbst kann #ifdef __EMSCRIPTEN_PTHREADS__ und #ifdef __SSE2__ verwenden, um bedingt zwischen parallelen (Threads und SIMD) Implementierungen derselben Funktionen und den seriellen Implementierungen zum Zeitpunkt der Kompilierung zu wählen. Er würde so aussehen:

void process_data(std::vector<int>& some_input) {
#ifdef __EMSCRIPTEN_PTHREADS__
#ifdef __SSE2__
  // …implementation using threads and SIMD for max speed
#else
  // …implementation using threads but not SIMD
#endif
#else
  // …fallback implementation for browsers without those features
#endif
}

Für die Ausnahmebehandlung sind keine #ifdef-Anweisungen erforderlich, da sie unabhängig von der zugrunde liegenden Implementierung, die über die Kompilierungs-Flags ausgewählt wurde, in C++ auf die gleiche Weise verwendet werden kann.

Richtiges Bundle laden

Nachdem Sie die Sets für alle Funktionskohorten erstellt haben, müssen Sie das richtige aus der Haupt-JavaScript-Anwendung laden. Dazu müssen Sie zuerst herausfinden, welche Funktionen im aktuellen Browser unterstützt werden. Dazu können Sie die Bibliothek wasm-feature-detection verwenden. Wenn Sie es mit dem dynamischen Import kombinieren, können Sie das am besten optimierte Bundle in jedem Browser laden:

import { simd, threads, exceptions } from 'https://unpkg.com/wasm-feature-detect?module';

let initModule;
if (await threads()) {
  if (await simd()) {
    if (await exceptions()) {
      initModule = import('./main.threads-simd-exceptions.mjs');
    } else {
      initModule = import('./main.threads-simd.mjs');
    }
  } else {
    initModule = import('./main.threads.mjs');
  }
} else {
  initModule = import('./main.basic.mjs');
}

const Module = await initModule();
// now you can use `Module` Emscripten object like you normally would

Abschließende Worte

In diesem Beitrag habe ich gezeigt, wie man Bundles für verschiedene Feature-Sets auswählt, erstellt und zwischen ihnen wechselt.

Wenn die Anzahl der Funktionen steigt, kann es sein, dass die Anzahl der Funktionskohorten nicht mehr aufrechterhalten werden kann. Um dieses Problem zu umgehen, können Sie Funktionskohorten basierend auf Ihren realen Nutzerdaten auswählen, die weniger beliebten Browser überspringen und auf etwas weniger optimale Kohorten zurückgreifen lassen. Solange Ihre Anwendung weiterhin für alle Nutzer funktioniert, kann dieser Ansatz ein vernünftiges Gleichgewicht zwischen progressiver Verbesserung und Laufzeitleistung bieten.

In Zukunft könnte WebAssembly eine integrierte Methode erhalten, um unterstützte Funktionen zu erkennen und zwischen verschiedenen Implementierungen derselben Funktion innerhalb des Moduls zu wechseln. Ein solcher Mechanismus wäre jedoch selbst eine Funktion nach dem MVP, die anhand des obigen Ansatzes ermittelt und unter bestimmten Bedingungen geladen werden muss. Bis dahin ist dies die einzige Möglichkeit, Code mit den neuen WebAssembly-Funktionen in allen Browsern zu erstellen und zu laden.