这是使用 Rust 和 WebAssembly 的**未发布**文档,已发布的文档可在Rust 和 WebAssembly 主文档网站上找到。此处记录的功能可能在 Rust 和 WebAssembly 工具的发布版本中不可用。

你好,世界!

本节将向您展示如何构建和运行第一个 Rust 和 WebAssembly 程序:一个在网页上弹出“你好,世界!”的网页。

在开始之前,请确保您已按照设置说明进行操作。

克隆项目模板

项目模板预先配置了合理的默认值,因此您可以快速构建、集成和打包代码以供 Web 使用。

使用以下命令克隆项目模板

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 文件指定了 Rust 的包管理器和构建工具 cargo 的依赖项和元数据。此文件预先配置了 wasm-bindgen 依赖项,一些我们稍后将深入研究的可选依赖项,以及为生成 .wasm 库而正确初始化的 crate-type

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 来协调以下构建步骤

  • 确保我们已安装 Rust 1.30 或更高版本以及 wasm32-unknown-unknown 目标(通过 rustup 安装),
  • 通过 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 以及向 JavaScript 公开 WebAssembly 函数的良好 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 文件包含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 <[email protected]>"
  ],
  "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 的依赖项,它是已发布到 npm 的初始 wasm-pack-template 包的版本。

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

将您的 Web 浏览器导航到http://localhost:8080/,您应该会看到一个弹出消息

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

每当您进行更改并希望它们反映在http://localhost: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 浏览器中的http://localhost: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");