Bei dem Thema WebAssembly handelt es sich um Code, welcher speziell für den Browser kompiliert wurde. Dieser liegt in einem sehr kompakten Binary-Format vor. Dabei ist WebAssembly eine sehr low-level assembler-artige Sprache. Wasm ist mittlerweile auch von der W3C auch als ein Webstandard festgelegt worden. Von vornherein sollte man auch direkt erwähnen, dass es sich bei Wasm um eine Ergänzung zu JavaScript handelt und nicht als Ersatz dienen soll. Da es als Ergänzung zählt, ist es möglich, innerhalb von JavaScript-Applikationen WebAssembly Module zu laden und deren Funktionen zu nutzen. Ebenfalls ist dies auch andersrum möglich. Somit nutzt man hier die Flexibilität von JavaScript und die Performance, die Wasm einem bietet. Aus diesem Grund ermöglicht diese Kombination Applikationen browserfähig zu bekommen, bei denen es zuvor nicht möglich war.
Um Wasm zu nutzen, gibt es verschiedene Möglichkeiten, wie zum Beispiel wasm-Code select schreiben oder vorhandenen Code in wasm-Code zu kompilieren. Dazu jedoch später mehr.
Früher hat die Rechenleistung von JavaScript alleine gereicht. Heutzutage reicht dies durch Applikationen im Bereich von 3D-Games, Virtual oder Augmented Reality, Image/Video Editing nicht mehr. Dieses Problem soll mit der Verwendung von Wasm behoben werden. Zu den Vorteilen, die beide Welten bieten gehören vor alle:
bei JavaScript:
sehr flexible, um Webanwendungen zu schreiben
benötigt kein vorheriges Compiling
grosses Ecosystem mit sehr vielen Frameworks, Libraries und anderen Tools
bei WebAssembly:
sehr kompaktes binary-Format
ist eine sehr low-level assembler-artige Sprache
Dabei wird von der Browser VM nun zwei Typen von Code geladen — JavaScript und WebAssembly. Die WebAssembly JavaScript API macht es erst möglich, Funktionen aus dem wasm-Code aufzurufen. Dies ist möglich, in dem die API den wasm-Code quasi umschliesst. Auch das Aufrufen von JavaScript-Funktionen innerhalb von wasm-Code ist kein Problem.
Ziele
WebAssembly definiert dabei vor allem diese vier Ziele:
Be fast, efficient, and portable
- wasm-Code kann nahezu in native Geschwindigkeit ausgeführt werden unabhängig von der Platform
Be readable and debuggable
- obwohl es eine low-level Assembler-artige Sprache ist, gibt es ein human-readable Text Format. Dadurch besteht die Möglichkeit Code selbst zu schreiben, ihn zu lesen und auch zu debuggen
Keep secure
- wasm wird in einer sicheren Sandbox Environment ausgeführt. Dabei übernimmt es dieselben same-origin und Permission-Policies wie der Browser
Don’t break the web
- ist dafür ausgelegt, dass wasm abwärtskompatibel ist und somit ohne Probleme mit anderen Web Technologien zusammen arbeiten kann.
Key Konzepte
Wenn man WebAssembly in seiner Applikation nutzen möchte, gibt es zunächst einmal die folgenden Key-Konzepte, die man wissen sollte, um den Aufbau zu verstehen.
Module
Ein Module stellt ein WebAssembly Binary dar, welches durch den Browser zu ausführbaren Maschinencode compiled wurde. Dabei ist ein Module, genauso wie ein Blob, stateless und kann zwischen Windows und Workers geteilt werden.
Memory
Bei dem Speicher handelt es sich um ein resizable ArrayBuffer.
Table
Eine Tabelle ist ein resizable Typed Array, welches Referenzen (bsp. Funktionen) enthält. Dabei können diese aus Sicherheitsgründen nur als Bytes gespeichert werden.
Instanz
Sobald es eine Kombination aus einem Module, States des Speichers, Tabellen und importierten Variablen gibt, spricht man von einer Instanz.
Das Einbinden
Leider gibt es noch nicht die Möglichkeit, reinen wasm-Code einfach über ein import
-Statement oder über das Html-Tag <script type='module'>
zu machen. Die gängigsten zwei Möglichkeiten sind einmal über Fetch API oder über einen XMLHttpRequest.
Fetch
Der schnellste und auch effektivste Weg ist über die Funktion WebAssembly.instantiateStreaming()
. Dabei nimmt diese als ersten Parameter die wasm-Datei entgegen. In unserem Beispiel wird diese Datei über den Fetch-Befehl geladen. Optional kann man einen weiteren Parameter angeben, welche Werte für die neue Instanz bereitstellt.
|
|
Als Response bekommt man ein Promise zurück, welches als ResultObject aufgelöst wird und zwei Felder enthält. Das erste ist das module
, welches das kompilierte WebAssembly Module darstellt. Dieses kann wiederum noch einmal instanziiert werden oder über die postMessage()
geteilt werden. Das zweite Feld ist instance
, welches alle exportieren WebAssembly Funktionen beinhaltet.
XMLHttpRequest
Dies ist eine etwas ältere Variante, wird jedoch immer noch supportet. Dabei muss zuerst ein XMLHttpRequest()
erstellt werden. In der open()
-Funktion muss nun die Request-Methode bestimmt werden, sowie der Name des wasm-Codes. Anschliessend ist es wichtig, den responseType auf arrrayBuffer
festzulegen. Nun kann der Request gesendet werden. In einem weiteren Schritt kann dann gleich wie oben auf das ResultObjekt zugegriffen werden.
|
|
Konvertieren
Wie oben erwähnt gibt es zusätzlich zu dem Binary-Format (.wasm
) noch ein human-readable-Format (.wat
). Falls Sie nur eines der beiden Formate haben können Sie dieses mit dem Tool wabt in das andere Format konvertieren.
Dafür installieren Sie zunächst einmal wabt:
|
|
Nun stehen ihnen zwei tools zur Verfügung um Ihren Code zu konvertieren: wasm2wat
und wat2wasm
.
|
|
Das Verwenden
Nun zeigen wir Ihnen, wie Sie wasm-Code selbst erstellen können. Da wir uns letzteWoche mit dem Thema Rust beschäftigt haben, möchten wir Ihnen nun auch zeigen, wie Sie eine Rust Applikation in wasm-Code konvertieren können, damit Sie diesen in ihrem Browser verwenden können.
Falls Sie Rust noch nicht installiert haben, können Sie dies hier tun. Als Nächstes müssen Sie über den Packagemanager cargo
das wasm-pack
installieren. Mithilfe dieses Package ist es möglich, Ihren Code zu wasm-Code zu kompilieren. Ebenso stellt es das korrekte Packaging bereit, um es innerhalb des Browsers zu verwenden.
|
|
Falls Sie noch keine Applikationen in Rust haben, die Sie kompilieren wollen, müssen Sie sich zunächst einmal ein Rust-Projekt erstellen.
|
|
Anschliessend sollten Sie folgende Ordner Struktur haben.
|
|
Um die Funktionalität von WebAssembly aufzuzeigen, werden wir zwei unterschiedliche Funktionen hinzufügen. Einmal das Aufrufen von JS-Code innerhalb von Rust-Code und auch andersrum. Dazu müssen Sie in unserem Fall das lib.rs
File wie folgt anpassen:
|
|
Auf Zeile 1 wird das wasm_bindgen::prelude
Modul importiert. Dieses wird dafür zuständig sein, um zwischen JS und Rust zu kommunizieren.
Bei dem Code von Zeile 3 bis Zeile 6 handelt es sich um den Aufruf der JavaScript-Funktion alert()
. Der Code von Zeile 8 bis Zeile 10 zeigt wiederum die Möglichkeit aus JavaScript diese Rust Funktion aufzurufen.
Um nun den Code auch zu WemAssembly zu kompilieren müssen Sie ihr Cargo.toml
noch nach folgendem Muster anpassen:
|
|
Nun sind Sie bereit, dafür das Package zu bilden. Dieser Vorgang kann ein bisschen dauern.
|
|
Bei diesem Vorgang wird Ihr Rust-Code zu WebAssembly kompiliert. Dabei wird ein JavaScript File um das WebAssembly File als Module erstellt, damit der Browser dieses verwenden kann. Des Weiteren wird ein pkg-Ordner erstellt, in den das JavaScript File und das WebAssembly-File verschoben werden. Ebenso wird aus dem Cargo.toml
einpassendes package.json
erstellt. Sollten Sie bereits ein README.md
haben wird auch dieses in diesen Ordner kopiert.
|
|
Der Ordner target
ist jedoch zu gross um diesen hier abzubilden.
Um nun Ihr Projekt auch im Browser verwenden zu können, müssen Sie ein index.html
File erstellen, welches Ihres Script einbindet. Dafür legen Sie in Ihrem root
-Verzeichnis folgendes HTML-File an:
|
|
Nun können Sie einen lokalen Webserver, beispielsweise mit python3 -m http.server
starten und Ihr index.html
über Ihren Browser aufrufen. Dadurch sollten Sie nun ein Hinweisfenster sehen mit der Nachricht “Hello, WebAssembly!”.
Nutzen Sie NPM
Eine weitere Möglichkeit besteht darin, eine Rust-Applikation mittels NPM zu verwenden. Die Voraussetzung hierfür sind einfach, dass Sie Node.js und Npm installiert haben. Hier für wechseln Sie zunächst ihn Ihr root-Verzeichnis ihrer Applikation und rufen folgenden Befehl auf:
|
|
Sie erhalten folgende Ausgabe:
|
|
Um nun dieses Package auch für andere JavaScript Package verfügbar zu machen, müssen Sie einmal in den pkg
-Ordner wechseln und ein npm link
ausführen.
|
|
Nun haben Sie ein npm Package, welches in Rust geschrieben wurde aber zu WebAssembly kompiliert wurde.
Nun müssen Sie wieder in das root-Verzeichnis wechseln, einen neuen Ordner anlegen und in diesen wieder wechseln. Durch das Ausführen von npm link wird ein neuer Ordner mit dem Namen node_modules
erstellt.
|
|
Zusätzlich müssen Sie noch ein package.json
und ein webpack.config.js
erstellen.
In dem ersten File werden verschiedene Dependencies bestimmt:
|
|
In dem Webpack Javascript File müssen Sie folgendes hinzufügen
|
|
Nun benötigen Sie noch ein JS-File, welches die Funktion aus ihrem WebAssembly File aufruft.
|
|
Da es nun auch notwendig ist, eine HTML-Seite zuhaben, die Ihre zuvor erstelltes JavaScript einbindet, benötigen Sie ebenfalls ein index.html File.
|
|
Nun haben Sie fast alle Schritte erledigt, um Ihre Rust-Applikation in Ihrem Browser aufzurufen. Nur noch alle Dependencies installieren und den Server starten.
|
|
Jetzt können Sie Ihre Seite im Browser aufrufen und bekommen Ihre Nachricht in einer Alter-Box angezeigt.
Nun haben wir Ihnen einmal die Grundlagen gezeigt wie Sie WebAssembly nutzen können. Was jedoch mit der Kombination aus JavaScript und WebAssembly möglich ist und wo es verwendet werden kann wird in nähere Zukunft ein sehr spannendes Thema werden, welches wir bei b-nova auf jeden Fall verfolgen werden.