WebAssembly(或Wasm)在今天已经并不陌生了,它设计的主要目标是在Web上运行高性能程序,弥补JavaScript的不足。这篇文章介绍如何将一个简单的C文件编译为wasm模块,并在Webpack中使用。
环境安装
C/C++的编译工具为Emscripten,可以参考MDN上的这篇文章安装。
编译wasm模块
这里以只有一个add函数的C文件做简单示例:
#include <emscripten.h>
EMSCRIPTEN_KEEPALIVE
int add(int a, int b)
{
return a + b;
}
EMSCRIPTEN_KEEPALIVE用来标记需要导出的函数。
接下来我们运行emcc add.c -o add.js,它将编译生成 add.wasm 与 add.js 两个文件。add.js 中包含了如何引入wasm文件并运行的一套代码,它是可以直接在html中引入或者在node中运行的。我们暂时不管这个JS文件,来看怎么使用生成的wasm模块。
在JS中引入wasm模块
在wasm文件目录下创建一个html文件,内容如下:
<html lang="en">
<head>
<title>Wasm Example</title>
</head>
<body>
<script>
async function run() {
const fetchPromise = fetch('add.wasm');
const { instance } = await WebAssembly.instantiateStreaming(fetchPromise);
console.log(instance.exports.add(1, 2))
}
run()
</script>
</body>
在浏览器中运行即可看到控制台中打印内容3,这说明我们的wasm模块已经加载并运行成功了。
const fetchPromise = fetch("add.wasm"); // 加载wasm文件
const { instance } = await WebAssembly.instantiateStreaming(fetchPromise); // 编译同时实例化
console.log(instance.exports.add(1, 2)); // 所有导出的函数都在 instance.exports 对象中
关于如何高效加载wasm模块可以参考Web Dev的这篇文章。使用wasm需要经过下载,编译,实例化3个步骤,在编写代码时需要注意使用异步API避免阻塞主进程以及使用streaming接口进行优化。
在Webpack中使用wasm模块
Webpack5中新增了asyncWebAssembly和syncWebAssembly两个实验属性用于支持wasm模块。
我们参考官网搭建一个简单的webpack项目,安装webpack与webpack-cli,并添加webpack.config.js文件如下:
const path = require("path");
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
filename: "main.js",
path: path.resolve(__dirname, "dist"),
},
devtool: false,
experiments: {
asyncWebAssembly: true,
syncWebAssembly: true,
},
};
现在我们即可通过下面这样简单的方式来使用wasm模块了:
import { add } from "./wasm/add.wasm";
console.log("add", add(100, 1));
查看webpack build后的结果,会发现除了生成 main.js,还会有一个wasm文件,这个文件即是之前的 add.wasm。
在执行wasm的方法前,webpack会先检测wasm模块是否加载和初始化完成,与我们之前的方法相似。
More
到这里,我们已经可以编译C文件为wasm,并知道如何在JS或Webpack中使用了。
其实Emscripten还有很多非常有用的配置,如emcc add.c -s ENVIRONMENT=web,worker可以指定运行环境,emcc add.c -pthread可以生成worker文件,让wasm的代码在web worker中运行。有兴趣的同学可以继续探索~