笔者近两年的工作中接触了一些WebAssembly相关的工作,其中包括像谷歌开源的项目Perfetto(c++ wasm),使用golang wasm实现的一个文件解析处理的能力,以及ffmpeg这种第三方包(已经打包好了wasm,不需要自己去写wasm),相对而言比较杂,没有比较系统的梳理和学习,这里用最近接触的AssemblyScript的上手过程记录一下自己接触wasm的一些感受。
什么是WebAssembly
WebAssembly(也称为 WASM),是一种可在 Web 中运行的全新语言格式,同时兼具体积小、性能高、可移植性强等特点,在底层上类似 Web 中的 JavaScript,在 2019 年 12 月 WebAssembly 被 W3C 接受为第四种标准。
为什么说在底层上类似 JavaScript,主要有以下几个理由:
和 JavaScript 在同一个层次执行:JS Engine,如 Chrome 的 V8
和 JavaScript 一样可以操作各种 Web API
同时 WASM 也可以运行在 Node.js 或其他 WASM Runtime 中。
WebAssembly 是一种运行在现代网络浏览器中的新型代码,并且提供新的性能特性和效果。它设计的目的不是为了手写代码而是为诸如 C、C++和 Rust 等低级源语言提供一个高效的编译目标。
WebAssembly的目标
快速、高效、可移植——通过利用
常见的硬件能力
,WebAssembly 代码在不同平台上能够以接近本地速度运行。
可读、可调试——WebAssembly 是一门低阶语言,但是它有确实有一种人类可读的文本格式(其标准即将得到最终版本),这允许通过手工来写代码,看代码以及调试代码。
保持安全——WebAssembly 被限制运行在一个安全的沙箱执行环境中。像其他网络代码一样,它遵循浏览器的同源策略和授权策略。
不破坏网络——WebAssembly 的设计原则是与其他网络技术和谐共处并保持向后兼容。
WASM的开发工具
因为二进制和文本格式都不适合编码,所以不适合将 WASM 作为一门可正常开发的语言,我们需要一些工具来进行体验,简单罗列了一下市面上的一些相关的开发工作
Emscripten
将C/C++ 代码跑在浏览器,编译成WebAssembly
AssemblyScript
是一种为WebAssembly而设计的语言,语法高度类似TypeScript,可以使用
Binaryen
将其编译成 WebAssembly
Yew 一个设计先进的
Rust
框架,目的是使用
WebAssembly
来创建多线程的前端 web 应用
由于篇幅优先,笔者在本篇blog中只会选择其中的AssemblyScript来做个简单的上手体验介绍,其他工具待日后笔者有更多尝试之后再分享。
通过其他语言上手wasm
上手一门新的编程语言的成本
对前端开发同学最熟悉的是ts js,上手其他语言,例如c/c++ golang rust显然还是有一定的学习成本
前端同学接触的场景大部分场景在web,小部分在服务端nodejs,从熟悉的场景上手会相对容易理解
多种开发语言之间切换
如果没有对应的同学来单独写wasm这部分的话,那就是相当于需要我们前端开发来写wasm到前端场景引入使用wasm的整个过程,这个过程中避免不了会切换语言。像笔者的话如果一段时间写其他语言之后重新写ts也会突然之间感到陌生,毕竟开发语言这种东西都是"无他,但手熟尔"。
工具链之间的差异
前端开发: vscode webstorm chrome webpack npm,这些对我们前端开发是最熟悉的东西,假如我们前端自己开发wasm,以golang为例,就需要接触go的各种生态,这个对我们而言也是一种成本。
其他编程语言: 有自己的一套 和前端工程很难整合到一起(总体流程为 源码 => wasm => 前端引入)
以perfetto项目为例,该项目涉及到了c++还有一堆工具,UI部分其实只需要一个wasm文件,一堆不熟悉的工具明显会给我们前端开发上手项目带来不少的麻烦。
Difference with TypeScript
AssemblyScript是偏底层的,不像js是一个运行时的语言,一个特点就是要明确实际执行的类型,所以在ts中的一些类型处理在这里就不适用了
不允许union type (string | number)
不允许 any or undefined
对象属性必须要严格类型,不能后续再添加属性(no optional)
所有模块都是都在沙箱中执行,无法直接访问任何外部API,包括各种DOM操作
需要手动import和export和外部共享的功能
不支持闭包
AssemblyScript的开发流程整体还是对我们前端开发者挺友好的,我们来尝试一下
pnpm install --save @assemblyscript/loader
pnpm install --save-dev assemblyscript
pnpx asinit .
整体的项目结构就是这样(下面这个截图笔者已经新增过一部分的文件了,但是总体的结构和初始化出来的项目结构是一样的)
assembly目录编写模块代码
我们可以简单写个加法的函数来试一下,例如这样
数据绑定交换
单独的WebAssembly还不能和宿主之间传输高级别的数据类型(string array obj),因此目前需要策略来与宿主/JavaScript交换这些数据结构。
通过asconfig.json配置bindings类型,实现生成上述绑定策略,从而实现交换策略
ESM Bindings
绑定全局变量
js globals全局的变量而且AssemblyScript标准库已经提供的可以直接通过env的命名空间来绑定, @external
来绑定
@external("env", "console.log")
declare function consoleLog(s: string): void
绑定js文件
assembly/utils.ts
@external('./utils.js', 'loop')
export declare function loopArray(a: i32): void;
index.html
import * as utils from "./utils.js"
const { instance: { exports } } = await WebAssembly.instantiateStreaming(fetch('./build/release.wasm'), {
......,
'./utils.js': utils
exports.main();
exports.sum(11, 12);
exports.save("88");
exports.loop(5);
utils.js
export function loop(param) {
for (let i = 0; i < param; i++) {
console.log(i);
绑定自定义模块
可以通过以下方式从Node依赖项导入自定义函数:
@external("othermodule", "myFunction")
declare function myFunction(...): ...
Raw Bindings
自己实现整个实例化wasm,胶水代码,绑定模块的流程
社区上有一些包封装了胶水代码,可以直接import, 例如asdom
Debug
以chrome devtools为例,可以通过开启实验特效的配置来断点调试
语法对前端开发者友好,同一份代码只要稍加调整就可以被tsc编译
有静态类型编译语言的优势
可以和浏览器 devtools 配合做debug调试(DWARF支持)
胶水代码相对简单
整体开发体验适合前端开发者,编译器、工具、库都在npm上面可用
学习曲线小
所有代码都是纯手撸,无法直接使用第三方的ts/js库(间接使用从外部引入 绑定 胶水代码)
标准库的能力也是比较有限
无法支持直接调用js全局的一些方法(go 语言有wasm相关的标准库,例如调用dom的方法,js.Global().Get("document").Call("getElementById",i[0].String()).Get("value").String()),得通过一堆胶水代码来实现
部分高级类型不支持(比如reg)
社区和生态系统比较年轻,还有很长的路要走,没有太多生产上的应用
什么情况推荐
想浅尝下wasm但是不想花过多时间在学习一门新语言
把js/ts代码或者相关依赖移植wasm
功能不太依赖平台的全局变量 函数的可以考虑(不然你就要自己实现或者通过运行时传入)
- 321
-
敲敲敲敲暴你脑袋
JavaScript
three.js