Skip to content

WebAssembly

是基于堆栈的虚拟机的二进制指令格式。Wasm 被设计为编程语言的可移植编译目标,支持客户端和服务器应用程序在 Web 上部署。官网:https://webassembly.org

TypeScript

ts -> wasm

安装一个转换工具:AssemblyScript 使用 Binaryen 将 TypeScript 的变体(基本上是带有类型的 JavaScript)编译为 WebAssembly。它生成精益而平均的 WebAssembly 模块,同时只需安装 npm。

bash
npm i assemblyscript

同时在package.json中添加脚本:

  • asc :编译 WebAssembly
  • 指定源文件:src/assembly/index.ts(这里先写一个简单的ts文件测试下,就不贴代码了)
  • --target release :指定目标为 release
json
{
  "scripts": {
    "ascbuild:release": "asc src/assembly/index.ts --target release"
  }
}

同时还需要添加asconfig.json文件:

  • targets.release:表示一个名为 release 的编译目标,通常用于生产环境,会开启优化
  • outFile:编译后的 wasm 二进制文件
  • textFile:编译后的 wat 文本格式
  • sourceMap:是否生成源映射文件
  • options.bindings:这个选项控制是否生成 JavaScript 绑定文件
    • esm:生成 es 模块
    • es5:传统 IIFE 格式(较少用)
    • false:不生成绑定文件
json
{
  "targets": {
    "release": {
      "outFile": "build/assembly/index.wasm",
      "textFile": "build/assembly/index.wat",
      "sourceMap": true
    }
  },
  "options": {
    "bindings": "esm"
  }
}

至此:执行npm run ascbuild:release即可编译出 wasm 文件。

use wasm by ts in html

然后再wasm文件同级下添加一个release.js文件,用来作为加载wasm文件的入口:

  • 需要注意的点:在编译之前的ts文件中,方法要用function,用箭头函数在index.html当中识别不出来函数
  • imports环境当中要补一个abort

语法说明:await (async url => { ... })(new URL('index.wasm', import.meta.url));

  • 这是一个立即调用函数
  • 将 URL 对象作为参数传递给异步函数
  • 异步函数内部通过 url 参数访问这个 URL 对象

补充:为什么要使用立即调用函数?

  • 作用域隔离:url 只在这个函数作用域内可用,不会污染全局作用域
  • 模块化和封装性:立即执行函数将整个初始化逻辑封装在一起,提高代码内聚
  • 控制执行时机:
    • URL 创建和模块加载是原子操作
    • 在模块导入时立即开始执行,而不是依赖外部变量
    • 避免了可能出现的时序问题
  • 函数式编程
  • 避免变量提升问题
  • 性能:立即执行函数在模块加载时一次性完成所有工作,避免了额外的变量声明和可能的重复计算
js
async function instantiate(module, imports = {}) {
  const {exports} = await WebAssembly.instantiate(module, imports);
  return exports;
};


/**
 * 需要做用户检测(分浏览器和node环境)
 * 浏览器环境:WebAssembly.compileStreaming
 * node环境:WebAssembly.compile
 * */

const imports = {
  env: {
    memory: new WebAssembly.Memory({initial: 256}), // 通用的函数占位符
    abort: () => {
    }
  }
};

export const {
  add, memory
} = await (async url => instantiate(await (async () => {
  const isNode = typeof process !== 'undefined' && process.version !== null;
  console.log('isNode =====', isNode);
  if (isNode) {
    const fs = await import('node:fs/promises');
    return await globalThis.WebAssembly.compile(await fs.readFile(url));
  } else {
    return await globalThis.WebAssembly.compileStreaming(globalThis.fetch(url));
  }
})(), imports))(new URL('index.wasm', import.meta.url));

最后在html当中引入这个js文件进行使用

html

<script type="module">
    import {add, memory} from './release.js';

    console.log('add', add, add(10, 20));
    console.log('memory', memory);
</script>

安装npm i http-server,执行命令http-server -p 8080

性能分析

利用performance API分析wasm性能,同时把编译前的ts文件改回js放在html当中进行分析性能进行比较

js
performance.mark('wasm_start');
console.log('add', add(10, 20));
performance.mark('wasm_end');
performance.measure('wasm_time', 'wasm_start', 'wasm_end');
console.log('performance', performance.getEntriesByName('wasm_time'));

By Modify.