你好,世界!

本節將說明如何建立並執行您第一個 Rust 和 WebAssembly 程式:顯示「你好,世界!」的網頁。

在開始前,請務必按照設定說明進行操作。

複製專案範本

專案範本預先設定好合理的預設值,讓您可以快速為網頁建立、整合和封裝您的程式碼。

使用此指令複製專案範本

cargo generate --git https://github.com/rustwasm/wasm-pack-template

這將會提示您輸入新專案的名稱。我們將使用「wasm-game-of-life」

wasm-game-of-life

內含什麼內容

進入新的 wasm-game-of-life 專案

cd wasm-game-of-life

了解它的內容

wasm-game-of-life/
├── Cargo.toml
├── LICENSE_APACHE
├── LICENSE_MIT
├── README.md
└── src
    ├── lib.rs
    └── utils.rs

讓我們詳細看看其中幾個檔案。

wasm-game-of-life/Cargo.toml

Cargo.toml 檔案為 cargo(Rust 的套件管理員和建置工具)指定了依賴項和資料。此檔案預先設定了一項 wasm-bindgen 依賴項、幾個我們稍後將深入探討的選用依賴項,以及為產生 .wasm 函式庫而正確初始化的 crate-type

wasm-game-of-life/src/lib.rs

src/lib.rs 檔案是我們編譯成 WebAssembly 的 Rust Crate 的根目錄。它使用 wasm-bindgen 與 JavaScript 介接。它匯入 window.alert JavaScript 函式,並匯出 greet Rust 函式,用於提醒問候語訊息。


# #![allow(unused_variables)]
#fn main() {
mod utils;

use wasm_bindgen::prelude::*;

// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

#[wasm_bindgen]
extern {
    fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet() {
    alert("Hello, wasm-game-of-life!");
}
#}

wasm-game-of-life/src/utils.rs

模組 src/utils.rs 提供常見的實用程式,讓處理編譯成 WebAssembly 的 Rust 變得更容易。我們稍後會在教學中更詳細地查看其中一些實用程式,例如在我們檢視 除錯我們的 wasm 程式碼 時,但我們現在可以忽略這個檔案。

建置專案

我們使用 wasm-pack 編排下列建置步驟

  • 確保我們已經透過 rustup 安裝 Rust 1.30 或更新版本和 wasm32-unknown-unknown 目標,
  • 使用 cargo 將我們的 Rust 原始碼編譯成 WebAssembly .wasm 執行檔,
  • 使用 wasm-bindgen 為使用我們 Rust 生成的 WebAssembly 產生 JavaScript API。

要執行以上所有操作,請在專案目錄中執行這個指令

wasm-pack build

建置完成後,我們可以在 pkg 目錄中找到其產出,它應該有這些內容

pkg/
├── package.json
├── README.md
├── wasm_game_of_life_bg.wasm
├── wasm_game_of_life.d.ts
└── wasm_game_of_life.js

README.md 檔案從主專案複製,但其他檔案是全新的。

wasm-game-of-life/pkg/wasm_game_of_life_bg.wasm

.wasm 檔案是 Rust 編譯器根據我們的 Rust 原始碼產生的 WebAssembly 執行檔。它包含所有我們的 Rust 函式和資料以編譯成 wasm 的版本。例如,它有已匯出的「greet」函式。

wasm-game-of-life/pkg/wasm_game_of_life.js

.js 檔案由 wasm-bindgen 產生,包含將 DOM 和 JavaScript 函式匯入 Rust 以及公開 WebAssembly 函式提供極好的 JavaScript API 的 JavaScript 黏合。例如,有一個 JavaScript greet 函式包裹 WebAssembly 模組匯出的 greet 函式。現在,這個黏合並沒有做太多事情,但當我們開始在 wasm 和 JavaScript 之間傳遞更多有趣的數值時,這將有助於將這些數值引導跨越邊界。

import * as wasm from './wasm_game_of_life_bg';

// ...

export function greet() {
    return wasm.greet();
}

wasm-game-of-life/pkg/wasm_game_of_life.d.ts

.d.ts 檔案包含 JavaScript 黏合的 TypeScript 型別宣告。如果您正在使用 TypeScript,您可以讓系統檢查呼叫 WebAssembly 函式的型別,而且您的 IDE 可以提供自動完成和建議!如果您沒有使用 TypeScript,您可以安全地忽略這個檔案。

export function greet(): void;

wasm-game-of-life/pkg/package.json

package.json 檔案包含有關產生的 JavaScript 和 WebAssembly 套件的元資料。 npm 和 JavaScript 打包器使用這項功能來確定套件之間的依賴關係、套件名稱、版本和其他許多東西。這有助於我們整合 JavaScript 工具,並允許我們將我們的套件發佈到 npm。

{
  "name": "wasm-game-of-life",
  "collaborators": [
    "Your Name <your.email@example.com>"
  ],
  "description": null,
  "version": "0.1.0",
  "license": null,
  "repository": null,
  "files": [
    "wasm_game_of_life_bg.wasm",
    "wasm_game_of_life.d.ts"
  ],
  "main": "wasm_game_of_life.js",
  "types": "wasm_game_of_life.d.ts"
}

將其放入網頁

要擷取我們的 wasm-game-of-life 套件並在網頁中使用它,我們使用 the create-wasm-app JavaScript 專案範本

wasm-game-of-life 目錄中執行這個指令

npm init wasm-app www

我們新的 wasm-game-of-life/www 子目錄包含以下內容

wasm-game-of-life/www/
├── bootstrap.js
├── index.html
├── index.js
├── LICENSE-APACHE
├── LICENSE-MIT
├── package.json
├── README.md
└── webpack.config.js

讓我們再次仔細查看其中幾個檔案。

wasm-game-of-life/www/package.json

這個 package.json 已經預先設定好 webpackwebpack-dev-server 相依項目,以及一個 hello-wasm-pack 相依項目,這是最初的 wasm-pack-template 套件已發布到 npm 的版本。

wasm-game-of-life/www/webpack.config.js

這個檔案會設定 webpack 和其本機開發伺服器。它已經預先設定好,你不應該需要修改這個任何設定來讓 webpack 和其本機開發伺服器正常運作。

wasm-game-of-life/www/index.html

這是網頁的根目錄 HTML 檔案。除了載入 bootstrap.js 之外,它並未執行太多動作,而 bootstrap.jsindex.js 的一個很薄的包裝器。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Hello wasm-pack!</title>
  </head>
  <body>
    <script src="./bootstrap.js"></script>
  </body>
</html>

wasm-game-of-life/www/index.js

index.js 是網頁 JavaScript 的主要進入點。它會匯入 hello-wasm-pack npm 套件,其中包含預設的 wasm-pack-template已編譯好的 WebAssembly 與 JavaScript 黏合程式,然後呼叫 hello-wasm-packgreet 函數。

import * as wasm from "hello-wasm-pack";

wasm.greet();

安裝相依項目

首先,確保本機開發伺服器及其相依項目已安裝,方法是在 wasm-game-of-life/www 子目錄中執行 npm install

npm install

這個指令只需要執行一次,它將安裝 webpack JavaScript 捆綁器及其開發伺服器。

請注意,使用 Rust 和 WebAssembly 不需要 webpack,它只是一個我們在此出於方便而選擇的捆綁器和開發伺服器。Parcel 和 Rollup 也應支援匯入 WebAssembly 作為 ECMAScript 模組。如果你願意,你也可以使用沒有捆綁器的 Rust 和 WebAssembly in a bundler

www 中使用我們的本機 wasm-game-of-life 套件

我們不希望使用來自 npm 的 hello-wasm-pack 套件,而是希望改用我們的本機 wasm-game-of-life 套件。這樣讓我們可以逐步開發我們的 Game of Life 程式。

開啟 wasm-game-of-life/www/package.json,在 "devDependencies" 旁邊,新增 "dependencies" 欄位,包括 "wasm-game-of-life": "file:../pkg" 項目。

{
  // ...
  "dependencies": {                     // Add this three lines block!
    "wasm-game-of-life": "file:../pkg"
  },
  "devDependencies": {
    //...
  }
}

接著,修改 wasm-game-of-life/www/index.js 以匯入 wasm-game-of-life,而不是 hello-wasm-pack 套件。

import * as wasm from "wasm-game-of-life";

wasm.greet();

由於我們新增一個相依項目,我們需要安裝它。

npm install

我們的網頁現在準備好可以在本機伺服!

在本地端提供服務

接著,為開發伺服器開啟一個新的終端機。在一個新的終端機中執行伺服器讓我們可以讓它在背景中執行,而且這不會阻擋我們在此同時執行其他指令。在新終端機中,從 wasm-game-of-life/www 目錄執行這個指令:

npm run start

在你的網頁瀏覽器導航到 https://127.0.0.1:8080/,你應該會看到一個快顯訊息。

Screenshot of the "Hello, wasm-game-of-life!" Web page alert

每次您進行變更並希望反映在 https://127.0.0.1:8080/ 中,只需在 wasm-game-of-life 目錄中重新執行 wasm-pack build 命令即可。

練習

  • 修改 wasm-game-of-life/src/lib.rs 中的 greet 函式以接受自訂提醒訊息的 name: &str 參數,並將您的姓名傳遞給 wasm-game-of-life/www/index.js 中的 greet 函式。使用 wasm-pack build 重新建置 .wasm 執行檔,然後在您的 Web 瀏覽器中重新整理 https://127.0.0.1:8080/,您應該會看到自訂的歡迎訊息!

    解答

    wasm-game-of-life/src/lib.rs 中的 greet 函式的新版本

    
    # #![allow(unused_variables)]
    #fn main() {
    #[wasm_bindgen]
    pub fn greet(name: &str) {
        alert(&format!("Hello, {}!", name));
    }
    #}

    wasm-game-of-life/www/index.jsgreet 的新呼叫

    wasm.greet("Your Name");