1. WebAssembly 简介
跨平台性,可以在任何支持 WebAssembly 的平台上运行,包括 Web 浏览器、服务器、移动设备等
高性能,采用了一种紧凑的二进制格式,可以在浏览器中快速加载和解析,从而提高应用程序的性能
安全性,采用了一种沙箱模型,可以隔离运行在其中的代码,从而保护系统免受恶意代码的攻击
可移植性,WebAssembly 的代码可以通过编译器从不同的编程语言中生成,因此可以轻松地将现有的代码转换为 WebAssembly 格式
可扩展性,WebAssembly 支持向后兼容的版本控制,并允许在未来添加新的指令和功能,从而使其具有更好的扩展性
2. 落地案例
autocad https://web.autocad.com/
在线协同设计工具 figma https://www.figma.com/
web 版 photoshop https://photoshop.adobe.com/
web 版 libreoffice https://lab.allotropia.de/wasm/
WebAssembly 实现的虚拟机 v86 https://github.com/copy/v86
在线运行 Go https://goscript.dev/
在线运行 Python https://pyscript.net/
更多使用 WebAssembly 的案例参考 https://madewithwebassembly.com/
3. 常见 WebAssembly 非前端运行时
WebAssembly 运行时是 WebAssembly 代码的运行环境,它负责加载 WebAssembly 模块、创建 WebAssembly 实例、执行 WebAssembly 代码。
- C/C++ 语言实现的 - WasmEdge、wasm3
- Rust 语言实现的 - wasmer、wasmtime
- Go 语言实现的 - WaZero
目前的主流浏览器、高版本的 Nodejs 都是支持 wasm 的。
4. 安装 WasmEdge
由于 Docker 新版本支持 WasmEdge,我在本地也选择使用 WasmEdge 作为 WebAssembly 运行时,以便于容器内保持一致。
- 下载 Wasmedge 二进制文件
前往 https://github.com/WasmEdge/WasmEdge/releases 下载对应平台的二进制文件。
|
|
- 解压 Wasmedge 并安装
|
|
如果需要拷贝到其他目录下,注意保持 bin、include、lib 三个目录的相对位置,否则会出现以下错误:
|
|
- 查看 Wasmedge 版本
|
|
5. 使用 TinyGo 编译 WebAssembly 程序
5.1 使用 TinyGo 的优劣
- 使用 TinyGo 的好处
TinyGo 是 Go 的子集,可以使用 Go 语言编写 WebAssembly 程序。
可以直接在 WasmEdge 上运行,无需 JavaScript 环境。
wasm 文件足够小,几十 KB。
- 使用 TinyGo 的缺点
TinyGo 有些库不能用,比如 net/http 等,具体可参考 https://tinygo.org/docs/reference/lang-support/stdlib/ 。
TinyGo 不支持全部 Go 语言特性,比如 Cgo 等,具体可参考 https://tinygo.org/docs/reference/lang-support/ 。
5.1 Hello, World!
- 新建 main.go 文件
|
|
- 编译代码
|
|
- 使用 WasmEdge 运行
|
|
- 新建 Dockerfile 文件
|
|
- 编译容器镜像
|
|
- 运行容器镜像
|
|
6. 使用 Go 编译 WebAssembly 程序
6.1 使用 Go 的优劣
- 使用 Go 的好处
可以使用 syscall/js 包与 JavaScript 进行交互。
可以使用 Go 语言的全部特性和包。
未来,WebAssembly 运行时支持 GC 后,程序体积、性能可能会得到改善。
- 使用 Go 的缺点
默认需要 JavaScript 环境支持。
体积较大,几 MB、甚至几十 MB。不过,很多祖传项目编译出来也是几十、上百 MB。
6.2 Hello, World!
- 新建 main.go 文件
|
|
- 编译代码
|
|
- 使用 node 运行
注意,nodejs 需要 12 及以上才行。
|
|
- 新建 Dockerfile 文件
|
|
- 编译容器镜像
|
|
- 运行容器镜像
|
|
6.3 使用 syscall/js 实现 Go 与 JavaScript 数据的交互
Go 语言提供了 syscall/js 包,可以用于与 JavaScript 进行交互。
- 在 Go 中调用 JavaScript 函数
通过 syscall/js
包提供的 Global() 函数获取宿主 JavaScript 环境的对象。
|
|
等价于
|
|
- 在 JavaScript 中调用 Go 函数
|
|
js.Global().Set
将对象注册到 JavaScript 环境中,在浏览器前端可以直接调用 myfunc
函数。
- 重写 hello world,在浏览器页面执行 wasm
|
|
- 拷贝 wasm_exec.js wasm_exec.html 文件
Go 编译器自带有一个样例。
|
|
wasm_exec.js 的作用是在浏览器中加载执行 wasm,wasm_exec.html 是一个实例。
- 修改 wasm_exec.html
为了演示 Go 与 JavaScript 的数据交互能力,这里稍微修改一下样例。
|
|
这里的 fetch 应该是编译出来的 wasm 文件,默认是 test.wasm
,根据需要修改为 main.wasm
。
|
|
这里新增一个按钮,用于调用 Go 函数 myfunc。
- 查看效果
本地启一个 http 服务
|
|
访问 http://localhost:8000/wasm_exec.html 页面
点击 Run 按钮,Go 调用前端对象,输出文本、在页面插入文本。
点击 callGoButton,前端调用 Go 函数。
在控制台访问 windows.myfunc_arg0
,获取 Go 在前端对象中设置的值。
7. 总结
本篇主要是在尝试 Go 编写 WebAssembly 的一些方式。如果不进行额外的配置,目前比较快捷的两种方式是:
- 使用 TingyGo 编写,直接在 WasmEdge 上运行
- 使用 Go 编写,需要借助 JS 加载,在 Nodejs 上运行
另外,还提供了 Go 1.19 编写 WebAssembly 与 JavaScript 函数、数据交互的一个示例。
文中相关代码都在 https://github.com/shaowenchen/demo/tree/master/wasm-hello-world 。