Photo by Alessandro Porri on Unsplash
WASI, Communication between Node.js and WASM modules, another way, with STDIN and STDOUT
My journey to free WASM from my browser
This blog post explains how to use stdin
and stdout
to communicate between the host application and the WebAssembly module.
The previous blog post is WASI, Communication between Node.js and WASM modules with the WASM buffer memory.
Requirements
You need to install the following:
GoLang and TinyGo (previous blog post: "Install Go and TinyGo").
Stdin, Stdout, Stderr?
In Node.js, stdin
, stdout
, and stderr
are objects representing standard input, standard output, and standard error streams, respectively. These streams are used to read input from the user, write output to the console, and report errors to the console.
These streams are connected to the console by default, but you can redirect them to files or other streams. In addition, you can also listen for events on these streams to handle user input, output, and errors.
We can use this with the WebAssembly application.
If you read the beginning of the WASI section of the Node.js documentation, there is this (👋 this is an extract):
new WASI([options])
options <Object>
stdin
<integer>
The file descriptor used as standard input in the WebAssembly application. Default:0
.stdout
<integer>
The file descriptor used as standard output in the WebAssembly application. Default:1
.stderr
<integer>
The file descriptor used as standard error in the WebAssembly application. Default:2
.
That means we can use stdin
to send data to the WASM program and stdout
to receive data from the WASM program (or stderr
to get the errors).
Let's create a new WASM program
We will first write a new WASM module without any function except the main
function. Create a new GoLang module and a main.go
file (in a directory):
go mod init function/hello
touch main.go
This is the content of the main.go
file:
package main
import (
"fmt"
"io"
"os"
)
func main() {
// Read the data on stdin (from host)
data, _ := io.ReadAll(os.Stdin)
// Send data on stdout (like a return value for the host)
fmt.Println("👋 Data from Node.js:", "👋", string(data), "🌍")
// Send data on stderr
println("🤬 oups I did it again!")
}
Build the WASM module
tinygo build -o main.wasm -scheduler=none --no-debug -target wasi ./main.go
Run the WASM module from Node.js
Let's create a new file named index.mjs
with the below content:
import * as fs from 'fs'
import * as path from 'path'
import * as os from 'node:os';
import * as crypto from 'node:crypto';
import { WASI } from 'wasi'
const uniqueId = crypto.randomUUID();
const stdinFile = path.join(os.tmpdir(), `stdin.wasm.${uniqueId}.txt`);
const stdoutFile = path.join(os.tmpdir(), `stdout.wasm.${uniqueId}.txt`);
const stderrFile = path.join(os.tmpdir(), `stderr.wadm.${uniqueId}.txt`);
fs.writeFileSync(stdinFile, "Start writing to stdin...");
const stdin = fs.openSync(stdinFile, 'r');
const stdout = fs.openSync(stdoutFile, 'a');
const stderr = fs.openSync(stderrFile, 'a');
const wasi = new WASI({
args: [],
env: {},
stdin, stdout, stderr,
returnOnExit: true
});
const importObject = { wasi_snapshot_preview1: wasi.wasiImport };
(async () => {
const wasm = await WebAssembly.compile(
fs.readFileSync("./function/main.wasm") // adapt the path
);
const instance = await WebAssembly.instantiate(wasm, importObject);
// 👋 send data to the WASM program
fs.writeFileSync(stdinFile, "Hello Bob Morane");
wasi.start(instance);
// 👋 get the result
console.log(fs.readFileSync(stdoutFile, 'utf8').trim())
// 👋 get the error
console.log(fs.readFileSync(stderrFile, 'utf8').trim())
fs.closeSync(stdin);
fs.closeSync(stdout);
fs.closeSync(stderr);
})();
And to execute the Node.js program, type the following command:
node --experimental-wasi-unstable-preview1 --no-warnings index.mjs
You should see this output:
👋 Data from Node.js: 👋 Hello Bob Morane 🌍
🤬 oups I did it again!
The complete source code is here: https://gitlab.com/wasmkitchen/wasi-nodejs-stdin-stdout
🎉 Thanks for reading! It was a little bit simpler than the previous method: WASI, Communication between Node.js and WASM modules with the WASM buffer memory, but keep in mind that the usage contexts and the performances could be different.
In the next blog post, we'll do our first steps with Wazero.
A very good information source that helps me a lot: