WebAssembly für Anfänger Teil 4: WebAssembly und JavaScript Companionship

In dieser vierten Episode unserer WebAssembly-Serie für Einsteiger werfen wir einen detaillierten Blick auf WebAssembly in Kombination mit JavaScript.

Wir werden untersuchen, wie Sie WebAssembly in Ihre JavaScript-Projekte integrieren können. Zudem werden wir die WebAssembly-JavaScript-API genauer unter die Lupe nehmen.

WebAssembly ist ein offener Standard, der ein binäres Format bereitstellt, welches Entwicklern erlaubt, Anwendungen mit annähernder nativer Geschwindigkeit in Webbrowsern auszuführen. Falls Sie bisher noch keine Gelegenheit hatten, sich damit zu beschäftigen, empfehlen wir Ihnen, die vorherigen Teile unserer Anleitung zu lesen.

Beginnen wir nun.

WebAssembly in Verbindung mit JavaScript verwenden

In unserem WebAssembly-Tutorial Teil 1 haben wir die Funktionsweise von WASM erläutert. Um optimalen Code für Ihre Webanwendung zu entwickeln, ist die Nutzung der WASM-APIs und -Funktionen in JavaScript unerlässlich. Wir haben ebenfalls die Möglichkeiten besprochen, wie JavaScript-Frameworks WASM einsetzen können, um Hochleistungsanwendungen zu realisieren.

Es ist derzeit nicht möglich, WASM-Module direkt wie ES6-Module mit <script type=“module“> zu laden. Hier kommt JavaScript ins Spiel, da es beim Laden und Kompilieren von WASM im Browser hilft. Die dazu erforderlichen Schritte sind:

  • Laden der .wasm-Bytes in einen ArrayBuffer oder ein typisiertes Array.
  • Kompilieren der Bytes mithilfe von WebAssembly.Module.
  • Instanziieren von WebAssembly.Module mit den nötigen Importen, um aufrufbare Exporte zu erhalten.

Sie benötigen also ein vorkompiliertes WASM-Modul. Hierbei haben Sie eine vielfältige Auswahl: Rust, C/C++, AssemblyScript und sogar TinyGo (Go) können für Ihren Code genutzt werden, um ihn anschließend in ein .wasm-Modul zu überführen.

WebAssembly ist im Grunde ein Kompilationsziel für diverse Programmiersprachen. Dies bedeutet, dass Sie Ihren Code in Ihrer bevorzugten Sprache schreiben und dann den generierten Binärcode in Ihrer Anwendung verwenden können. Bei serverseitigem Einsatz ist WASI als Schnittstelle zu den Betriebssystemen notwendig.

Da WebAssembly linearen Speicher in Form eines erweiterbaren Arrays nutzt, können sowohl JavaScript als auch WASM synchron darauf zugreifen, was die Entwicklung performanter und funktionsreicher Anwendungen ermöglicht.

WebAssembly- und JavaScript-Beispiele

Lassen Sie uns an praktischen Beispielen demonstrieren, wie WASM in Verbindung mit JavaScript genutzt werden kann.

Wie bereits erwähnt, benötigen Sie ein vorkompiliertes WASM-Modul. Für unser Beispiel verwenden wir Emscripten (C/C++). Aufgrund des leistungsstarken binären Formats von WASM können wir den erzeugten Code parallel zu JavaScript oder anderen Sprachen ausführen.

Einrichten der Werkzeuge

Da wir Emscripten verwenden, ist das emsdk-Tool erforderlich, welches Ihnen ermöglicht, C/C++-Code in .wasm-Code zu übersetzen.

Führen Sie einfach den folgenden Befehl in Ihrem Terminal aus. Wenn Sie Git noch nicht installiert haben, folgen Sie bitte unserer Anleitung Open Source 101: Version Control System and Git Guide.

git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
#Output

[email protected]:~/Projects/WASM2$ git clone https://github.com/emscripten-core/emsdk.git
Cloning into 'emsdk'...
remote: Enumerating objects: 3566, done.
remote: Counting objects: 100% (62/62), done.
remote: Compressing objects: 100% (49/49), done.
remote: Total 3566 (delta 31), reused 38 (delta 13), pack-reused 3504
Receiving objects: 100% (3566/3566), 2.09 MiB | 2.24 MiB/s, done.
Resolving deltas: 100% (2334/2334), done.
[email protected]:~/Projects/WASM2$ cd emsdk
[email protected]:~/Projects/WASM2/emsdk$

Nachdem wir uns im emsdk-Verzeichnis befinden, benötigen wir einen weiteren Befehl, um die neueste, einsatzbereite Version von Emscripten zu erhalten.

Die folgenden Befehle sind auszuführen:

./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
#Output

[email protected]:~/Projects/WASM2/emsdk$ ./emsdk install latest
Resolving SDK alias 'latest' to '3.1.31'
Resolving SDK version '3.1.31' to 'sdk-releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit'
Installing SDK 'sdk-releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit'..
Installing tool 'node-14.18.2-64bit'..
Downloading: /home/nitt/Projects/WASM2/emsdk/zips/node-v14.18.2-linux-x64.tar.xz from https://storage.googleapis.com/webassembly/emscripten-releases-builds/deps/node-v14.18.2-linux-x64.tar.xz, 21848416 Bytes
Unpacking '/home/nitt/Projects/WASM2/emsdk/zips/node-v14.18.2-linux-x64.tar.xz' to '/home/nitt/Projects/WASM2/emsdk/node/14.18.2_64bit'
Done installing tool 'node-14.18.2-64bit'.
Installing tool 'releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit'..
Downloading: /home/nitt/Projects/WASM2/emsdk/zips/1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-wasm-binaries.tbz2 from https://storage.googleapis.com/webassembly/emscripten-releases-builds/linux/1eec24930cb2f56f6d9cd10ffcb031e27ea4157a/wasm-binaries.tbz2, 349224945 Bytes
Unpacking '/home/nitt/Projects/WASM2/emsdk/zips/1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-wasm-binaries.tbz2' to '/home/nitt/Projects/WASM2/emsdk/upstream'
Done installing tool 'releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit'.
Done installing SDK 'sdk-releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit'.
[email protected]:~/Projects/WASM2/emsdk$ ./emsdk activate latest
Resolving SDK alias 'latest' to '3.1.31'
Resolving SDK version '3.1.31' to 'sdk-releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit'
Setting the following tools as active:
  node-14.18.2-64bit
  releases-1eec24930cb2f56f6d9cd10ffcb031e27ea4157a-64bit

Next steps:
- To conveniently access emsdk tools from the command line,
 consider adding the following directories to your PATH:
  /home/nitt/Projects/WASM2/emsdk
  /home/nitt/Projects/WASM2/emsdk/node/14.18.2_64bit/bin
  /home/nitt/Projects/WASM2/emsdk/upstream/emscripten
- This can be done for the current shell by running:
  source "/home/nitt/Projects/WASM2/emsdk/emsdk_env.sh"
- Configure emsdk in your shell startup scripts by running:
  echo 'source "/home/nitt/Projects/WASM2/emsdk/emsdk_env.sh"' >> $HOME/.bash_profile

Der letzte Befehl, „source ./emsdk_env.sh“, stellt sicher, dass der Pfad des emcc Emscripten-Compilers gesetzt ist, damit Sie ihn zum Kompilieren Ihres Codes nutzen können.

#Output

[email protected]:~/Projects/WASM2/emsdk$ source ./emsdk_env.sh
Setting up EMSDK environment (suppress these messages with EMSDK_QUIET=1)
Adding directories to PATH:
PATH += /home/nitt/Projects/WASM2/emsdk
PATH += /home/nitt/Projects/WASM2/emsdk/upstream/emscripten
PATH += /home/nitt/Projects/WASM2/emsdk/node/14.18.2_64bit/bin

Setting environment variables:
PATH = /home/nitt/Projects/WASM2/emsdk:/home/nitt/Projects/WASM2/emsdk/upstream/emscripten:/home/nitt/Projects/WASM2/emsdk/node/14.18.2_64bit/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
EMSDK = /home/nitt/Projects/WASM2/emsdk
EMSDK_NODE = /home/nitt/Projects/WASM2/emsdk/node/14.18.2_64bit/bin/node
[email protected]:~/Projects/WASM2/emsdk$ 

Nun erzeugen wir den WASM-Code mit folgendem Befehl.

emcc hello-wdzwdz.c -o hello-wdzwdz.js
#Output

[email protected]:~/Projects/WASM2$ emcc hello-wdzwdz.c -o hello-wdzwdz.js
shared:INFO: (Emscripten: Running sanity checks)
cache:INFO: generating system asset: symbol_lists/1c683af19e290d0b5ca7a8747d74a76f63dcb362.txt... (this will be cached in "/home/nitt/Projects/WASM2/emsdk/upstream/emscripten/cache/symbol_lists/1c683af19e290d0b5ca7a8747d74a76f63dcb362.txt" for subsequent builds)
cache:INFO: - ok
[email protected]:~/Projects/WASM2$ dir
emsdk hello-wdzwdz.c hello-wdzwdz.js hello-wdzwdz.wasm
[email protected]:~/Projects/WASM2$ 

Wie Sie sehen, erhalten Sie die Ausgabedateien „hello-wdzwdz.js“ und „hello-wdzwdz.wasm“. Sie können die Dateien überprüfen, indem Sie im Projektverzeichnis den Befehl dir ausführen.

Diese beiden Dateien sind essenziell. Die hello-wdzwdz.wasm-Datei beinhaltet den kompilierten Code, während die hello-wdzwdz.js-Datei JavaScript-Code enthält, der für die Ausführung erforderlich ist. Da Emscripten sowohl Web- als auch node.js-Ausführungen unterstützt, können wir es mit Node testen.

node hello-wdzwdz.js
#Output

[email protected]:~/Projects/WASM2$ node hello-wdzwdz.js
Hello, wdzwdz! 
[email protected]:~/Projects/WASM2$ 

Wenn Sie es im Browser sehen möchten, können Sie die HTML-Datei ebenfalls mit Emscripten erstellen. Dazu führen Sie folgenden Befehl aus.

emcc hello-wdzwdz.c -o hello-wdzwdz.html
#Output

[email protected]:~/Projects/WASM2$ emcc hello-wdzwdz.c -o hello-wdzwdz.html
[email protected]:~/Projects/WASM2$ 

Zum Ausführen der HTML-Datei können Sie den Python 3 HTTPServer nutzen, indem Sie den folgenden Befehl ausführen.

python3 -m http.server 8000

Gehen Sie nun zu https://localhost:8000/hello-wdzwdz.html, um die Ausgabe zu betrachten.

Hinweis: Auf den meisten Systemen ist Python vorinstalliert. Wenn dies nicht der Fall ist, können Sie es einfach installieren, bevor Sie den Python3-Server starten.

Nutzung der JavaScript-API für WASM

In diesem Abschnitt werden wir die JavaScript-WASM-API detaillierter betrachten und lernen, wie man WASM-Code lädt und ausführt. Zunächst werfen wir einen Blick auf den folgenden Code.

fetch('hello-wdzwdz.wasm').then( response =>
 response.arrayBuffer())
 .then (bytes =>
  WebAssembly.instantiate(bytes))
  .then(result=>
   alert(result.instance.exports.answer()))

Der oben gezeigte Code nutzt die folgenden JavaScript-APIs:

  • fetch()-Browser-API
  • WebAssembly.instantiate

Neben diesen gibt es weitere wichtige APIs, wie zum Beispiel:

  • WebAssembly.compile
  • WebAssembly.Instance
  • WebAssembly.instantiate
  • WebAssembly.instantiateStreaming

fetch()-Browser-API

Die fetch()-API dient zum Laden der .wasm-Netzwerkressource. Wenn Sie versuchen, diese lokal zu laden, müssen Sie die ursprungsübergreifende Ressourcenfreigabe deaktivieren, um die Netzwerkressource zu laden. Alternativ können Sie einen Node-Server verwenden, der dies automatisch für Sie erledigt. Führen Sie den folgenden Befehl aus, um einen Node-Server zu installieren und zu starten.

sudo apt install npm

Als Nächstes führen Sie den folgenden Befehl aus, um den Server zu starten:

npx http-server -o
#Output

http-server version: 14.1.1

http-server settings: 
CORS: disabled
Cache: 3600 seconds
Connection Timeout: 120 seconds
Directory Listings: visible
AutoIndex: visible
Serve GZIP Files: false
Serve Brotli Files: false
Default File Extension: none

Available on:
 https://127.0.0.1:8080
 https://192.168.0.107:8080
Hit CTRL-C to stop the server
Open: https://127.0.0.1:8080

(2023-01-28T19:22:21.137Z) "GET /" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.70"
(node:37919) (DEP0066) DeprecationWarning: OutgoingMessage.prototype._headers is deprecated
(Use `node --trace-deprecation ...` to show where the warning was created)
(2023-01-28T19:22:21.369Z) "GET /favicon.ico" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.70"
(2023-01-28T19:22:21.369Z) "GET /favicon.ico" Error (404): "Not found"

Dieser Befehl öffnet den Webbrowser, in dem Sie alle Ihre Projektdateien sehen können.

Öffnen Sie nun die Datei hello-wdzwdz.html und starten Sie die Web Developer Tools. Rufen Sie dort die Konsole auf und geben Sie folgendes ein:

fetch("hello-wdzwdz.wasm");

Sie erhalten das folgende Promise zurück:

#Output

Promise {<pending>}
((Prototype)): Promise
((PromiseState)): "fulfilled"
((PromiseResult)): Response
body: (...)
bodyUsed: false
headers: Headers {}
ok: true
redirected: false
status: 200
statusText: "OK"
type: "basic"
url: "https://127.0.0.1:8080/hello-wdzwdz.wasm"
((Prototype)): Response

Sie können auch das folgende Skript verwenden und es über HTML ausführen.

Um Ihre WASM-Module serverseitig in Node.js auszuführen, verwenden Sie folgenden Code:

const fs = require('fs');
const run = async() => {
 const buffer = fs.readFileSync("./hello-wdzwdz.wasm");
 const result = await WebAssembly.instantiate(buffer);
 console.log(result.instance.exports.answer());
};

run();

Wir empfehlen Ihnen, die WebAssembly-JavaScript-API-Dokumentation zu lesen, um weitere Details zu erfahren.

JavaScript vs. WASM

Um die Interaktion zwischen WASM und JavaScript zu verstehen, ist es hilfreich, sie miteinander zu vergleichen. WASM ist im Kern schneller und verwendet ein binäres Format für die Zielkompilierung, während JavaScript eine interpretierte Hochsprache ist. Der Binärcode von WASM ist komplexer zu erlernen, doch es existieren Wege, effizient mit WASM zu arbeiten.

Die Hauptunterschiede zwischen WASM und JavaScript sind:

  • WASM ist eine kompilierte Sprache, während JS eine interpretierte Sprache ist. Ein Browser muss JavaScript zur Laufzeit herunterladen und parsen, während WASM-Code durch seinen vorkompilierten Code direkt zur Ausführung bereitsteht.
  • WebAssembly ist eine Low-Level-Sprache im Vergleich zu JavaScript, einer High-Level-Sprache. JavaScript ist auf einer höheren Ebene und daher einfacher in der Handhabung. WASM ist jedoch durch seinen Low-Level-Ansatz um einiges schneller als JavaScript.
  • Zuletzt profitiert JavaScript von seiner großen Community. Wenn Sie also eine bessere Entwicklungserfahrung suchen, ist JS eine naheliegende Wahl. WebAssembly ist hingegen relativ neu und es existieren daher weniger Ressourcen.

Als Entwickler müssen Sie sich keine Sorgen um die Wahl machen, da JS und WASM zusammenarbeiten und nicht konkurrieren.

Wenn Sie also eine Hochleistungs-Anwendung erstellen möchten, können Sie die leistungskritischen Teile der Anwendung mit WebAssembly codieren. Die JavaScript-API ermöglicht es Ihnen, WASM-Module direkt in Ihren JavaScript-Code einzubinden und zu verwenden.

Abschließende Gedanken

WebAssembly ist eine ausgezeichnete Ergänzung zu JavaScript. Es eröffnet Entwicklern die Möglichkeit, leistungsstarke Anwendungen im Web und auch außerhalb zu entwickeln, ohne dabei JavaScript ersetzen zu wollen.

Wird WASM jedoch zu einem umfassenden Paket und ersetzt JavaScript? In Anbetracht der Ziele von WebAssembly ist dies eher unwahrscheinlich. Die Vorstellung, dass WebAssembly eines Tages JavaScript ersetzen könnte, ist dennoch nicht ganz ausgeschlossen.

Als nächstes empfehlen wir Ihnen, sich unsere Zusammenstellung der besten JavaScript (JS)-UI-Bibliotheken zur Entwicklung moderner Anwendungen anzuschauen.