2021-05-06 WebAssembly
开发环境
操作系统
目前用的操作系统是windows。
Node
开发环境 。
先下载一个Nodejs。
传送门
当前版本: 14.16.1
[图片上传失败...(image-842b47-1620292429591)]
Rust
这个就不介绍怎么安装了,
我的版本是
[图片上传失败...(image-8913b1-1620292429591)]
教程参考
编译成WebAssembly 有两个教程。
官网 mozilla官方教程
还有另外一个是 ,不过这个用的是他们自己写的一个构建工具。
secondstate 出品的SSVM
还有一本书,建议深入的时候读。上面教程都是非常简单的。书比较系统
rustwasm
动手
我们先按官方教程撸一把。
在本教程中,我们将使用 Rust 的 npm 包构建工具 wasm-pack
来构建一个 npm 包。这个包只包含 WebAssembly 和 JavaScript 代码,以便包的用户无需安装 Rust 就能使用。他们甚至不需要知道这里包含 WebAssembly!
安装wasm-pack
要构建我们的包,我们需要一个额外工具 wasm-pack
。它会帮助我们把我们的代码编译成 WebAssembly 并制造出正确的 npm
包。使用下面的命令可以下载并安装它:
$ cargo install wasm-pack
构建我们的 WebAssembly npm 包
万事俱备,来创建一个新的 Rust 包吧。打开你用来存放私人项目的目录,我的目录是 d:/workspace:
$ cargo new --lib hello-wasm
[图片上传失败...(image-eca9bb-1620292429591)]
这里会在名为 hello-wasm
的子目录里创建一个新的库,里面有下一步之前我们所需要的一切:
+-- Cargo.toml
+-- src
+-- lib.rs
首先,我们有一个 Cargo.toml
文件,这是我们配置构建的方式。如果你用过 Bundler 的 Gemfile
或者 npm 的 package.json
,你应该会感到很熟悉。Cargo 的用法和它们类似。
接下来,Cargo 在 src/lib.rs
生成了一些 Rust 代码:
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
我们完全不需要使用这些测试代码,所以继续吧,我们删掉它。
修改Cargo.toml
接下来不太按教程的套路走了,如果还按教程的套路,那么不如直接自己看教程好了。
首先我们需要去修改 Cargo.toml
添加下列内容
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
第一个部分 — [lib]
— 告诉 Rust 为我们的包建立一个 cdylib
版本;官方解释
-
--crate-type=cdylib
,#[crate_type = "cdylib"]
- A dynamic system library will be produced. This is used when compiling a dynamic library to be loaded from another language. This output type will create*.so
files on Linux,*.dylib
files on macOS, and*.dll
files on Windows.
第二个部分是 [dependencies]
部分。在这里我们告诉 Cargo 我们需要依赖哪个版本的 wasm-bindgen
;
[图片上传失败...(image-156bc0-1620292429591)]
我们到crate.io 上看一下,最新版本还是0.2.73
写点RUST代码吧。
让我们在 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));
}
这就是我们这个 Rust 项目的内容。它有三个主要部分,让我们按顺序来讲。
使用 wasm-bindgen
在 Rust 与 JavaScript 之间通信
第一部分看起来像这样:
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
第一行就像在说 “哇 Rust,我们在用一个叫做 wasm_bindgen 的库”。在 Rust 当中,库被称为 “crates”,因为我们使用的是一个外部库,所以有 "extern"。通过 extern crate + crate名称将包内的模块引入当前环境。
在使用 extern 引入包之后,可以用 use 将模块引入到当前作用域。
第三行包括了一个将库中的代码引入到你的代码中的使用命令。在这个情况下,将会引入 wasm_bindgen::prelude
的全部模块。
在我们开始下一节之前,我们将讲一讲 wasm-bindgen
.
wasm-pack
使用 wasm-bindgen
,其它工具,去提供一个连接 JavaScript 和 Rust 的桥。它允许 JavaScript 使用 string 调用 Rust API,或者调用一个 Rust function 去捕获 JavaScript 异常。
我们将在我们的包中使用 wasm-bindgen
的功能。事实上,这是下一节的内容!
在 Rust 中调用来自 JavaScript 的外部函数
接下来的部分看起来像这样:
#[wasm_bindgen]
extern {
pub fn alert(s: &str);
}
在 #[]
中的内容叫做 "属性",并以某种方式改变下面的语句。在这种情况下,下面的语句是一个 extern
,它将告诉 Rust 我们想调用一些外部定义的函数。这个属性告诉我们 "wasm-bindgen 知道如何找到这些函数"。
第三行是用 Rust 写的函数签名。它告诉我们 "alert
函数接受一个叫做 s 的字符串作为参数。"
你可能会疑惑这个函数是什么,你的疑惑可能是正确的:这是the alert
function provided by JavaScript!
编写能够在JavaScript中调用的 Rust 函数
最后一部分是这样的:
#[wasm_bindgen]
pub fn say(name: &str) {
alert(&format!("Hello, {}!", name));
}
我们又看到了 #[wasm_bindgen]
属性。在这里,它并非定义一个 extern
块,而是 fn,
这代表我们希望能够在 JavaScript 中使用这个 Rust 函数。 这和 extern
正相反:我们并非引入函数,而是要把函数给外部世界使用。
这个函数的名字是 say
,它需要一个参数,一个字符串 (写作 &str
)。它调用了我们前面在 extern
块中引入的 alert
函数。它传递了一个让我们串联字符串的 format!
宏的调用。
在这里有两个参数,一个格式化字符串和一个要填入的变量。格式化字符串是 "Hello, {}!"
部分。它可以包含一个或多个 {}
,变量将会被填入其中。传递的变量是 name
,也就是这个函数的参数。所以当我们调用 greet("Steve")
时我们就能看到 "Hello, Steve!"。
这个传递到了 alert()
,所以当我们调用这个函数时,我们应该能看到他谈弹出了一个带有 "Hello, Steve!" 的消息框。
我们的库写完了,是时候构建它了。
构建包
现在我们已经完成了所有配置项,开始构建吧!在命令行输入以下命令:
$ wasm-pack build
-
wasm-pack build
, 构建生成 pkg 文件夹; -
wasm-pack test
用于测试
这个命令将做一系列事情 (这会花一些时间,特别是当你第一次运行 wasm-pack
)。想了解详细情况,查看这篇在 Mozilla Hacks 上的文章。简单来说,wasm-pack build
将做以下几件事:
- 将你的 Rust 代码编译成 WebAssembly。
- 在编译好的 WebAssembly 代码基础上运行
wasm-bindgen
,生成一个 JavaScript 文件将 WebAssembly 文件包装成一个模块以便 npm 能够识别它。 - 创建一个
pkg
文件夹并将 JavaScript 文件和生成的 WebAssembly 代码移到其中。 - 读取你的
Cargo.toml
并生成相应的package.json。
- 复制你的
README.md
(如果有的话) 到文件夹中。
[图片上传失败...(image-438fd-1620292429591)]
[图片上传失败...(image-29f44-1620292429591)]
.......
[图片上传失败...(image-359e3a-1620292429591)]
第一次编译非常非常多东西.....
最后的结果?你在 pkg
文件夹下有了一个 npm 包。
我们看看里面有什么东西
[图片上传失败...(image-4eb720-1620292429591)]
对代码体积的一些说明
如果你检查生成的 WebAssembly 文件体积,它可能有几百 kB。我们没有让 Rust 去压缩生成的代码,从而大大减少生成包的体积。这和本次教程主题无关,但如果你想了解更多,查看 Rust WebAssembly 工作组文档上关于 减少 .wasm 体积 的说明。真的有用的传送门
我们先看看现在编译出来的wasm有多大?
[图片上传失败...(image-522d02-1620292429591)]
然后我们按照教程上的
Cargo.toml里面进行修改
[profile.release]
lto = true
这为 LLVM 提供了更多内联和修剪功能的机会。它不仅会变小,而且还会使它在运行时间更快!缺点是编译需要更长的时间。
告诉LLVM优化尺寸而不是速度
LLVM 的优化通行证经过调整,以在默认情况下提高速度,而不是尺寸。我们可以通过将该部分修改为:
[profile.release]
lto = true
opt-level = 's'
[图片上传失败...(image-654412-1620292429591)]
有效果哦!!
或者,更积极地优化尺寸,进一步的潜在速度成本:
[profile.release]
lto = true
opt-level = 'z'
[图片上传失败...(image-b7c211-1620292429591)]
多了一点....
我们建立一个使用我们包的网站
让我们建立一个使用我们包的网站! 人们通过各种打包工具使用 npm包,在本教程中,我们将使用 webpack
。 它比其他某些打包工具稍微复杂一点,但展示了更实际的用法。
让我们离开pkg
目录,并创建一个新目录site
,尝试以下操作:
创建一个新文件 package.json
,然后输入如下代码:
{
"scripts": {
"serve": "webpack-dev-server"
},
"dependencies": {
"@mynpmusername/hello-wasm": "^0.1.0"
},
"devDependencies": {
"webpack": "^4.25.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10"
}
}
我们没有像教程那样上传到npm中。我们需要对 dependencies 进行修改一下。指向相对地址
- dependencies: 表示生产环境下的依赖管理;无论开发还是生产都会下载使用的依赖
- devDependencies: 表示开发环境下的依赖管理;
{
"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。 创建 webpack.config.js
并输入:
const path = require('path');
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "index.js",
},
mode: "development"
};
现在我们需要一个HTML文件。 创建一个index.html
并写入如下内容:
hello-wasm example
最后,从HTML文件中引用index.js
:
const js = import("../pkg/hello_wasm.js");
js.then(js => {
js.say("WebAssembly");
});
这将从pkg
文件夹导入我们的模块。这不是最佳做法,但这里只做一个演示,因此暂时就这样用。 加载后,它将从该模块调用say
函数,并传入字符串“WebAssembly”参数。注意这里看上去没有什么特别的,但是我们正在调用 Rust 代码! 就JavaScript代码所知,这只是一个普通模块。
我们已经完成了所有的文件! 让我们试一下:
$ npm install
$ npm run serve
[图片上传失败...(image-a4d2b-1620292429591)]
我们看一下。写的wasm加载到了前端了。
[图片上传失败...(image-1019c7-1620292429591)]
打完收工