Using Deno's First-class Wasm Support
To reach a broader audience, this article has been translated from Japanese.
You can find the original version here.
Introduction
#In the November release of Deno 2.1, first-class Wasm support was added.
Deno 2.1: Wasm Imports and other enhancements
Previously in Deno, it was possible to load and execute Wasm using Web standard APIs, but from version 2.1 onwards, it can be used with import
just like regular modules.
Loading and Executing Wasm: The Traditional Method
#Below is an example of instantiating a WebAssembly called add.wasm
placed locally and using the exposed function (add
).
const wasmInstance = await WebAssembly.instantiateStreaming(
fetch(new URL("./add.wasm", import.meta.url)));
const { add } = wasmInstance.instance.exports as { add: (a: number, b: number) => number };
console.log(add(1, 2));
(This is also to prevent TypeScript type errors, but) it's quite a lengthy code. First, it instantiates with the WebAssembly API, then retrieves the exported function (in this case add
) from the instance to make it usable.
Execution uses the --allow-read
permission[1].
$ deno --allow-read main.ts
3
The above add.wasm
is converted from the following wat
file.
(module
(func (export "add") (param i32 i32) (result i32)
local.get 0
local.get 1
i32.add))
By installing WABT (WebAssembly Binary Toolkit) and using the wat2wasm
command, you can convert this wat
file to wasm format.
By using the wat2wasm
demo site, you can download the converted wasm file without introducing WABT locally.
For the Web standard API on loading and executing Wasm, please refer to the following document.
Loading and running WebAssembly code - WebAssembly | MDN
Usage of Wasm using Web standard APIs in Deno is also introduced in the following (though it's quite an old article).
Loading and Executing Wasm: Import
#From Deno 2.1 onwards, Wasm can be loaded and executed using import
.
import { add } from "./add.wasm";
console.log(add(1, 2));
It has become very simple. Execution is also simple.
deno main.ts
Deno performs type checking even on Wasm modules.
Trying It from a Web Worker
#In a previous article, I introduced an example of executing Wasm code in a distributed fashion using Web Workers. At that time, the method was to pre-compile the Wasm in the main process, pass it to the Worker, and have the Worker instantiate and execute it[2]. Now, since the Worker code can directly import
, it has become easier.
The code on the Worker side. Using the add
module, it performs addition processing based on parameters extracted from messages, and sends the result.
import { add } from "./add.wasm";
self.onmessage = e => {
const result = add(e.data.a, e.data.b); // Execute add
postMessage(self.name + ": " + result); // Send worker name and result
};
The main code. It just creates multiple workers and writes the processing when messages are received from the workers.
const worker1 = createWorker("worker1");
const worker2 = createWorker("worker2");
handleWorkerMessage(worker1); // Register message receiving handler for worker1
handleWorkerMessage(worker2); // Register message receiving handler for worker2
worker1.postMessage({ a: 1, b: 2 }); // Send message to worker1
worker2.postMessage({ a: 3, b: 4 }); // Send message to worker2
function createWorker(name: string) {
const worker = new Worker(new URL("./worker.ts", import.meta.url).href, {
type: "module",
name: name
});
return worker;
}
function handleWorkerMessage(worker: Worker) {
worker.onmessage = e => {
console.log(e.data);
};
}
Execution result. We can obtain the processing results from each Worker using Wasm.
$ deno --allow-read main.ts
worker1: 3
worker2: 7
Conclusion
#That's a quick experiment with Deno's first-class Wasm support. Wasm is incorporated into Deno's module graph, and due to Deno's analysis and caching, it seems to be usable at high speed.
As expected with first-class support, there are no glue code-like parts at all. The barriers to using Wasm have significantly lowered.