Running Wasm with Deno by compiling TypeScript using AssemblyScript
Deno is a new runtime for JavaScript and TypeScript, with native support for loading WebAssembly (Wasm) modules. This is just one of many reasons to be looking forward to Deno's v1.0 release.
AssemblyScript is a compiler that understands a subset of TypeScript and produces Wasm modules. Being able to write a high-level language that isn't all of TypeScript, but isn't C or Rust, seems like a really helpful way to get started experimenting with Wasm in an existing JS or TS application.
For a new server project I'm starting, I wanted to see if I could use Deno instead of Node, and write some of the server's logic in AssemblyScript for high performance. It turns out that it's not very hard to get these two great projects to work together.
This project relies on having node
, npm
and deno
installed.
I'm using node
12, npm
6.13, and deno
0.34.
You can install the latter by running:
curl -fsSL https://deno.land/x/install/install.sh | sh -s v0.34.0
To install AssemblyScript, just run
npm ci
Then take a moment to appreciate that node_modules
is only 30MB on-disk!
assembly/fib.ts
contains an example of TypeScript code written for AssemblyScript (taken from their documentation).
There are two ways we can compile it for use with Deno:
- Using the AssemblyScript runtime.
- Using no runtime. This is easier to get into Deno, but you'll miss out on everything documented here.
This project is set up to create outputs using both approaches in assembly/fib.runtime.wasm
and assembly/fib.standalone.wasm
respectively.
To compile both modules, run
npm run asbuild
Take a look at package.json
to see what this script does.
Then you'll see a bunch of files created in the assembly
folder.
The TypeScript files that will be compiled to Wasm are placed in their own folder so they can have a custom tsconfig.json
which loads a bunch of compatibility stuff.
I'm not sure if this can be worked around, but it's an acceptable constraint for now!
There are three ways to import Wasm modules into Deno JS/TS code which are used in test_fib.ts
and test_sum.ts
:
Direct imports
Deno allows you to import a Wasm module as an ES module:
import { fib } from "./assembly/fib.standalone.wasm";
This is convenient, but won't work with more advanced features like AS's runtime, as far as I can tell.
Browser WebAssembly API
Deno implements many of the same APIs as in browsers.
WebAssembly is just one example.
See the runWithStandalone
function in test_sum.ts
for an example and MDN for more details.
AssemblyScript loader
AS has a loader script which helps setting things up for you.
I had to inline a copy of the loader here (in assemblyscript_loader.js
), because the existing source does not use ES modules.
Hopefully that will change in a future version.
After building your Wasm modules, run the example using:
deno --allow-read=. test_fib.ts
You should see:
fib(10) from module with runtime:
89
fib(10) from standalone module:
89
allow-read
is required because the script loads the fib.runtime.wasm
file from disk.
If you wrote a script that only used the ESM import
method to include fib.standalone.wasm
this permission would not be required, because imports do not require permissions.
test_sum.ts
shows a more complex example of passing a memory region (an array of 10 Float64
s) into the Wasm module.
The Wasm code in assembly/sum.ts
sums the values in memory given a start and end index.
Run the example using:
deno --allow-read=. test_sum.ts
You should see:
sum(0, 10) from module with runtime:
10
sum(0, 10) from standalone module:
10
An important concern is the interaction between the passed-in memory buffer and AssemblyScript's runtime code (when compiled with the runtime).
Note that in the NPM script asbuild:sum.runtime
, we pass the --memoryBase 80
option.
This reserves 80 bytes of space (enough for 10 f64
s) before the AS runtime places its static data in memory.
It's not necessary to pass this option in asbuild:sum.standalone
because AS doesn't need to use any data
segments for the runtime's data structures.
For more details, see https://docs.assemblyscript.org/details/memory