REPL that utilizes Web Assembly to run in the Browser!
This was very hard, because I had to use many completely new technologies: Web Assembly, Web Workers, wasm-bindgen, Vue + Nuxt, xterm.js, and certain other crazy browser APIs like SharedArrayBuffers and Atomics.
stdio - stdin, stdout, stderr
I pulled out the basic terminal operations into an interface so that I can supply the implementation based on whether I’m running in the browser or in a native terminal. What came out was WasmStdio and TerminalStdio. It basically gives me the stdin reader, and the stdout/stderr writers by reference, allowing me to use the normal std::io operations on them. WasmStdio also does buffering and TerminalStdio handles raw terminal stdin operations i.e. key presses for history, deletion, etc. I used termion for the key parsing on native, but rolled my own for Wasm, which is still a work-in-progress right now.
WasmIo
The new implementation is kind of just like VirtualIo, but Io now also exposes now() -> time::Duration, since the implementation requires syscalls / JS APIs. On native targets, it goes through the OS, while it uses the Date API in the browser.
Crossterm Issues
I could not compile for Wasm at first, because I was using comfy-table to render the REPL tables originally, which was dependent on crossterm. It could only compile for native targets, so I had to swap comfy-table out for tabled and also remove rustyline so that I got rid of the crossterm transitive dependency. This also meant that I had to replace the implementation for reading in commands by the user with my own one.
Wasm + Worker Architecture
Now that it compiles, I still have to ‘drive’ it. To not block my main thread with the runtime, I put it on a web worker and made it communicate via postMessage() for stdout/stderr and a SharedArrayBuffer for stdin. The buffer in combination with Atomics.wait allows us to put the web worker to sleep, until it receives a new input line from the terminal. When originally implementing this, I kind of forgot that, and it crashed everything! Windows sure does not isolate those processes in the right way, even though it sucks up all of my RAM :’(
xterm.js
This is a cool npm package that gives me the raw terminal-like interactions in the browser. I still have to hook up input processing, buffering, and output all by myself though. But that was actually good, because it allowed me to hook it up with the database really well.
Vue + Nuxt Frontend
I was unsure as to what frontend framework I wanted to use, but I finally decided on Vue, because… it does not really matter. I do not like using React, because it mixes up scripting with markup/styling. So when looking for alternatives, Vue seemed the most stable. I’ve used Svelte before, which was really nice, but it’s also quite unstable and less popular, so Vue seemed like the best fit.
Going Forward
Now that it runs as one big thing, I’m going to optimize the implementation and extend it with new features after!
The attachment shows the REPL working in the Browser through xterm.js.
Full diff: https://codeberg.org/Vimthusiast/tempest/compare/89d9950...a81ee13