The simplest way to get started with WebAssembly
If you don’t know what WebAssembly is, why you should care about it, how it works and so on, then this article won’t be of much help. But you should definitely check out this interview with Brendan Eich about it, Lin Clark’s cartoon intro into WebAssembly and maybe dig into the superb docs compiled by the nice people over at MDN.
Step 1: setting up the environment
The first thing you should install (assuming you have Node set up already) is AssemblyScript, which is a subset of TypeScript, with the added (incredibly cool!) feature of being able to compile directly to WASM. So by adding a few extra annotations, a plain old JS function can be transformed into WASM using the AssemblyScript command line tool.
So just go ahead, run the following npm command:
npm install -g assemblyscript
You don’t necessarily need to install it globally, but it makes it easier to use asc, the AssemblyScript CLI. Test if the installation was successful by typing asc into the console.
The second NPM package you’ll need to install is called http-server. I’ll explain why it’s needed a bit later, now just install it using this command:
npm install -g http-server
Again, the global installation is useful because http-server has a CLI, that’s easier to use without the whole node_modules/… path.
Step 2: creating your first WebAssembly module
First of all, you’ll need to create a tsconfig.json file with the following contents:
This will basically configure the compiler environment by extending tsconfig.assembly.json.
The next file you’ll need to create should be called fib.ts. This is a TypeScript file, where we’ll write our AssemblyScript code. If you want to, you can use the .as extension, so it’s clear that this is not a regular TypeScript file. As for now, just copy and paste the following function into fib.ts:
As you may have noticed, this function calculates the n-th Fibonacci number. Don’t get scared of all the i32 annotations. It just means that the given variable will have as its type a 32-bit unsigned integer.
Creating the actual WASM file takes just a few seconds. Run the following command from your console:
asc fib.ts -o fib.wasm
This will compile fib.ts and print the outputs into a new filed called fib.wasm.
Step 3: run your WASM file from a browser
To run the code in a browser, not too surprisingly, you’ll need a browser! I’ve tested this in Chrome 61.0, but any other browser should be all right, if it supports WASM (most of the modern ones do) and the fetch API.
Along with the browser, you’ll need an HTML file with the following content:
OK, at first, this may be a bit confusing. Why do we need the fetch API and a bunch of promises to load a WASM file? Can’t we just load it via <script type=”module”> or the ES6 import statement? Well, not yet. In the future these two options will be totally valid, but right now browsers don’t support it, and that’s why we need to mess around with fetch. If you want to know how it works exactly check out this MDN page. Right now for us the only important part is that our fib.wasm file was loaded, and the fib function is now reachable via window.fib.
( AssemblyScript does provide its own loader, but I used fetch instead, since it was easier to use a native API, than to import an external dependency. This is a minor detail, use whichever you fancy.)
Open the freshly created .html site. You should see an error in the console, about the fetch API not being able to load the WASM file. It should also say that “URL scheme must be “http” or “https” for CORS request.”. So basically CORS rules won’t allow the browser to load such a file directly from the file system. That’s why we needed to install the http-server package earlier: it creates a simple Node server, that’ll serve our files. To start the server, run this command in your console:
After the server starts, it will print into the console the address you can use to access your site (in my case it’s http://127.0.0.1:8080). Upon loading it, there should be no error, and you should be able to try out the fib function in your browser’s console, like this:
> fib(5)<- 8> fib(6)<- 13> fib(7)<- 21
Bonus step: measure it!
One of the cool things about WebAssembly is that it should be faster than plain JS. Well, let’s test how our fib.wasm compares against a plain JS solution.
I’ve added a few extra functions to my test site:
simpleFib is the plain JS version of the function we implemented in AssemblyScript (in fib.js).
timer takes a function as the first parameter and an input as the second parameter, and calls the given function with the given parameter. The cool thing about it is that it also measures the execution time of the function, and logs it in the console (using console.time).
Using these two added functions, we compare speeds in the browser console, like this:
Experiment with it! The WASM implementation should be faster even for smaller inputs, and the gap should increase as the input (and the number of computations executed) increases.
WebAssembly is wickedly cool. Now, thanks to AssemblyScript it’s even more accessible, so in my book, no real case can be made against trying it out, and even using it to optimize the computation-heavy parts of your code. If you want to dive deeper, you should definitely read the AssemblyScript docs, since it has a few important details you’ll need, in a surprisingly concise form.
Oh, and one last thing: in my browser calculating the 9999-th Fibonacci number was 84 times faster using the WASM version…