前端应该知道wasm-pack,用JS调用Rust+WASM很方便

查理谈科技 2024-05-12 22:55:23

Rust 语言以其高性能、出色的内存安全机制等特性, 吸引了大量开发人员的注意, 微软、华为等大型科技公司也纷纷采用Rust 语言开发其公司的各种产品; 而且随着Web Assembly的迅速发展, WebAssembly 也成为越来越多的跨平台应用程序的编译目标, 在这种情况下, Rust + WebAssembly,就成为一种构建高性能Web 应用的新途径, 把Rust编写的高性能组件, 编译成WebAssembly 代码, 然后在浏览器端被JavaScript 调用和展示, 充分利用WebAssembly接近原生的运行速度,构建在浏览器上运行的新型的高性能Web 应用。

WebAssembly(缩写为 Wasm)是一种基于堆栈的虚拟机的二进制指令格式。 Wasm 被设计为编程语言的可移植编译目标,支持在网络上部署客户端和服务器应用程序。————Web Assembly 定义

WebAssembly 设计之初,就是用来可以和JavaScript 协同工作, 通过使用WebAssembly的JavaScript API, 把WebAssembly模块加载到JavaScript 应用中, 并且在两者之间共享对象、函数等。

而WasmPack的出现,就是为了能够更好的把Rust 和WebAssembly 结合起来。WasmPack官方的定义, 就是让开发人员更方便的把Rust语言转换成WebAssmebly,WebPack就是为了实现这个目的提供了一站式的软件和服务, 让浏览器端应用或者Node.js更方便的调用Rust 编写的代码。当然, WasmPack的目标,可不仅仅是为了把Rust语言转换成Wasm,更是想着把更多的语言转换成Wasm代码格式, 要不然这个名字就可以换成RustWasm 了, 不过Rust语言是当前阶段的主要目标。

下面我们就来体验一下WasmPack这个工具。

环境准备: 安装 WasmPack CLI

首先, 要安装WasmPack的CLI,命令行工具, 这个主要由三种方法:

通过Shell 命令安装:

curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh

第二种安装方式, 通过Rust Cargo 安装:

cargo install wasm-pack

第三种,通过yarn

npm install -g wasm-pack

或者yarn:

yarn global add wasm-pack

安装完成后,可以通过 wasm-pack -V来确认版本,当前最新版本为wasm-pack 0.12.1

WasmPack 依赖库

除了Wasm-Pack CLI 本身之外, 还需要安装下面的软件

Rust这个没啥好说的, 需要Rust环境来开发Rust 代码, Rustup安装也比较方便。Npm当前, wasm-pack需要npm来打包和发布, 没错, wasm-pack打包出来的文件,可以发布到NPM的网站, 为此, 需要开发者拥有一个Npm的网站账号, wasm-pack 提供了login 和publish 两个命令,用来登陆Npm 网站和发布package。WasmPack 主要命令

WasmPack 目前主要有如下几个命令:

wasm-pack new: 通过下载模板代码来创建RustWasm项目wasm-pack build: 构建,从rustwasm crate产生Npm wasm pkg 包wasm-pack test: 测试, 调用浏览器运行测试代码wasm-pack pack/publish: 创建发行包, pack用于创建包, 而publish 用于提交发行包到Npm 站点。

创建一个WasmPack 项目

这里首先创建一个WasmPack 项目:

wasm-pack new charley-wasm

执行结果:

wasm-pack new 是可以指定模板的, 语法为:

wasm-pack new myproject --template https://github.com/rustwasm/wasm-pack-template

默认的模版是https://github.com/rustwasm/wasm-pack-template

在wasm-pack new 的背后,其实是Rust Cargo在默默工作,cargo-generate 这个工具用来通过指定的模版来快速定制和创建Rust 项目。

当然,作为初学者, 不太需要了解cargo-generate这种工具以及细节,只需要简单了解一下。

下面是wasm-pack产生的代码:

理解WasmPack 项目的wasm-bindgen

下面来看一下产生的项目,打开Cargo.toml, 我们可以看到在依赖项方面,增加了wasm-bindgen

[package]name = "charley-wasm"version = "0.1.0"authors = ["Charley Chen <hintcnuie@sina.com>"]edition = "2018"[lib]crate-type = ["cdylib", "rlib"][features]default = ["console_error_panic_hook"][dependencies]wasm-bindgen = "0.2.84"# The `console_error_panic_hook` crate provides better debugging of panics by# logging them with `console.error`. This is great for development, but requires# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for# code size when deploying.console_error_panic_hook = { version = "0.1.7", optional = true }[dev-dependencies]wasm-bindgen-test = "0.3.34"[profile.release]# Tell `rustc` to optimize for small code size.opt-level = "s"

这个wasm-bindgen正是Rust代码和Wasm 交互的关键所在。

正如wasm-bindgen官网自己介绍, wasm-bingen致力于为wasm模块和JavaScript的高水平交互提供便利API, 通过这个工具, 开发人员可以方便的在Rust 中调用浏览器端的DOM元素、操控页面、Console log等,同时还把Rust语言的类、函数等暴露给JavaScript; 同时, wasm-bindgen 还丰富了Rust 和Wasm 交互的数据类型,增加了诸如String、Class、Closure等数据类型, 而不是Wasm 本身的u32和Float两种类型。

下图是wasm-bindgen中的web-sys crate, 可以看出为Rust 暴露了Windows对象、Dom Node 对象、WebGL、WebAudio等等对象,可以说,通过Web_sys和JS_sys, wasm_bindgen为Rust 在浏览器端的操作,开启了无限的可能。

再来看代码,最主要的文件就是src/lib.rs:

mod utils;use wasm_bindgen::prelude::*;#[wasm_bindgen]extern "C" { fn alert(s: &str);}#[wasm_bindgen]pub fn greet() { alert("Hello, charley-wasm!");}

在这个示例代码中, 首先就是引入了wasm_bindgen的代码库, 然后再通过Rust的extern 功能, 引入了alert 函数(这个alert 函数接收String 类型)。这里稍微讲解一下“#[ ]” , 这个其实是一个修饰符, 称之为属性, 在这里的作用是, 告诉Rust编译器,下面要调用一些外部定义的函数,而且告诉Rust“wasm-bindgen知道如何找到这些函数”。

随后Rust定义了一个greet函数,这个函数是专门供JavaScript 调用的,在函数内部还调用了alert函数,从而最后在HTML 页面中弹出对话框。

我们在这里把greet 函数更改如下:

mod utils;use std::f64;use wasm_bindgen::prelude::*;#[wasm_bindgen]extern "C" { fn alert(s: &str);}#[wasm_bindgen]pub fn greet() { alert("你好, 这里是 Charley的 Wasm示例代码, 由Rust 代码 经过wasm-pack 转译而成");}// Called by our JS entry point to run the example#[wasm_bindgen(start)]fn run() -> Result<(), JsValue> { // Use `web_sys`'s global `window` function to get a handle on the global // window object. let window = web_sys::window().expect("no global `window` exists"); let document = window.document().expect("should have a document on window"); let body = document.body().expect("document should have a body"); // Manufacture the element we're gonna append let val = document.create_element("p")?; val.set_text_content(Some("你好, 这里是 Charley的 Wasm示例代码, 由Rust 代码 经过wasm-pack 转译而成")); body.append_child(&val); let canvas = document.get_element_by_id("canvas").unwrap(); let canvas: web_sys::HtmlCanvasElement = canvas .dyn_into::<web_sys::HtmlCanvasElement>() .map_err(|_| ()) .unwrap(); let context = canvas .get_context("2d") .unwrap() .unwrap() .dyn_into::<web_sys::CanvasRenderingContext2d>() .unwrap(); context.begin_path(); // Draw the outer circle. context .arc(75.0, 75.0, 50.0, 0.0, f64::consts::PI * 2.0) .unwrap(); // Draw the mouth. context.move_to(110.0, 75.0); context.arc(75.0, 75.0, 35.0, 0.0, f64::consts::PI).unwrap(); // Draw the left eye. context.move_to(65.0, 65.0); context .arc(60.0, 65.0, 5.0, 0.0, f64::consts::PI * 2.0) .unwrap(); // Draw the right eye. context.move_to(95.0, 65.0); context .arc(90.0, 65.0, 5.0, 0.0, f64::consts::PI * 2.0) .unwrap(); context.stroke(); Ok(())}

在上面的代码中,我们先是调用alert,弹出了一个字符串,然后在页面中动态添加了一个HTML 节点<p>, 显示一段文字; 最后, 为了展示wasm_bindgen的能力,我们还用Rust代码,在页面的canvas元素上画了一个笑脸

这样,我们WasmPack 部分的代码就完成了。

编译和打包Wasm-Pack项目

wasm-pack项目的编译是比较简单的,在命令行中,运行:

wasm-pack build

执行结果:

结果中,对于代码有些建议, 这里先不用理会。

打包完成后,在pkg 目录中就会出现符合Npm 包规范的各种文件,我们编写的Rust代码被打包成wasm文件,同时还有调用这个wasm文件的ts和js文件,这里面最主要的是package.json:

编译完成之后, 还可以调用浏览器测试一下我们的代码, 命令如下:

wasm-pack test --headless --firefox

主要就是启动无头模式的浏览器,这里使用的是firefox, 来运行一遍tests/web.rs:

//! Test suite for the Web and headless browsers.#![cfg(target_arch = "wasm32")]extern crate wasm_bindgen_test;use wasm_bindgen_test::*;wasm_bindgen_test_configure!(run_in_browser);#[wasm_bindgen_test]fn pass() { assert_eq!(1 + 1, 2);}

最后,我们就可以运行publish命令:

wasm-pack publish

这里需要在命令行中按下回车键,然后在浏览器的打开页面中, 登陆Npm账号,随后就可以发布了:

下面是本文作者在Npm的Package 页面,地址:https://www.npmjs.com/package/charley-wasm?activeTab=readme

好了, 发布到Npm之后, WasmPack部分就结束了, 这里的Rust 代码,已经编译成Wasm 模块了, 下面的部分,就是在JavaScript中调用这个Npm 包。

创建Npm项目

在命令行中, 输入如下命令:

npm init wasm-app charley-rust-wasm-npm

运行结果:

npm init wasm-app charley-rust-wasm-npm

Rust + Wasm = ❤

通过这行代码,可以创建一个WebPack 代码, 这个项目基于模板 https://github.com/rustwasm/create-wasm-app

这里面的主要文件有:

index.html:包含 webpack 包的基本 html 文档index.js:带有注释的示例 js 文件,显示如何导入和使用 wasm pkgpackage.json 和 package-lock.json:引入 devDependency 以使用 webpackWebPackwebpack-cliwebpack-开发服务器:定义一个启动脚本来运行 webpack-dev-serverwebpack.config.js:用于将 js 与 webpack 捆绑在一起的配置文件

其中,在index.js中,调用了我们的Rust 编写并编译而成的Wasm 模块代码:

import * as wasm from "charley-wasm";wasm.greet();添加charley-wasm模块

我们上面生成的代码是基于代码模板的,默认会引用hello-wasm-pack

注意,这个npm 包并不是之前我们编写并上传的npm 库, 而是由Nick Fitzgerald编写的另外一个库,网址:https://www.npmjs.com/package/hello-wasm-pack

我们可以先运行一下,看看效果:

npm installnpm run buildnpm start

当运行npm run build 的时候,可能会遇到这么一个opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ]错误:

at FSReqCallback.readFileAfterClose [as oncomplete] (node:internal/fs/read_file_context:68:3) {

opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ],

library: 'digital envelope routines',

reason: 'unsupported',

code: 'ERR_OSSL_EVP_UNSUPPORTED'

我本地用的Node版本是Node.js v18.15.0,所以会有这种错误,有两种解决办法, 一种是使用nvm 切换Nodejs版本,切换到Node.js v16 就好;另外一种,就是在运行npm run build之前,提前执行:

export NODE_OPTIONS=--openssl-legacy-provider

我本地环境为MacOS, 如果是Windows 命令行, 执行的语句为

set NODE_OPTIONS=--openssl-legacy-provider

如果是Windows PowerShell,则执行:

$env:NODE_OPTIONS = "--openssl-legacy-provider"

随后,一切正常运行:

npm run start

打开本地页面:

这里, 我们需要删除之前的库:

npm uninstall hello-wasm-pack

然后,添加我们自己的npm 库:

npm i charley-wasm

然后,我们重新运行:

npm run start

运行结果:

动态效果如图示:

从效果上看, 完整实现了我们之前在Rust 写的功能: 调用alert, 绘制canvas和冬天添加一个<p>节点。

为了方便观看,这里我还把这个项目代码部署到了我个人的博客网站中,网址:https://www.zwchen.com/wasm/pack/

总结

本文介绍了WasmPack 项目, 这是一个方便将Rust 代码编译成Wasm 代码的工具, 非常适合Rust 开发者和前端人员学习。本文还介绍了Wasm-bindgen, 这个框架中提供了Rust 和JavaScript 相互操作的大量工具, 通过Wasm-bindgen , Rust 程序员可以更好的操纵HTML和JavaScript。

本文代码都提交到github中:

wasmpack 项目:https://github.com/hintcnuie/charley-wasm-pack

npm项目:https://github.com/hintcnuie/charley-new-wasm-app

总之, WasmPack值得每一个前端开发者和Rust 开发者学习, 极大的方便了JavaScript 和Rust 的相互操作。

 #wasm-pack# 

0 阅读:0

查理谈科技

简介:感谢大家的关注