WebAssembly-Funktionserkennung

Hier erfahren Sie, wie Sie die neuesten WebAssembly-Funktionen nutzen und gleichzeitig Nutzer in allen Browsern unterstützen.

WebAssembly 1.0 wurde vor vier Jahren veröffentlicht, aber die Entwicklung endete damit noch nicht. Neue Funktionen werden durch den Standardisierungsprozess für Vorschläge hinzugefügt. Wie bei neuen Funktionen im Web können sich die Implementierungsreihenfolge und die Zeitpläne zwischen den verschiedenen Suchmaschinen erheblich unterscheiden. Wenn Sie diese neuen Funktionen nutzen möchten, müssen Sie dafür sorgen, dass keine Ihrer Nutzer ausgeschlossen werden. In diesem Artikel erfahren Sie, wie Sie das erreichen.

Einige neue Funktionen verbessern die Codegröße, indem neue Anweisungen für gängige Operationen hinzugefügt werden, einige bieten leistungsstarke Funktionen zur Leistungssteigerung und andere verbessern die Entwicklererfahrung und die Integration in den Rest des Webs.

Die vollständige Liste der Angebote und ihre jeweiligen Phasen finden Sie im offiziellen Repository. Den Implementierungsstatus in Suchmaschinen können Sie auf der offiziellen Seite zur Produkt-Roadmap verfolgen.

Um sicherzustellen, dass Nutzer aller Browser Ihre Anwendung verwenden können, müssen Sie überlegen, welche Funktionen Sie verwenden möchten. Teilen Sie sie dann je nach Browserunterstützung in Gruppen auf. Kompilieren Sie dann Ihre Codebasis für jede dieser Gruppen separat. Schließlich müssen Sie auf Browserseite die unterstützten Funktionen erkennen und das entsprechende JavaScript- und Wasm-Bundle laden.

Elemente auswählen und gruppieren

Sehen wir uns diese Schritte an, indem wir als Beispiel eine beliebige Funktionsgruppe auswählen. Angenommen, ich habe festgestellt, dass ich aus Gründen der Größe und Leistung SIMD, Threads und die Ausnahmebehandlung in meiner Bibliothek verwenden möchte. Unterstützte Browser:

Eine Tabelle mit der Browserunterstützung der ausgewählten Funktionen.
Sehen Sie sich diese Featuretabelle unter webassembly.org/roadmap an.

Sie können Browser in die folgenden Kohorten unterteilen, um für jeden Nutzer die bestmögliche Nutzererfahrung zu ermöglichen:

  • Chrome-basierte Browser: Threads, SIMD und die Ausnahmebehandlung werden unterstützt.
  • Firefox: Thread und SIMD werden unterstützt, die Ausnahmebehandlung nicht.
  • Safari: Threads werden unterstützt, SIMD und die Ausnahmebehandlung nicht.
  • Andere Browser: Es wird nur die grundlegende WebAssembly-Unterstützung vorausgesetzt.

Diese Aufschlüsselung erfolgt nach Funktionsunterstützung in der jeweils neuesten Version der einzelnen Browser. Moderne Browser sind zeitlos und werden automatisch aktualisiert. In den meisten Fällen müssen Sie sich also nur um die neueste Version kümmern. Solange Sie jedoch die WebAssembly-Baseline als Fallback-Kohorte angeben, können Sie auch Nutzern mit veralteten Browsern eine funktionierende Anwendung zur Verfügung stellen.

Für verschiedene Funktionsgruppen kompilieren

WebAssembly bietet keine integrierte Möglichkeit, unterstützte Funktionen zur Laufzeit zu erkennen. Daher müssen alle Anweisungen im Modul auf dem Ziel unterstützt werden. Daher müssen Sie den Quellcode für jede dieser verschiedenen Funktionsgruppen separat in Wasm kompilieren.

Jede Toolchain und jedes Build-System ist anders. Informationen dazu, wie Sie diese Funktionen anpassen, finden Sie in der Dokumentation Ihres eigenen Compilers. Zur Vereinfachung verwende ich im folgenden Beispiel eine C++-Bibliothek mit einer einzelnen Datei und zeige, wie sie mit Emscripten kompiliert wird.

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

# 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

Im C++-Code selbst können #ifdef __EMSCRIPTEN_PTHREADS__ und #ifdef __SSE2__ verwendet werden, um bedingt zwischen parallelen (Threads und SIMD) Implementierungen derselben Funktionen und den seriellen Implementierungen zur Kompilierungszeit zu wählen. Dies 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-Direktiven erforderlich, da sie unabhängig von der über die Kompilierungsoptionen ausgewählten zugrunde liegenden Implementierung auf dieselbe Weise aus C++ verwendet werden kann.

Das richtige Bundle wird geladen

Nachdem Sie Bundles für alle Feature-Kohorten erstellt haben, müssen Sie das richtige aus der Haupt-JavaScript-Anwendung laden. Dazu müssen Sie zuerst ermitteln, welche Funktionen im aktuellen Browser unterstützt werden. Dazu können Sie die Bibliothek wasm-feature-detect 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 Wörter

In diesem Beitrag habe ich gezeigt, wie Sie Pakete für verschiedene Funktionsgruppen auswählen, erstellen und wechseln.

Wenn die Anzahl der Funktionen zunimmt, kann die Anzahl der Funktionskohorten unhandlich werden. Um dieses Problem zu vermeiden, können Sie Funktionskohorten basierend auf Ihren realen Nutzerdaten auswählen, die weniger beliebten Browser überspringen und auf etwas weniger optimale Kohorten zurückgreifen. Solange Ihre Anwendung für alle Nutzer funktioniert, kann dieser Ansatz ein angemessenes Gleichgewicht zwischen progressiver Verbesserung und Laufzeitleistung bieten.

Zukünftig wird WebAssembly möglicherweise eine integrierte Möglichkeit erhalten, 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 Sie mit dem oben beschriebenen Ansatz erkennen und bedingt laden müssten. Bis dahin ist dies die einzige Möglichkeit, Code mit neuen WebAssembly-Funktionen in allen Browsern zu erstellen und zu laden.