這是 Rust 和 WebAssembly 使用手冊的未發布版本,已發布的版本可於 主要的 Rust 和 WebAssembly 文件網站 上取得。在此記載的功能可能不在已發布的 Rust 和 WebAssembly 工具版本中提供。

Hello,世界!

本節將示範如何建立並執行您的第一個 Rust 和 WebAssembly 程式:一個會彈出「Hello,世界!」警示的網頁。

在開始前,請確認您已遵循 設定說明

複製專案範本

此專案範本預設為妥善設定,讓您能快速建置、整合和封裝您的程式碼,以在網路上使用。

使用此指令複製專案範本

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 的依賴項和元資料,而 cargo 是 Rust 的套件管理員和建構工具。其中預先設定的資料包括 wasm-bindgen 依賴項、一些我們將在稍後深入了解的選用依賴項,以及已妥善初始化的 crate-type,用於產生 .wasm 函式庫。

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

src/lib.rs 檔案是我們編譯為 WebAssembly 的 Rust 箱根。它使用 wasm-bindgen 與 JavaScript 介接。它匯入了 window.alert JavaScript 函數,並匯出了 greet Rust 函數,其中會發出問候訊息。


# #![allow(unused_variables)]
#fn main() {
extern crate cfg_if;
extern crate wasm_bindgen;

mod utils;

use cfg_if::cfg_if;
use wasm_bindgen::prelude::*;

cfg_if! {
    // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
    // allocator.
    if #[cfg(feature = "wee_alloc")] {
        extern crate 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 產生的,其中包含 Javascript 黏著劑,用於將 DOM 及 JavaScript 函數匯入 Rust 中,並向 JavaScript 公開 WebAssembly 函數的良好 API。舉例來說,有一個 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 檔案包含 TypeScript 類型宣告,用於 JavaScript 黏著劑。如果您正在使用 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 套件並在網頁中使用它,我們使用 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,這是一個 index.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 綁定套件及其開發伺服器。

請注意,webpack 不是處理 Rust 和 WebAssembly 所必需的,它只是我們在此處為方便起見選擇的綁定套件和開發伺服器。Parcel 和 Rollup 也應支援將 WebAssembly 匯入為 ECMAScript 模組。您也可以使用 Rust 和 WebAssembly 不用綁定套件,如果您想要的話!

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

我們不使用 npm 的 hello-wasm-pack 套件,而是希望使用我們自己的本機 wasm-game-of-life 套件。這將讓我們逐步開發我們的生命遊戲程式。

開啟 wasm-game-of-life/www/package.json 並編輯 "dependencies" 來包含 "wasm-game-of-life": "file:../pkg" 項目

{
  // ...
  "dependencies": {
    "wasm-game-of-life": "file:../pkg", // Add this line!
    // ...
  }
}

再來,修改 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 二進位檔,然後在你的瀏覽器中更新 https://127.0.0.1:8080/,你應該會看到一個自訂的問候!

    解答

    wasm-game-of-life/src/lib.rsgreet 函式的新版本

    
    # #![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");