Hello, World!

本节将向你展示如何构建和运行你的第一个 Rust 和 WebAssembly 程序:一个警告“Hello,World!”

开始之前,请确保已按照安装说明进行操作。

克隆项目模板

项目模板预先配置了 sane 默认值,因此你可以快速构建、集成和打包Web代码。

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

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

这将提示你输入新项目的名称。我们将使用 “wasm-game-of-life”

  1. wasm-game-of-life

里面的内容

输入命令进入新创建的项目 wasm-game-of-life

  1. cd wasm-game-of-life

让我们看看它的内容:

  1. wasm-game-of-life/
  2. ├── Cargo.toml
  3. ├── LICENSE_APACHE
  4. ├── LICENSE_MIT
  5. ├── README.md
  6. └── src
  7. ├── lib.rs
  8. └── utils.rs

让我们详细地看一下这些文件吧。

wasm-game-of-life/Cargo.toml

Cargo.toml 文件是 CargoRust 的包管理器和构建工具指定依赖项和元数据。这一个预先配置了一个wasm bindgen依赖项、几个我们稍后将深入研究的可选依赖项,以及为生成 .wasm 库而正确初始化的 crainte type

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

src/lib.rs 文件是我们正在编译到 WebAssembly 的 Rust crate 的根文件。它使用 wasm bindgen 与JavaScript 接口。它导入 window.alert JavaScript 函数,并导出名为 greet Rust 函数,该函数用于创建警告弹窗消息。

  1. mod utils;
  2. use wasm_bindgen::prelude::*;
  3. // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
  4. // allocator.
  5. #[cfg(feature = "wee_alloc")]
  6. #[global_allocator]
  7. static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
  8. #[wasm_bindgen]
  9. extern {
  10. fn alert(s: &str);
  11. }
  12. #[wasm_bindgen]
  13. pub fn greet() {
  14. alert("Hello, wasm-game-of-life!");
  15. }

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

src/utils.rs 模块提供了公共实用程序,使处理编译到 WebAssembly 的 Rust 变得更容易。我们将在本教程后面的部分更详细地了解其中的一些实用程序,例如在调试 wasm 代码时,但现在可以忽略此文件。

构建项目

我们使用 wasm-pack 构建的步骤:

  • 确保我们有 Rust 1.30 或更新版本,并且通过 rustup安装了 wasm32 unknown target,
  • 经过 cargo,将我们的 Rust 源代码编译成为 WebAssembly .wasm 的二进制文件,
  • 使用 wasm bindgen 生成 JavaScript API,以使用我们的 Rust-generated WebAssembly。

要执行所有这些操作,请在项目目录中运行以下命令:

  1. wams-pack build

构建完成后,我们可以在pkg目录中找到它的工件,它应该有以下内容:

  1. pkg/
  2. ├── package.json
  3. ├── README.md
  4. ├── wasm_game_of_life_bg.wasm
  5. ├── wasm_game_of_life.d.ts
  6. └── 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,并将一个优雅的 API 开放给 JavaScript 的 WebAssembly 函数。 例如,下面的 JavaScript greet 函数是从 WebAssembly 模块导出的 greet函数。 现在,这个胶水层的作用不大,但是当我们开始在 wasm 和 JavaScript 之间来回传递更多有趣的值时,它将帮助引导这些值越过边界。

  1. import * as wasm from './wasm_game_of_life_bg';
  2. // ...
  3. export function greet() {
  4. return wasm.greet();
  5. }

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

.d.ts 文件包含了 JavaScript 胶水层的 TypeScript 类型声明。 如果你使用了 TypeScript,你可以检查对 WebAssembly 函数的调用类型,还有你的 IDE 可以根据你提供的类型文件进行类型推导。如果不使用TypeScript,可以安全地忽略此文件。

  1. export function greet(): void;

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

package.json 文件包含有关生成的JavaScript和WebAssembly包的元数据。。 npm 和 JavaScript 使用它来确定包、包名、版本还有其他内容之间的依赖关系。 它帮助我们与 JavaScript 工具集成,并允许我们将包发布到npm。

  1. {
  2. "name": "wasm-game-of-life",
  3. "collaborators": [
  4. "Your Name <your.email@example.com>"
  5. ],
  6. "description": null,
  7. "version": "0.1.0",
  8. "license": null,
  9. "repository": null,
  10. "files": [
  11. "wasm_game_of_life_bg.wasm",
  12. "wasm_game_of_life.d.ts"
  13. ],
  14. "main": "wasm_game_of_life.js",
  15. "types": "wasm_game_of_life.d.ts"
  16. }

把它放到网页上

为了将我们的 wasm-game-of-life 包用于 Web 页面,我们可以使用 create-wasm-app JavaScript项目模板

wasm-game-of-life 里面运行命令:

  1. npm init wasm-app www

下面是我们新的 wasm-game-of-life/www 子目录包含的内容:

  1. wasm-game-of-life/www/
  2. ├── bootstrap.js
  3. ├── index.html
  4. ├── index.js
  5. ├── LICENSE-APACHE
  6. ├── LICENSE-MIT
  7. ├── package.json
  8. ├── README.md
  9. └── webpack.config.js

再来一次,让我们仔细看看这些文件中的一些。

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

package.json 配置了 webpackwebpack-dev-server 依赖以及对 hello-wasm-pack 的依赖关系,它是已发布到 npm 的初始 wasm-pack-templatek的一个版本。

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

这个文件配置了 webpack 以及本地开发服务器。 它是预先配置好的,不需要任何调整或配置即可在本地运行了。

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

这是网页的根文件,它除了加载 bootstrap.js 之外,它没有做其他操作。这是 index.js 的一个较小的封装。

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>Hello wasm-pack!</title>
  6. </head>
  7. <body>
  8. <script src="./bootstrap.js"></script>
  9. </body>
  10. </html>

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

index.js 是网页 JavaScript 的主要入口点。 它导入 hello-wasm-pack npm 包,其中包含默认 wasm-pack 模板的编译 WebAssembly 和 JavaScript 胶水层,然后调用 hello-wasm-packgreet 函数。

  1. import * as wasm from "hello-wasm-pack";
  2. wasm.greet();

安装依赖

首先,通过在 wasm-game-of-life/www 子目录中运行 npm install,确保安装了本地开发服务器及其依赖项:

  1. npm install

此命令只需运行一次,将会安装 webpack JavaScript 依赖及其开发服务器。

请注意,Rust 和 WebAssembly 并不依赖 webpack,它只是我们为方便起见选择的绑定器和开发服务器。Parcel and Rollup 还应支持将 WebAssembly 作为 ECMAScript 模块导入。如果你愿意,也可以使用不带捆绑程序的 RustWebAssembly

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" 加到里面:

  1. {
  2. // ...
  3. "dependencies": { // Add this three lines block!
  4. "wasm-game-of-life": "file:../pkg"
  5. },
  6. "devDependencies": {
  7. //...
  8. }
  9. }

然后引入 wasm-game-of-life 到文件 wasm-game-of-life/www/index.js 里面,而不是 hello-wasm-pack 包:

  1. import * as wasm from "wasm-game-of-life";
  2. wasm.greet();

既然我们新增了依赖,那我们就需要更新一下:

  1. npm install

接着,我们的网页现在可以在本地运行了!

在本地运行

接下来,为开发服务器打开一个新终端。 在一个新的终端上运行服务器可以让它在后台运行,同时不会阻止我们运行其他命令。 在新终端中,从 wasm-game-of-life/www 目录中运行以下命令:

  1. npm run start

打开浏览器进入 http://localhost:8080,将会出现弹窗警告信息。

alert message

更改之后,如果希望出现在 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 二进制文件,然后在浏览器重新打开 http://localhost:8080 ,将会出现自定义的弹窗警告信息。

答案

将新的函数 greet 写在 wasm-game-of-life/src/lib.rs

  1. #[wasm_bindgen]
  2. pub fn greet(name: &str) {
  3. alert(&format!("Hello, {}!", name));
  4. }

可以使用调用方式

  1. wasm.greet("Name");