A working, zero-dependency implementation of WASI preview1, with lots of filesystem options. It will work in places other than a browser, but that is the primary target. It's easy, light, simple, effortless to modify/extend, and should work with any WASI-enabled wasm and any modern js runtime.
If you need framebuffer/audio/input WASI devices, check out web-zen-dev.
Install into your bundled project with npm i @easywasm/wasi
. You can also use it on the web, directly from cdn:
<script type="module">
import WasiPreview1 from 'https://esm.sh/@easywasm/wasi'
</script>
And I like to use sourcemaps:
<script type="importmap">
{
"imports": {
"@easywasm/wasi": "https://esm.sh/@easywasm/wasi",
"fflatefs": "https://easywasm.github.io/wasi/fflatefs.js",
"fflate": "https://esm.sh/fflate/esm/browser.js"
}
}
</script>
<script type="module">
import { WasiPreview1 } from '@easywasm/wasi'
</script>
You can use it without a filesystem, like this:
import WasiPreview1 from '@easywasm/wasi'
const wasi_snapshot_preview1 = new WasiPreview1()
const {
instance: { exports }
} = await WebAssembly.instantiateStreaming(fetch('example.wasm'), {
wasi_snapshot_preview1
// your imports here
})
wasi_snapshot_preview1.start(exports)
To really unlock it's power, though, give it an fs
instance.
I used to use zenfs, but it seems broken every time I try, now. I think they intend that you use packaging and stuff, but I just want to import functional things from the web/cdn. In my opinion it's non-functional.
Now, i use a much simpler approach:
import WasiPreview1 from 'https://esm.sh/@easywasm/wasi'
import fflatefs from 'fflatefs'
const fs = await fflatefs('fs.zip')
const wasi_snapshot_preview1 = new WasiPreview1({ fs })
const {
instance: { exports }
} = await WebAssembly.instantiateStreaming(fetch('example.wasm'), {
wasi_snapshot_preview1
// your imports here
})
wasi_snapshot_preview1.start(exports)
This implements the bare-min statSync
/readFileSync
. If you want more functionality, implement more, but that is all you really need to read files.
Have a look in example to see how I fit it all together.
Keep in mind, you can easily override every function yourself, too, like if you want to implement the socket-API, which is the only thing I left out:
import { defs, WasiPreview1 } from '@easywasm/wasi'
class WasiPreview1WithSockets extends WasiPreview1 {
constructor(options = {}) {
super(options)
// do something with options to setup socket
}
// obviously implement these however
sock_accept(fd, flags) {
return defs.ERRNO_NOSYS
}
sock_recv(fd, riData, riFlags) {
return defs.ERRNO_NOSYS
}
sock_send(fd, siData, riFlags) {
return defs.ERRNO_NOSYS
}
sock_shutdown(fd, how) {
return defs.ERRNO_NOSYS
}
}
// usage
const wasi_snapshot_preview1 = new WasiPreview1WithSockets({ fs })
const {
instance: { exports }
} = await WebAssembly.instantiateStreaming(fetch('example.wasm'), {
wasi_snapshot_preview1
})
wasi_snapshot_preview1.start(exports)
Have a look at WasiPreview1 to figure out how to implement it, if you want things to work differently.
- this article has some nice initial ideas
- this article has some good WASI imeplentations
- browser-wasi-shim has a very nice interface, and this is basically the same in JS, but using more extensible filesystem, and I improved a few little things.