image.png

WebAssembly是什么?

下面是来自官方的定义:

WebAssembly or wasm is a new portable, size- and load-time-efficient format suitable for compilation to the web.

翻译成中文的话,WebAssembly(wasm)就是一个可移植、体积小、加载快并且兼容 Web 的全新格式。
实际上,WebAssembly 是一种新的编码方式,可以在现代的网络浏览器中运行 - 它是一种低级的类汇编语言,具有紧凑的二进制格式,可以接近原生的性能在Web中运行。目前可以使用 C、C++、Rust、Go、Java、C#等编译器来创建 wasm 模块。该模块以二进制的格式发送到浏览器,并在专有虚拟机上执行,与 JavaScript 虚拟机共享内存和线程等资源。
经过编译器编译之后的二进制代码,无需经过 Parser 和 ByteCode Compiler 这两步,比 asm.js 更快。WebAssembly 强制使用静态类型,在语法上完全脱离 JavaScript,同时具有沙盒化的执行环境,安全性更好。

WebAssembly的目标?

  • 快速、高效、可移植——通过利用常见的硬件能力,WebAssembly 代码在不同平台上能够以接近本地速度运行。
  • 可读、可调试——WebAssembly 是一门低阶语言,但是它有确实有一种人类可读的文本格式(其标准即将得到最终版本),这允许通过手工来写代码,看代码以及调试代码。
  • 保持安全——WebAssembly 被限制运行在一个安全的沙箱执行环境中。像其他网络代码一样,它遵循浏览器的同源策略和授权策略。
  • 不破坏网络——WebAssembly 的设计原则是与其他网络技术和谐共处并保持向后兼容。

    WebAssembly的关键概念?

  • 模块:表示一个已经被浏览器编译为可执行机器码的 WebAssembly 二进制代码。一个模块是无状态的,并且像一个二进制大对象(Blob)一样能够被缓存到 IndexedDB中或者在 windows 和 workers 之间进行共享(通过postMessage() (en-US)函数)。一个模块能够像一个 ES2015 的模块一样声明导入和导出。

  • 内存:ArrayBuffer,大小可变。本质上是连续的字节数组,WebAssembly 的低级内存存取指令可以对它进行读写操作。
  • 表格:带类型数组,大小可变。表格中的项存储了不能作为原始字节存储在内存里的对象的引用(为了安全和可移植性的原因)。
  • 实例:一个模块及其在运行时使用的所有状态,包括内存、表格和一系列导入值。一个实例就像一个已经被加载到一个拥有一组特定导入的特定的全局变量的 ES2015 模块。

一个 WebAssembly 实例能够调用普通 JavaScript 函数暴露出来的代码。通过把 JavaScript 函数导入到 WebAssembly 实例中,任意的 JavaScript 函数都能被 WebAssembly 代码同步调用。WebAssembly 代码的基本单元被称作一个模块,并且 WebAssembly 的模块在很多方面都和 ES2015 的模块是等价的。
因为 JavaScript 能够完全控制 WebAssembly 代码如何下载、编译运行,所以,JavaScript 开发者甚至可以把 WebAssembly 想象成一个高效地生成高性能函数的 JavaScript 特性。

如何在应用中使用WebAssembly?

核心点:

  • 代码的二进制格式
  • 加载运行该二进制代码的 API

着手点:

  • 使用 Emscripten 移植一个 C/C++应用程序。
  • 直接在汇编层,编写或生成 WebAssembly 代码。
  • 编写 Rust 程序,将 WebAssembly 作为它的输出。
  • 使用AssemblyScript,它类似于 TypeScript 并且可编译成二进制 WebAssmebly 代码

编译 Rust 为 WebAssembly

两种用例:

  • 构建应用的组成部分 —— 在现存的 JavaScript 前端中使用 Rust。
  • 构建完整应用 —— 整个 Web 应用都基于 Rust 开发!

    在现存的 JavaScript 前端中使用 Rust

    1.安装Rust

    Install Rust 页面并跟随指示安装 Rust。安装一个名为“rustup”的工具,这个工具能让你管理多个不同版本的 RustRustup 会安装 Rust 的编译器 rustc、Rust 的包管理工具 cargo、Rust 的标准库 rust-std 以及一些有用的文档 rust-docs。

    2.安装wasm-pack

    1. cargo install wasm-pack

    3.安装Node.js并获取npm账户

    npm signup page 注册 npm 账户,并填写表格。
    接下来,在命令行中运行 npm adduser:
    1. > npm adduser
    2. Username: yournpmusername
    3. Password:
    4. Email: (this IS public) you@example.com

你需要完善你的用户名,密码和邮箱。如果成功了,将会看到: :::tips Logged in as yournpmusername on https://registry.npmjs.org/. :::

4.构建WebAssembly npm包

首先创建一个新的 Rust 包

  1. cargo new --lib hello-wasm
  2. Created library `hello-wasm` project

:::tips +— Cargo.toml
+— src
+— lib.rs ::: 这里有一个 Cargo.toml 文件,这是配置构建的方式。Cargo 的用法和 npm 的 package.json类似。

在lib.rs中重写Rust代码

  1. //使用外部的 wasm-bindgen库 提供 JavaScript 和 Rust 类型之间的桥梁,
  2. //允许 JavaScript 使用字符串调用 Rust API,或调用 Rust 函数来捕获 JavaScript 异常
  3. extern crate wasm_bindgen;
  4. //引入 wasm_bindgen::prelude 的全部模块
  5. use wasm_bindgen::prelude::*;
  6. //在 Rust 中调用来自 JavaScript 的外部函数
  7. #[wasm_bindgen]
  8. extern {
  9. pub fn alert(s: &str);
  10. }
  11. //能够在 JavaScript 中调用的 Rust 函数
  12. #[wasm_bindgen]
  13. pub fn greet(name: &str) {
  14. alert(&format!("Hello, {}!", name));
  15. }

greet 函数需要一个字符串类型参数(rust中字符串类型定义为 &str)。它调用了前面在 extern 块中引入的 alert 函数,并且传递一个让我们串联字符串的 format! 宏的调用。
format! 在这里有两个参数,一个格式化字符串和一个要填入的变量。格式化字符串是 “Hello, {}!” 部分。它可以包含一个或多个 {},变量将会被填入其中。传递的变量是 name,也就是这个函数的参数。所以当我们调用 greet(“Steve”)时我们就能看到 “Hello, Steve!”
这个传递到了 alert(),所以当我们调用这个函数时,我们应该能看到弹出了一个带有 “Hello, Steve!” 的消息框。

把代码编译到WebAssembly
首先我们需要配置 Cargo.toml。打开这个文件,将内容改为如下所示:

  1. [package]
  2. name = "hello-wasm"
  3. version = "0.1.0"
  4. authors = ["Your Name <you@example.com>"]
  5. description = "A sample project with wasm-pack"
  6. license = "MIT/Apache-2.0"
  7. repository = "https://github.com/yourgithubusername/hello-wasm"
  8. [lib]
  9. crate-type = ["cdylib"]
  10. [dependencies]
  11. wasm-bindgen = "0.2"

💡上面的信息需要改为自己的仓库,同时 Cargo 需要通过 git 来完善 authors 部分。

然后进行构建,命令行输入:

  1. wasm-pack build --scope mynpmusername

简单来说,wasm-pack build 将做以下几件事:

  1. 将 Rust 代码编译成 WebAssembly。
  2. 在编译好的 WebAssembly 代码基础上运行 wasm-bindgen,生成一个 JavaScript 文件将 WebAssembly 文件包装成一个模块以便 npm 能够识别它。
  3. 创建一个 pkg 文件夹并将 JavaScript 文件和生成的 WebAssembly 代码移到其中。
  4. 读取你的 Cargo.toml 并生成相应的 package.json。
  5. 复制你的 README.md (如果有的话) 到文件夹中。

运行后的文件目录如下:
image.png

5.将包发布到npm

  1. cd pkg
  2. npm publish --access=public

然后就可以在项目中使用这个由Rust语言写的npm包了

6.在网站上使用我们的包

创建一个新目录site:

  1. mkdir site
  2. cd site

创建一个新文件 package.json,然后输入如下代码:

  1. {
  2. "scripts": {
  3. "serve": "webpack-dev-server"
  4. },
  5. "dependencies": {
  6. "@coconut-killer/hello-wasm": "^0.1.0"
  7. },
  8. "devDependencies": {
  9. "webpack": "^4.25.1",
  10. "webpack-cli": "^3.1.2",
  11. "webpack-dev-server": "^3.1.10"
  12. }
  13. }

接下来,我们配置 Webpack。创建 webpack.config.js 并输入:

  1. const path = require('path');
  2. module.exports = {
  3. entry: "./index.js",
  4. output: {
  5. path: path.resolve(__dirname, "dist"),
  6. filename: "index.js",
  7. },
  8. mode: "development"
  9. };

创建一个index.html并写入如下内容:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>hello-wasm example</title>
  6. </head>
  7. <body>
  8. <script src="./index.js"></script>
  9. </body>
  10. </html>
  1. const js = import("./node_modules/@coconut-killer/hello-wasm/hello_wasm.js");
  2. js.then((js) => {
  3. js.greet("WebAssembly");
  4. });

运行

  1. npm install
  2. npm run serve

相关资源