Kompilieren von Rust zu WebAssembly
Wenn Sie etwas Rust-Code geschrieben haben, können Sie ihn in WebAssembly (Wasm) kompilieren. In diesem Tutorial erfahren Sie, wie Sie ein Rust-Projekt in WebAssembly kompilieren und in einer bestehenden Web-App verwenden.
Anwendungsfälle für Rust und WebAssembly
Es gibt zwei Hauptanwendungsfälle für Rust und WebAssembly:
- Erstellen einer gesamten Anwendung — eine komplette Web-App, die auf Rust basiert.
- Erstellen eines Teils einer Anwendung — Verwenden von Rust in einem bestehenden JavaScript-Frontend.
Derzeit konzentriert sich das Rust-Team auf den letzteren Fall, und das behandeln wir hier. Für den ersten Fall schauen Sie sich Projekte wie yew
und leptos an.
In diesem Tutorial erstellen wir ein Paket mit wasm-pack
, einem Tool zur Erstellung von JavaScript-Paketen in Rust. Dieses Paket enthält nur WebAssembly- und JavaScript-Code, sodass Benutzer Rust nicht installiert haben müssen. Sie werden möglicherweise nicht einmal bemerken, dass es in Rust geschrieben ist.
Rust-Umgebung einrichten
Wir beginnen mit der Einrichtung der notwendigen Umgebung.
Rust installieren
Installieren Sie Rust, indem Sie die Seite Install Rust besuchen und den Anweisungen folgen. Dadurch wird ein Tool namens "rustup" installiert, mit dem Sie mehrere Versionen von Rust verwalten können. Standardmäßig wird die neueste stabile Rust-Version installiert, die Sie für die allgemeine Rust-Entwicklung verwenden können. Rustup installiert rustc
, den Rust-Compiler, cargo
, Rusts Paketmanager, rust-std
, Rusts Standardbibliotheken, und einige hilfreiche Dokumentationen — rust-docs
.
Hinweis:
Achten Sie auf die Nachinstallationshinweise bezüglich des bin
-Verzeichnisses von cargo, das in Ihrem System-PATH
benötigt wird. Dies wird automatisch hinzugefügt, aber Sie müssen Ihr Terminal neu starten, damit es wirksam wird.
wasm-pack
Um das Paket zu erstellen, benötigen wir ein zusätzliches Tool, wasm-pack
. Dieses hilft dabei, den Code in WebAssembly zu kompilieren und die richtige Verpackung für die Verwendung im Browser zu erstellen. Um es herunterzuladen und zu installieren, geben Sie den folgenden Befehl in Ihr Terminal ein:
cargo install wasm-pack
Unser WebAssembly-Paket erstellen
Genug vorbereitet; lassen Sie uns ein neues Paket in Rust erstellen. Navigieren Sie zu dem Ort, an dem Sie Ihre Projekte speichern, und geben Sie Folgendes ein:
cargo new --lib hello-wasm
Dies erstellt eine neue Bibliothek in einem Unterverzeichnis namens hello-wasm
mit allem, was Sie benötigen, um loszulegen:
├── Cargo.toml └── src └── lib.rs
Cargo.toml
ist die Datei, die unser Build konfiguriert. Sie funktioniert ähnlich wie Gemfile
von Bundler oder package.json
von npm.
Cargo hat auch etwas Rust-Code für uns in src/lib.rs
generiert:
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
Schreiben wir etwas Rust
Wir werden den generierten Code in src/lib.rs
, der oben gezeigt wird, nicht verwenden; ersetzen Sie ihn durch Folgendes:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
pub fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet(name: &str) {
alert(&format!("Hello, {}!", name));
}
Unser Rust-Code hat drei Hauptteile; lassen Sie uns jeden der Reihe nach betrachten. Wir geben hier eine allgemeine Erklärung und überfliegen einige Details; um mehr über Rust zu erfahren, lesen Sie bitte das kostenlose Online-Buch The Rust Programming Language.
Verwenden von wasm-bindgen
zur Kommunikation zwischen Rust und JavaScript
Der erste Teil sieht so aus:
use wasm_bindgen::prelude::*;
Bibliotheken werden in Rust als "Crates" bezeichnet.
Verstanden? Cargo verschifft Crates.
Die erste Zeile enthält einen use
-Befehl, der Code aus einer Bibliothek in Ihren Code importiert. In diesem Fall importieren wir alles im Modul wasm_bindgen::prelude
. Wir verwenden diese Funktionen im nächsten Abschnitt.
Bevor wir zum nächsten Abschnitt übergehen, sollten wir mehr über wasm-bindgen
sprechen.
wasm-pack
verwendet wasm-bindgen
, ein weiteres Tool, um eine Brücke zwischen den Typen von JavaScript und Rust zu bieten. Es ermöglicht JavaScript, eine Rust-API mit einem String aufzurufen, oder eine Rust-Funktion, um eine JavaScript-Ausnahme abzufangen.
Wir nutzen die Funktionalität von wasm-bindgen
in unserem Paket. Tatsächlich ist das der nächste Abschnitt.
Aufrufen externer Funktionen in JavaScript aus Rust
Der nächste Teil sieht so aus:
#[wasm_bindgen]
extern "C" {
pub fn alert(s: &str);
}
Der Abschnitt innerhalb der #[ ]
wird als "Attribut" bezeichnet und modifiziert die nächste Anweisung irgendwie. In diesem Fall ist diese Anweisung ein extern
, wodurch Rust mitgeteilt wird, dass wir einige extern definierte Funktionen aufrufen möchten. Das Attribut sagt "wasm-bindgen weiß, wie man diese Funktionen findet".
Die dritte Zeile ist eine Funktionssignatur, die in Rust geschrieben ist. Sie besagt, dass die alert
-Funktion ein Argument nimmt, einen String mit dem Namen s
.
Wie Sie vielleicht vermuten, ist dies die alert
-Funktion, die von JavaScript bereitgestellt wird. Wir rufen diese Funktion im nächsten Abschnitt auf.
Immer wenn Sie JavaScript-Funktionen aufrufen möchten, können Sie sie zu dieser Datei hinzufügen, und wasm-bindgen
richtet alles für Sie ein. Noch nicht alles wird unterstützt, aber wir arbeiten daran. Bitte melden Sie Fehler, wenn etwas fehlt.
Erstellen von Rust-Funktionen, die JavaScript aufrufen kann
Der letzte Teil ist dieser:
#[wasm_bindgen]
pub fn greet(name: &str) {
alert(&format!("Hello, {}!", name));
}
Erneut sehen wir das #[wasm_bindgen]
-Attribut. In diesem Fall modifiziert es nicht einen extern
-Block, sondern einen fn
; das bedeutet, dass wir möchten, dass diese Rust-Funktion von JavaScript aufgerufen werden kann. Es ist das Gegenteil von extern
: dies sind nicht die Funktionen, die wir brauchen, sondern die Funktionen, die wir der Welt zur Verfügung stellen.
Diese Funktion heißt greet
und nimmt ein Argument, einen String (geschrieben &str
), name
. Sie ruft dann die alert
-Funktion auf, die wir im extern
-Block zuvor angefordert haben. Sie übergibt einen Aufruf des format!
-Makros, das es uns ermöglicht, Strings zu verketten.
Das format!
-Makro nimmt in diesem Fall zwei Argumente: einen Formatstring und eine Variable, die darin eingesetzt wird. Der Formatstring ist das "Hello, {}!"
-Element. Es enthält {}
s, wo Variablen interpoliert werden. Die Variable, die wir übergeben, ist name
, das Argument für die Funktion, sodass wenn wir greet("Steve")
aufrufen, wir "Hello, Steve!"
sehen sollten.
Dies wird an alert()
übergeben, sodass wenn wir diese Funktion aufrufen, ein Alert-Box mit "Hello, Steve!" darin erscheint.
Jetzt, da unsere Bibliothek geschrieben ist, lassen Sie uns sie kompilieren.
Kompilieren unseres Codes zu WebAssembly
Um unseren Code korrekt zu kompilieren, müssen wir ihn zuerst mit Cargo.toml
konfigurieren. Öffnen Sie diese Datei und ändern Sie ihren Inhalt, sodass er so aussieht:
[package]
name = "hello-wasm"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
description = "A sample project with wasm-pack"
license = "MIT/Apache-2.0"
repository = "https://github.com/yourgithubusername/hello-wasm"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
Füllen Sie Ihre eigene Repository-Informationen aus und verwenden Sie dieselben Angaben wie git
für das authors
-Feld.
Der große Teil zur Ergänzung ist der [package]
. Der [lib]
-Teil sagt Rust, dass es eine cdylib
-Version unseres Pakets erstellen soll; wir werden hier nicht darauf eingehen, was das bedeutet. Für mehr Informationen konsultieren Sie die Cargo und Rust Linkage Dokumentation.
Der letzte Abschnitt ist der [dependencies]
-Abschnitt. Hier sagen wir Cargo, welche Version von wasm-bindgen
wir einbeziehen möchten; in diesem Fall ist das jede 0.2.z
-Version (aber nicht 0.3.0
oder höher).
Das Paket erstellen
Jetzt, da wir die Einrichtung abgeschlossen haben, lassen Sie uns das Paket erstellen. Wir werden den generierten Code in einem nativen ES-Modul und Node.js verwenden. Zu diesem Zweck verwenden wir das --target
Argument in wasm-pack build
, um anzugeben, welche Art von WebAssembly und JavaScript generiert wird.
Führen Sie zunächst den folgenden Befehl in Ihrem hello-wasm
-Verzeichnis aus:
wasm-pack build --target web
Dies tut mehrere Dinge. Um mehr darüber zu erfahren, lesen Sie diesen Blogbeitrag auf Mozilla Hacks. Kurz gesagt, wasm-pack build
:
- Kompiliert Ihren Rust-Code zu WebAssembly.
- Führt
wasm-bindgen
auf diesem WebAssembly aus und generiert eine JavaScript-Datei, die dieses WebAssembly-File zu einem für den Browser verständlichen Modul umschließt. - Erstellt ein
pkg
-Verzeichnis und verschiebt diese JavaScript-Datei und Ihren WebAssembly-Code dorthin. - Liest Ihre
Cargo.toml
-Datei und erstellt ein entsprechendespackage.json
. - Kopiert Ihre
README.md
(falls vorhanden) in das Paket.
Das Endergebnis? Sie haben ein Paket im pkg
-Verzeichnis.
Das Paket im Web verwenden
Jetzt, da wir ein kompiliertes Wasm-Modul haben, lassen Sie es uns im Browser ausführen.
Beginnen wir damit, eine Datei namens index.html
im Stammverzeichnis des Projekts zu erstellen, sodass wir die folgende Projektstruktur erhalten:
├── Cargo.lock ├── Cargo.toml ├── index.html <-- new index.html file ├── pkg │ ├── hello_wasm.d.ts │ ├── hello_wasm.js │ ├── hello_wasm_bg.wasm │ ├── hello_wasm_bg.wasm.d.ts │ └── package.json ├── src │ └── lib.rs └── target ├── CACHEDIR.TAG ├── release └── wasm32-unknown-unknown
Fügen Sie den folgenden Inhalt in die index.html
-Datei ein:
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>hello-wasm example</title>
</head>
<body>
<script type="module">
import init, { greet } from "./pkg/hello_wasm.js";
init().then(() => {
greet("WebAssembly");
});
</script>
</body>
</html>
Das Skript in dieser Datei importiert den JavaScript-Leimcode, initialisiert das Wasm-Modul und ruft die greet
-Funktion auf, die wir in Rust geschrieben haben.
Servern Sie das Projekt-Stammverzeichnis mit einem lokalen Webserver (z.B. python3 -m http.server
). Wenn Sie nicht sicher sind, wie das geht, lesen Sie Einen einfachen lokalen HTTP-Server ausführen.
Hinweis:
Verwenden Sie einen aktuellen Webserver, der den MIME-Typ application/wasm
unterstützt. Ältere Webserver könnten ihn noch nicht unterstützen.
Laden Sie index.html
vom Webserver (wenn Sie das Python3-Beispiel verwendet haben: http://localhost:8000
). Ein Alert-Fenster erscheint auf dem Bildschirm, das Hello, WebAssembly!
enthält. Wir haben erfolgreich von JavaScript nach Rust und von Rust nach JavaScript aufgerufen.
Unser Paket für npm bereitstellen
Wir erstellen ein npm-Paket, daher müssen Sie Node.js und npm installiert haben.
Um Node.js und npm zu erhalten, besuchen Sie die Seite Get npm! und folgen Sie den Anweisungen. Dieses Tutorial zielt auf Node 20 ab. Um zwischen Node-Versionen zu wechseln, können Sie nvm verwenden.
Um das WebAssembly-Modul mit npm zu verwenden, müssen wir einige Änderungen vornehmen.
Lassen Sie uns damit beginnen, unser Rust mit der bundler
-Option als Ziel neu zu kompilieren:
wasm-pack build --target bundler
Wir haben jetzt ein npm-Paket, das in Rust geschrieben, aber zu WebAssembly kompiliert wurde. Es ist bereit zur Verwendung von JavaScript und erfordert nicht, dass der Benutzer Rust installiert hat; der enthaltene Code ist der WebAssembly-Code, nicht der Rust-Quellcode.
Das npm-Paket im Web verwenden
Lassen Sie uns eine Website erstellen, die unser neues npm-Paket nutzt. Viele Leute verwenden npm-Pakete über verschiedene Bundler-Tools, und wir werden in diesem Tutorial eines davon, webpack
, verwenden. Es ist nur ein wenig komplex und zeigt einen realistischen Anwendungsfall.
Lassen Sie uns ein neues Verzeichnis im hello-wasm
-Verzeichnis namens site
erstellen, um es auszuprobieren.
Wir haben das Paket noch nicht im npm-Registry veröffentlicht, daher können wir es aus einer lokalen Version mit npm i /path/to/package
installieren.
Sie können npm link
verwenden, aber die Installation aus einem lokalen Pfad ist für diese Demo praktisch:
mkdir site && cd site
npm i ../pkg
Installieren Sie die webpack
Dev-Abhängigkeiten:
npm i -D webpack@5 webpack-cli@5 webpack-dev-server@5 copy-webpack-plugin@12
Als Nächstes müssen wir webpack konfigurieren. Erstellen Sie webpack.config.js
und fügen Sie Folgendes ein:
const CopyPlugin = require("copy-webpack-plugin");
const path = require("path");
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "index.js",
},
mode: "development",
experiments: {
asyncWebAssembly: true,
},
plugins: [
new CopyPlugin({
patterns: [{ from: "index.html" }],
}),
],
};
In Ihrer package.json
können Sie build
- und serve
-Skripte hinzufügen, die webpack mit der gerade erstellten Konfigurationsdatei ausführen:
{
"scripts": {
"build": "webpack --config webpack.config.js",
"serve": "webpack serve --config webpack.config.js --open"
},
"dependencies": {
"hello-wasm": "file:../pkg"
},
"devDependencies": {
"copy-webpack-plugin": "^12.0.2",
"webpack": "^5.97.1",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.1.0"
}
}
Erstellen Sie als nächstes eine Datei namens index.js
und geben Sie ihr diesen Inhalt:
import * as wasm from "hello-wasm";
wasm.greet("WebAssembly with npm");
Dies importiert das Modul aus dem node_modules
-Ordner und ruft die greet
-Funktion auf, wobei "WebAssembly with npm"
als String übergeben wird. Beachten Sie, dass hier nichts Besonderes ist, dennoch rufen wir in Rust-Code auf. Aus Sicht des JavaScript-Codes ist dies einfach nur ein normales Modul.
Schließlich müssen wir eine HTML-Datei hinzufügen, um das JavaScript zu laden. Erstellen Sie eine index.html
-Datei und fügen Sie Folgendes hinzu:
<!doctype html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>hello-wasm example</title>
</head>
<body>
<script src="./index.js"></script>
</body>
</html>
Das hello-wasm/site
-Verzeichnis sollte folgendermaßen aussehen:
├── node_modules ├── index.html ├── index.js ├── package-lock.json ├── package.json └── webpack.config.js
Wir sind mit dem Erstellen der Dateien fertig. Lassen Sie uns das ausprobieren:
npm run serve
Dies startet einen Webserver und öffnet http://localhost:8080
. Sie sollten ein Alert-Fenster auf dem Bildschirm sehen, das den Text Hello, WebAssembly with npm!
enthält. Wir haben das Rust-Modul erfolgreich mit npm verwendet!
Wenn Sie Ihr WebAssembly außerhalb der lokalen Entwicklung nutzen möchten, können Sie das Paket mit den Befehlen pack
und publish
im hello-wasm
-Verzeichnis veröffentlichen:
wasm-pack pack
npm notice
npm notice 📦 hello-wasm@0.1.0
npm notice Tarball Contents
npm notice 2.9kB hello_wasm_bg.js
npm notice 16.7kB hello_wasm_bg.wasm
npm notice 85B hello_wasm.d.ts
npm notice 182B hello_wasm.js
npm notice 549B package.json
...
hello-wasm-0.1.0.tgz
[INFO]: 🎒 packed up your package!
Um auf npm zu veröffentlichen, benötigen Sie ein npm-Konto und müssen Ihren Rechner mit npm adduser
autorisieren.
Wenn Sie bereit sind, können Sie mit wasm-pack
veröffentlichen, das npm publish
im Hintergrund aufruft:
wasm-pack publish
Fazit
Dies ist das Ende unseres Tutorials; wir hoffen, es war nützlich für Sie.
In diesem Bereich gibt es viele spannende Arbeiten; wenn Sie dazu beitragen möchten, es noch besser zu machen, schauen Sie bei der Rust und WebAssembly Arbeitsgruppe vorbei.