前言#
今天咱们来学习rust搭配webAssembly,他俩很适合呢~(嘿嘿)。
基于文档:Compiling from Rust to WebAssembly - WebAssembly | MDN (mozilla.org)
如果不想手动搭建,可以直接拉这个包
安装#
rust我们已经有了,这里就不多说了
我们来安装支持webAssembly的工具wasm-pack[2],它能帮助我们将rust代码转换成wasm
cargo install wasm-pack很遗憾,作为window用户,这个安装方式基本跑不通,除非你安装了openssl-sys。这个工具里面有些crate依赖于这个openssl-sys,要下载这玩意儿还得折腾一会。
对应issue :
解决方案:
https://rustwasm.github.io/wasm-pack/installer/rustwasm.github.io/wasm-pack/installer/
下载下来后运行exe即可。
rust部分#
内容#
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern {
pub fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet(name: &str) {
alert(&format!("Hello, {}!", name));
}
use wasm_bindgen::prelude::*;将wasm_bindgen里的预导入这个crate全部导入,这样我们就可以直接用里面的一些东西了,比如宏。
#[wasm_bindgen]是一个属性宏,来自于wasm_bindgen这个crate。

wasm-pack是基于这个包的一个工具,它提供了rust和js沟通的桥梁,我们可以通过cargo下载下来或者直接去官方网站下载exe文件。
extern大家应该都认识,就是用来对接其它语言的。我们在unsafe那一章中接触过。
这里调用了js的alert方法。
回到我们的代码中
我们这里调用了js中的alert。
这里有个点,如果你跑了下代码你会发现我们的alert方法并不需要unsafe包裹,根据之前的学习知识,通过extern拓展进来的都是unsafe的,所以这里理论上是需要用到unsafe来包裹调用这个alert方法的代码。但是实际上并不需要,因为我们用了#[wasm_bindgen],它帮我们处理了unsafe的逻辑,然后提供安全的入口给我们。
测试#
那么我们的代码已经写好了,这里照例应该是得写test方法用来测试这块是否正常,但是涉及到两种编程语言之间的沟通,这个test暂时还不能写,得到js那边写。
配置#
然后我们就可以build了?不行,我们还没有配置wasm_bindgen,没有配置就想用?
[package]
name = "wasm_demo"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2" 其它我们都认识,这个crate-type是个啥呢?
官方文档中给出的链接:Linkage - The Rust Reference (rust-lang.org)
跟着链接可以看到

这是在声明这个crate的类型是一个dynamic library。这样这个crate在不同操作系统上打包出来的内容是不一样的。
打包#
那么现在我们就可以打包了,wasm_pack这个工具现在就派上用场了。
wasm-pack build --target web 

如果你遇到类似下面的报错

这边建议你换一个包的版本
这里有个issue:
但是没有给出解决方案,实际上应该是已经修复了的。
我这里0.2,自动是0.2.x中最新的版本。
前端部分#
我们直接在当前文件夹里新建一个index.html,省事。
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<title>hello-wasm example</title>
</head>
<body>
<script type="module">
import init, { greet } from "./pkg/wasm_demo.js";
init().then(() => {
greet("WebAssembly");
});
</script>
</body>
</html>
然后你可以看到我们这里引入了pkg/wasm_demo.js。
不过现在直接将html文件在浏览器中打开是会跨域的

所以如果要测试可以开一个服务器抛出来。
不过再省事一点,安装live server这个vscode插件

这回就行啦~
发布到npm上#
如果只是用在我们的项目中,那么到上面就完事了。
但是如果你想把你这个包分享出去,那么自然就需要接触到npm。
我们来切换之前的指令
wasm-pack build --target bundler这样它就以一个包的形式打包出来了
将包发布到本地#
然后cd到pkg文件夹中
npm link 使用npm link可以在本地使用一个未发布的包,一般就是用来在发布前测试是否正常的。
这个包现在就已经link到我们本机的npm包里了。
什么?你的电脑里没有配置Nodejs和npm?
那么请出门左转:Getting started | npm Docs
现在我们来创建前端环境
cd ../
mkdir test_wasm
cd test_wasm
npm init --yes
npm link wasm_demo现在我们已经有可以使用包的环境了

接下来就是配置可以编译然后运行在浏览器中的
配置Vite环境#
教程中使用的是webpack[7]三件套,我这里就不跟着它来了,咱们直接vite[8]走起。
pnpm create vite我这里的包管理工具是pnpm,如果你没安装,可以直接用npm即可。
然后框架咱们直接选vanilla[9] ,只是用来当做服务器和编译工具。
然后进入我们的包中,把wasm的包link过来。
npm link wasm_demo 这样就配置完了
引入到vite项目中#
然后我们来改下代码,进入到main.ts中。
import './style.css'
const containerStyle = `width: 100px; height: 100px; background-color: #fff;`
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
<div id="container" style="${containerStyle}">
</div>
`
const container = document.querySelector<HTMLDivElement>('#container')!;
container.addEventListener('click', () => {
const init = import("wasm_demo");
init.then(res => {
res.greet("hi i am running wasm in browser!");
})
})然后我们pnpm run dev试下
不过并没有成功,报错了

也就是当前ES module并不支持直接使用wasm,所以我们需要配置vite-plugin-wasm[10]插件(如果有需要兼容低版本浏览器,还需要vite-plugin-top-level-await配合)
pnpm add vite-plugin-wasm -D然后我们来配置下vite.config.ts
import { defineConfig } from 'vite';
import wasm from "vite-plugin-wasm";
export default defineConfig({
plugins: [
wasm()
]
}) 但是依旧是在重复同样的错误,然后我发现是不符合vite的写法[11],所以main.ts文件中改成了
import './style.css'
import init from 'wasm_demo/wasm_demo_bg.wasm?init';
const containerStyle = `width: 100px; height: 100px; background-color: #fff;`
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
<div id="container" style="${containerStyle}">
</div>
`
const container = document.querySelector<HTMLDivElement>('#container')!;
container.addEventListener('click', () => {
init({}).then(res => {
console.log(res);
}).catch(e => {
console.log(e);
})
}) 但是这回还是报错了

然后只能又到github上面的找,这回应该不是vite这方面的问题了
果然在wasm-bindgen中找到相同的issue
这时我发现可能是因为打包是用的bundler,虽然这是用来搭配bundler的,但是可能和vite不搭。
所以我改成--target web重新编译一次
果然,不再报原来的错了,但是报了另一个错误

这个报错问题应该是webAssesmbly实例没有初始化的问题,当请求的数据返回时实际上还有个default的方法

我们再来尝试下,并加上异步await等待初始化
import './style.css'
const containerStyle = `width: 100px; height: 100px; background-color: #fff;`
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
<div id="container" style="${containerStyle}">
</div>
`
const container = document.querySelector<HTMLDivElement>('#container')!;
container.addEventListener('click', () => {
import('wasm_demo').then(async js => {
console.log(js);
await js.default();
js.greet('hi');
})
})再来运行下

这回总算可以了。。。。。
为什么--target web可以正常运行被使用呢?因为出口的js文件并没有涉及wasm,也就是说这个文件相当于一个普通的js文件,而--target bundler编译的输出内容是涉及到wasm文件的,所以并不能作为单独的js文件引入。
我这里是直接绕开这个问题,而不是去解决它。
至于--target bundler为什么不行,如果有知道的大佬麻烦评论区回答一下,不胜感激~
最后#
wasm-pack的官方文档:
参考#
- ^install https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_wasm#wasm-pack
- ^wasm-pack https://github.com/rustwasm/wasm-pack
- ^start-write-rust https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_wasm#lets_write_some_rust
- ^config https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_wasm#compiling_our_code_to_webassembly
- ^build-pkg https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_wasm#building_the_package
- ^publish_to_npm https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_wasm#making_our_package_available_to_npm
- ^webpack https://webpack.js.org/
- ^vite https://vitejs.dev/
- ^vanillajs https://vanilla.js.org/
- ^vite-plugin-wasm https://www.npmjs.com/package/vite-plugin-wasm
- ^vite-webAssembly https://vitejs.dev/guide/features.html#webassembly
编辑于 2023-01-27 19:13・IP 属地广东
