相关文章推荐
笑点低的帽子  ·  'int' object has no ...·  1 月前    · 
痴情的帽子  ·  SQLAlchemy ...·  1 年前    · 
不羁的生姜  ·  在 Azure Boards ...·  1 年前    · 
调皮的小蝌蚪  ·  java - Getting : ...·  1 年前    · 
正直的椰子  ·  javascript - ...·  1 年前    · 

笔者近两年的工作中接触了一些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

    功能不太依赖平台的全局变量 函数的可以考虑(不然你就要自己实现或者通过运行时传入)

    分类:
    前端
  •