您当前的位置:首页 > 计算机 > 编程开发 > Other

基于 Rust 构建 WebAssembly

时间:12-14来源:作者:点击数:

本文试验系统:Windows 10 64位

安装 rust

前往 https://www.rust-lang.org/install.html 并按照说明安装 Rust。在安装过程的某个步骤,你会收到一个信息说明为什么需要安装 Visual Studio 2013 或更新版本的 C++ build tools。获取这些 build tools 最方便的方法是安装 Build Tools for Visual Studio 2019。当被问及需要安装什么的时候请确保选择 C++ build tools,并确保包括了 Windows 10 SDK 和英文语言包(English language pack)组件。

在rust官网安装页面下载 rustup-init.exe 64 位安装包

运行 rustup-init.exe

默认安装 rust 在本地用户相关目录比如 C:/user/qqing ,也可使用环境变量改变安装路径,这里安装在 d:/rust 根目录(包含 .rustup.cargo 两个子目录),设置环境变量 RUSTUP_HOMEd:/rust/.rustup ,环境变量 CARGO_HOMEd:/rust/.cargo

为了方便可控,这里选择自定义安装完全包及改变 PATH 环境变量

安装 wasm-pack

wasm-pack 用于将 rust 程序编译成 WebAssembly 包,运行 cargo install wasm-pack

从 Rust 程序构建 WebAssembly 包

新建一个库工程

这里以 hello-wasm 为例

编辑 cargo.toml 文件,增加如下内容

[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
[profile.release]
lto=true
opt-level="z"

cdylib 指示构建动态库,对应 Linux 上输出为 *.so 文件,Windows 输出为 *.dll 文件,MacOS 输出为 *.dylib 文件。

wasm-bindgen 被 wasm-pack 使用,在 JavaScript 类型和 Rust 之间架起一座桥梁。它允许 JavaScript 使用字符串调用 Rust API,或者使用 Rust 函数来捕获 JavaScript 异常。

lto 和 opt-level 用于优化 wasm 包的大小。

修改 src/lib.rs ,替换为如下内容

extern crate wasm_bindgen;use wasm_bindgen::prelude::*;#[wasm_bindgen]extern {pub fn alert(s: &str);}#[wasm_bindgen]pub fn say(name: &str) {alert(&format!("Hello, {}!", name));}#[wasm_bindgen]pub fn fib(i: u32) -> u32 {match i {0 => 0,1 => 1,_ => fib(i-1) + fib(i-2)}}

开头两行表示使用外部库 wasm_bindgen 的 prelude 模块,后面的 #[wasm_bindgen] 表示将下面的 pub 函数转成 wasm 字节码,及生成对应的 js 包装代码。alert 为 Web js API。

构建适用于 Web 的 wasm 包

为了和后面 npm 环境区别,这里输出到 webpgk 子目录。 --target 选项值的不同主要影响生成结果js文件内的代码,具体含义参考如下

Option

Usage

Description

not specified or

bundler

Bundler

Outputs JS that is suitable for interoperation with a Bundler like Webpack. You'll import the JS and the module key is specified in package.json. sideEffects: false is by default.

nodejs

Node.js

Outputs JS that uses CommonJS modules, for use with a require statement. main key in package.json.

web

Native in browser

Outputs JS that can be natively imported as an ES module in a browser, but the WebAssembly must be manually instantiated and loaded.

no-modules

Native in browser

Same as web, except the JS is included on a page and modifies global state, and doesn't support as many wasm-bindgen features as web

在 Web 上使用 wasm 包

新建主页文件

在 hello-wasm 当前目录下创建 index.html 文件,输入内容如下

<!DOCTYPE html><html><head><meta charset="utf-8"><title>hello-wasm example</title></head><body><script type="module">function fib(i) {if (i === 0) return 0;if (i === 1) return 1;return fib(i - 1) + fib(i - 2);}var n = 40;import init, {say,fib as wasm_fib} from "./webpkg/hello_wasm.js";init().then(() => {console.time('wasm');console.log(wasm_fib(n));console.timeEnd('wasm');console.time('js');console.log(fib(n));console.timeEnd('js');say("WebAssembly");});</script></body></html>

生成的 js 代码分析

webpkg 子目录下的 hello_wasm.js 文件包含了装载 hello_wasm_bg.wasm 的代码,封装在 load 和 init 函数中

rust say 和 fib 接口包装如下

rust 调用的 js API alert 包装如下

运行 Web 服务

这里使用 basic-http-server,若没有安装,则先运行 cargo install basic-http-server

指定 0.0.0.0 通用地址是为了通过 127.0.0.1localhost 和本地 IP 皆可访问。

浏览器访问

地址栏输入 localhost:8000 ,回车或刷新后结果如下

可以看到浏览器页面有弹框,及控制台输出了调用 wasm fib 函数的耗时。

在 Web 中使用 npm 包

构建适用于 npm 的 wasm 包

不指定选项参数,则默认目标为 bundler,且输出在 pkg 子目录。

生成的 js 代码分析

查看 pkg 目录下的 hello_wasm_bg.jshello_wasm.js 文件

可以发现与原生 Web 环境不同的是使用了 import 语句来导入 wasm 字节码。 rust 接口 say 和 fib,及调用的 js API alert 包装如下

安装 nodejs 和 npm

下载这个 msi 软件包,然后按向导逐步安装

创建站点

创建 site 子目录

进入 site 并创建 package.json 文件,输入内容如下

{"scripts": {"serve": "webpack-dev-server"},"dependencies": {"hello_wasm": "file:../pkg"},"devDependencies": {"webpack": "^4.25.1","webpack-cli": "^3.1.2","webpack-dev-server": "^3.1.10"}}

这里使用 webpack 工具, file:../pkg 表示使用相对位置, dependencies 表示生产环境依赖包, devDependencies 表示开发依赖包。

新建 webpack.config.js 文件,输入内容如下

const path = require('path');module.exports = {entry: "./index.js",output: {path: path.resolve(__dirname, "dist"),filename: "index.js",},mode: "development"};

新建 index.html 文件,输入内容如下

<!DOCTYPE html><html><head><meta charset="utf-8"><title>hello-wasm example</title></head><body><script src="./index.js"></script></body></html>

新建 index.js 文件,输入内容如下

function fib(i) {if (i === 0) return 0;if (i === 1) return 1;return fib(i - 1) + fib(i - 2);}var n = 40;const js = import("../pkg/hello_wasm.js");js.then(js => {console.time('wasm');console.log(js.fib(n));console.timeEnd('wasm');console.time('js');console.log(fib(n));console.timeEnd('js');js.say("WebAssembly with npm");});

所有文件创建完后如下

安装依赖包

运行 npm install

可以看到创建了一个 node_modules 目录,该目录下存放所有依赖包

运行 Web with npm 服务

指定通用 IP 和 8080 端口

浏览器访问

地址栏输入 localhost:8080,回车或刷新后结果如下

可以看到浏览器页面有弹框,及控制台输出了调用 wasm fib 函数的耗时。对比前述原生 Web 环境的js运行结果,可以发现 wasm 调用耗时差不多,但js耗时差别比较大。

进一步减少 wasm 包大小

前文在 Cargo.toml 配置中使用了 opt-level 的 z 优化级别来减少尺寸,进一步可用 wasm-strip(可从 https://github.com/WebAssembly/wabt.git 编译构建)去掉所有 section,下面以 webpkg 下的文件为例

strip 后,相比减少了 100+ 字节。

离线版:https://www.wenjiangs.com/wp-content/uploads/2023/04/wjFnvrZX3bdtn1Fr.zip

方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
    无相关信息
栏目更新
栏目热门
本栏推荐