flowchart LR 0["概念"] 0 --> WASM 0 --> Rust 0 --> JavaScript Rust --> rustc Rust --> rustup Rust --> wasm-pack Rust --> Cargo
WebAssembly是什么?
WebAssembly (wasm) 是一种简单的机器模型和具有 广泛规范 的可执行格式。 它被设计为便携、紧凑,并以或接近原生速度执行。
作为一种编程语言,WebAssembly 由两种格式组成
- .wat 文本格式
- .wasm二进制格式是较低级别的
WASM 开发语言的选择
要写 WASM 应用的话首先不能选用有 GC 的语言,不然垃圾收集器的代码也会占用很大一部分的体积,对 WASM 文件的初始化加载并不友好,比较好的选择就是 C/C++/Rust 这几个没有 GC 的语言,当然使用 Go、C#、TypeScript 这些也是可以的,但是性能也会没有 C/C++/Rust 这么好。
Rust 的官方和社区对于 WASM 都有着一流的支持,而且它也是一门系统级编程语言,有一个和 NPM 一样好用的包管理器 Cargo,同时 Rust 也拥有着很好的性能,用来写 WASM 再好不过了。同时它的社区热度也在不断的上升中。
Rust 开发 WASM
Rust 提供了对 WASM 一流的支持,Rust 无需 GC 、零运行时开销的特点也让它成为了 WASM 的完美候选者。Rust 是怎么编译成 WASM 代码的:
从零开始 WASM 项目并打包发布
安装rust
➜ rustc --version
rustc 1.70.0 (90c541806 2023-05-31)
安装打包器(wasm-pack) https://rustwasm.github.io/wasm-pack/
下载 wasm-pack,用于将 rust 代码打包成 .wasm 文件
一个专门用于打包、发布 WASM 的工具,可以用于构建可在 NPM 发布的 WASM 工具包。当我们开发完 WASM 模块时,可以直接使用 wasm-pack publish 命令把我们开发的 WASM 包发布到 NPM 上。使用 cargo install wasm-pack
命令来进行安装。
➜ wasm-pack
📦 ✨ pack and publish your wasm!
Usage: wasm-pack [OPTIONS] <COMMAND>
Commands:
build 🏗️ build your npm package!
pack 🍱 create a tar of your npm package but don't publish!
new 🐑 create a new project with a template
publish 🎆 pack up your npm package and publish!
login 👤 Add an npm registry user account! (aliases: adduser, add-user)
test 👩🔬 test your wasm!
help Print this message or the help of the given subcommand(s)
Options:
-v, --verbose... Log verbosity is based off the number of v used
-q, --quiet No output printed to stdout
--log-level <LOG_LEVEL> The maximum level of messages that should be logged by wasm-pack. [possible values: info, warn, error] [default: info]
-h, --help Print help
-V, --version Print version
➜ wasm-pack --version
wasm-pack 0.12.1
创建基础工程
cargo new example --lib
cd example
npm init -y
Hello World
src/lib.rs
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern {
pub fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet(name: &str) {
alert(&format!("Hello, {}!", name));
}
然后在 Cargo.toml 文件中添加 wasm-bindgen 依赖,wasm-bindgen来提供 JavaScript 和 Rust 类型之间的异常桥梁,允许使用 JavaScript 字符串调用 Rust API,或调用 Rust 函数来捕获 JavaScript。
[package]
name = "hello-wasm"
version = "0.1.0"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.89"
构建
WebAssembly 构建产物将会输出在 pkg 目录下
wasm-pack build
├── README.md
├── hello_wasm.d.ts
├── hello_wasm.js
├── hello_wasm_bg.js
├── hello_wasm_bg.wasm
├── hello_wasm_bg.wasm.d.ts
└── package.json
如果想当 npm 包发布的话,可以添加 —scope 参数,将会在 pkg 下生成 package.json 文件用于发布或当做一个 npm 包来使用,这样也可以在前端工程中直接当做一个模块来导入使用。
wasm-pack build --scope mynpmusername
借助 wasm-pack 可以非常轻松的将 rust 打包成 wasm,同时还提供了 js 相关支持。直接打包成 js 可导入的 npm 包,而不是让用户导入 wasm 文件然后通过浏览器 WebAssembly 对象来加载 WebAssembly 代码,其他语言的 WebAssembly 开发也是如此。
运行
编辑src/index.js
async function main() {
const module = await import('../pkg/hello_wasm.js');
module.greet("Hello Rs");
}
main();
根目录index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WASM Hello World</title>
</head>
<body></body>
<script src="js/index.js"></script>
</html>
vite.config.js
import wasm from "vite-plugin-wasm";
import topLevelAwait from "vite-plugin-top-level-await";
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
wasm(),
topLevelAwait()
],
server: {
host: '0.0.0.0',
port: 3000,
},
});
这时候就可以通过 js 直接导入使用
const js = import("./hello-wasm/hello_wasm.js");
js.then(js => {
js.greet("WebAssembly");
});
console
控制台打印 Hello World!
src/lib.rs
use wasm_bindgen::prelude::*;
use web_sys::console;
#[wasm_bindgen]
pub fn greet() {
console::log_1(&JsValue::from_str("Hello World!"));
}
js/index.js
async function main() {
const module = await import('../pkg/hello_wasm.js');
module.greet();
}
main();
Cargo.toml
[package]
categories = ["wasm"]
description = ""
edition = "2021"
name = "hello-wasm"
version = "0.1.0"
[lib]
# 一个动态的系统库将会产生,类似于C共享库。当编译一个从其它语言加载调用的动态库时这属性将会被使用
crate-type = ["cdylib"]
[features]
[dependencies]
# 用于将实体从 Rust 绑定到 JavaScript,或反过来。
# 提供了 JS 和 WASM 之间的通道,用来传递对象、字符串、数组这些数据类型
wasm-bindgen = "0.2.83"
wee_alloc = {version = "0.4.5", optional = true}
# web-sys 可以和 JS 的 API 进行交互,比如 DOM
[dependencies.web-sys]
features = ["console"]
version = "0.3.60"
[dev-dependencies]
# 用于所有JS环境 (如Node.js和浏览器)中的 JS 全局对象和函数的绑定
js-sys = "0.3.60"
# 0 – 不优化
# 1 – 基础优化
# 2 – 更多优化
# 3 – 全量优化,关注性能时建议开启此项
# s – 优化二进制大小
# z – 优化二进制大小同时关闭循环向量,关注体积时建议开启此项
[profile.dev]
debug = true
# link time optimize LLVM 的链接时间优化,false 时只会优化当前包,true/fat会跨依赖寻找关系图里的所有包进行优化
# 其它选项还有 off-关闭优化,thin是fat的更快版本
lto = true
opt-level = 'z'
[profile.release]
debug = false
lto = true
opt-level = 'z'
Rust 实现 MD5 算法
包查询
https://crates.io/crates/digest
加入依赖
Cargo.toml
[dependencies]
...
md5="0.7.0"
看到使用方法
src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn md5(input: &str)-> String {
let digest = md5::compute(input);
format!("{:x}", digest)
}
前端调用
反调试
通过 wasm 可以有效的将代码隐藏起来
发布NPM
Cargo.toml,并在 name 后面加上你的包名
[package]
name = "hello-wasm-taoya7"
wasm-pack login
wasm-pack publish
错误
Error: wasm32-unknown-unknown target not found in sysroot
https://github.com/rustwasm/wasm-pack/issues/579
RUSTUP_USE_CURL=1 rustup update
Rustup 是 Rust 的命令行工具,用于在系统上安装和管理 Rust 编译器。
解决办法: 切换 版本
参考
https://juejin.cn/post/7219613068275449893