大家好,我是 世奇,笔名 ConardLi

大家好,我是 ConardLi,不知道有没有小伙伴关注今年的 Google 开发者大会,今年的大会在 11.16 号开始。

Google 开发者大会 (Google Developer Summit) 是 Google 面向开发者和科技爱好者展示最新产品和平台的年度盛会。2021 年,Google 开发者大会以 “Develop as One” 为主题,携手开发者与合作伙伴共创机遇,共谋发展!

今年在 Web 方面,有 DevtoolsPWA、核心网页指标、CMS、隐私沙盒等等:

其中隐私沙盒的最新进展我在前几天的文章里已经介绍过,没看过的的小伙伴可以看这里:

三方 Cookie 替代品 — 隐私沙盒的最新进展

今天,我们来看看另一个我比较感兴趣的议题:WebAssembly

本次大会上的分享人是来自 GoogleWebAssembly 开发技术推广工程师 Ingvar Stepanyan

什么是 WebAssembly

WebAssembly 是一种二进制指令格式,简称为 Wsam,它可以运行在适用于堆栈的虚拟机上。

WebAssembly 存在的意义就是成为编程语言的可移植编译目标,让在 Web 上部署客户端和服务端应用成为可能。

WebAssembly 可以为我们带来什么

可移植性

如果你的网站现在想用一个能力,但是这个能力还没有被任何的 JavaScript 库实现,但是在其他编程领域里已经有了解决方案。

这时,你就可以借助 WebAssembly 将所需要的库编译为可以在 Web 上运行的二进制格式,在某些情况下甚至你还可以编译整个应用。一旦编译到 WebAssembly ,代码就可以在任何装有网络浏览器的设备上运行了,例如 PC、手机、平板电脑等等。

安全性

WebAssembly 需要在沙盒中运行,在沙盒中,除了初始化时程序主动提供给它的内容,它无法访问其他主机的内存和函数。

这意味着, WebAssembly ,在你没有给它下发命令的情况下,永远不会损坏你的主机进程内存,也无法随意访问文件系统或与其他设备通信。这就让它与运行在虚拟机和容器中的应用有相同的优势

高效

JavaScript 等人类可读的语言相比, WebAssembly 的字节码可以用更少的字节表示相同的指令,并且在 WebAssembly 模块依然处于下载期间就可以被编译。

因为编译器已经事先完成了优化工作,在 WebAssembly 中可以更轻松的获取到可预测的性能

WebAssembly 的开源应用

Squoosh

Squoosh 是一个超强的图像压缩Web应用程序,可让你深入研究各种图像压缩器提供的高级选项,例如比较视觉差异和文件大小以及下载优化后的图片版本。

https://squoosh.app/

它借助 WebAssembly 纳入了非常多的图片编解码器,这些编解码器可能来源于 C、C++、Rust 等等,在浏览器的标签页旧可以直接执行它们,不需要服务端做任何额外的处理。这让 Squoosh 可以处理很多旧的图片格式(例如 JPEG、PNG),也可以处理很多新的图片格式(例如 AVIF、JPEG-XL)。

FFMpeg

FFmpeg 是视频处理最常用的开源软件,它功能强大,用途广泛,大量用于视频网站和商业软件(比如 YoutubeiTunes),也是许多音频和视频格式的标准编码/解码实现。

借助 WebAssembly 的能力,它现在有了一个 Web 版本:FFMPEG.WASM,让你可以在浏览器里处理视频,你可以到下面这个网址上去体验一下:

https://ffmpegwasm.netlify.app/

MediaPipe

MediaPipe 是一款由 Google 开发并开源的数据流处理机器学习应用开发框架。它是一个基于图的数据处理管线,用于构建使用了多种形式的数据源,如视频、音频、传感器数据以及任何时间序列数据。

https://google.github.io/mediapipe/

它支持多个平台,融入了 WebAssemblyWebGL 的强大能力,可以通过 JavaScriptWeb 上提供机器学习模型。

WebAssembly 用法

如果你现在有一个想要移植到 WebAssembly 的库,该怎么用呢?

实际上, WebAssembly 的官网 webassembly.org 是一个很好的开始,上面对于各种语言的教程都是比较全的,在这些教程里你可以学到怎么去用相应的工具链,怎么向 WebAssembly 构建代码,以及如何利用到 Web 上,下面我们看几个最常用的工具链。

Emscripten

Emscripten 是一个开源的编译器,可以将 C/C++ 的代码编译成高度优化的 JavaScript 并且高效运行在现代浏览器上面,它推出的时间甚至比 WebAssembly 还要早。

现在,它可以将相同的 C/C++ 代码编译到 WebAssembly ,并提供各种各样的工具和绑定关系帮助你将生成的代码继承到 Web 中。

例如,Emscripten 提供 SDL 实现,可以用于在画布上绘制内容以及播放 Web 中的音频,来转换对 WebGL 的调用。

SDL(简单直接媒体层)是一个跨平台的开源开发库,旨在提供对输入和图形硬件的低级访问,用 C 语言编写,视频播放软件、模拟器和许多流行游戏都使用它。

Embind

不同语言都拥有不同的类型和内存表示法,JavaScript 和 C++ 也不例外,当你编译成 WebAssembly 也是一样的情况,所以仅仅通过编译是无法解决这个问题的。

想要使用这些库中的结果,还需要一些中间层来转换双向传递的值。

Emscripten 中实现这点最简单的方法,是使用一个叫 Embind 的功能,下面是一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
// quick_example.cpp
#include <emscripten/bind.h>

using namespace emscripten;

float lerp(float a, float b, float t) {
return (1 - t) * a + t * b;
}

EMSCRIPTEN_BINDINGS(my_module) {
function("lerp", &lerp);
}

通过 EMSCRIPTEN_BINDINGS 块,就可以以 JavaScript 函数形式声明对外开放的 API,以及转换作为实参传递到 C++ 函数的值或者从 C++ 返回的值。这样一来,你就可以将现有任何的 C++ 库封装到一个对 JavaScript 友好的 API 中。

最后你可以同时编译 API 封装容器和之前构建的依赖项,并传递一个 --bind 参数来启用 Embind

1
emcc --bind -o quick_example.js quick_example.cpp

如果将其编译为 扩展项,它会生成一个 ES6 兼容模块,然后你就可以从 JavaScript 代码导入它,异步初始化这个模块。

1
2
3
4
5
import initModule from './mylib.mjs';

const Module = await initModule();
Module.lerp(1,2,3);

然后你就可以使用之前从 EMSCRIPTEN_BINDINGS 块声明的所有 API。

wasm-bindgen

如果你熟悉 Rust ,就知道它在 WebAssembly 领域的贡献是非常大的。

Rust 提供了 wasm-bindgen 这个工具来支持为任何 Web API 生成绑定关系,以及将你自己的 Rust 函数导出为 JavaScript

感兴趣你可以看一下下面这个在线教程: https://rustwasm.github.io/

教程中有将 Rust 函数导出为 JavaScript 的详细指引,以及一些示例,和 Embind 一样,它也负责在语言之间的双向类型转换,参考下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
extern "C" {
fn alert(s: &str);
}

#[wasm_bindgen]
pub fn greet(name: &str) {
alert(&format!("Hello, {}!", name));
}

当你在一个 extern 块上应用 wasm_bindgen 属性时,就可以导入指定的 API,当你在自己的类型和函数上应用 wasm_bindgen 属性时,系统会导出相应的类型和函数。

在每种情况下,工具链都负责在后台为库生成类型转换, 以及 JavaScript 封装容器,甚至是 TypeScript 定义,声明 API 后,就可以编译库生成一个 ES6 模块。

1
2
3
4
5
6
const rust = import('./pkg');

rust
.then(m => m.greet('World!'))
.catch(console.error);

Emscripten 的示例类似,也需要异步将其初始化一次,然后就可以作为常规的 JavaScript 模块进行调用。

将 JS/TS 编译成 WebAssembly

那么,JavaScript、TypeScript 能不能编译成 WebAssembly 呢?

答案是否定的,因为 JavaScript 是高度动态的语言,而 WebAssembly 属于静态类型语言,不过我们可以借助 AssemblyScript 来帮助我们模拟实现这一点。

AssemblyScript 是一个 TypeScriptWebAssembly 的编译器,你可以到 https://www.assemblyscript.org/ 去了解它的详细用法。

未来

WebAssembly 现在已经处于稳定阶段了,几年前就被所有主流浏览器所支持,但是它仍在不断发展,探索新的能力。

在这些探索中,有一些改进了与 JavaScriptWeb 的集成,有一些缩减了代码体积、,还有一些进一步提升了性能,想了解更多,可以到下面的网址进行查看:

https://webassembly.org/roadmap/

现代浏览器的功能早已不局限在简单的页面呈现,这就是为什么 WebAssembly 会诞生的重要原因之一。为了将沉重的任务性能提升到一个新的水平,在 JavaScript 和机器代码之间搭建了一座桥梁,由此才有了 WebAssembly

让我们期待 WebAssembly 可以在 Web 上带给我们更多的可能性吧~

如果你想加入高质量前端交流群,或者你有任何其他事情想和我交流也可以添加我的个人微信 ConardLi