WebAssembly primer: Get started with WebAssembly

Learn how to make use of the next-generation toolkit for delivering high-performance binary apps in the browser

WebAssembly primer: Get started with WebAssembly

WebAssembly promises a whole new kind of web—snappier performance for users, and more flexibility for developers. Instead of being locked into using JavaScript as the sole language for client-side web interaction, a developer can choose from a broad range of other languages—C, TypeScript, Rust, Ruby, Python—and work in the one they’re most comfortable with.

Originally, the only way to create WebAssembly (or WASM for short) was to compile C/C++ code to WebAssembly using the Emscripten toolchain. Today, not only do developers have more language options, but it has become easier to compile these other languages directly to WebAssembly, with fewer intervening steps.

In this piece, we’ll examine the steps required to implement WebAssembly components in a web app. Because WebAssembly is a work-in-progress, the steps are highly dependent on which language you use, and the toolchain is likely to keep changing for some time. But right now, it’s possible to write and deploy useful, if minimal, WebAssembly applications in a number of languages.

Choose a WebAssembly supported language

The first step toward deploying a WebAssembly application is to choose a language that can compile to WebAssembly as a target. There is a good chance that at least one of the major languages you’re using in production can be converted to WebAssembly, or has a compiler that can emit WebAssembly.

Here are the front-runners:

  • C. Obviously. The typical way to turn C code to WebAssembly is via Emscripten, since C-to-Emscripten-to-WebAssembly was the first WebAssembly toolchain to come along. But other tools are emerging. An entire compiler, Cheerp, has been engineered specifically to generate WebAssembly applications from C/C++ code. Cheerp can also target JavaScript, asm.js, or any combination of the above. It is also possible to use the Clang toolchain to build WebAssembly payloads, although the process still requires a good deal of manual lifting. (Here is one example.)
  • Rust. Mozilla’s systems programming language, designed to be both safe and fast, is one of the major candidates for native WebAssembly support. Extensions to the Rust toolchain let you compile directly from Rust code to WebAssembly. You need to use Rust’s nightly toolchain to perform WebAssembly compilation, so this feature should be considered experimental for now.
  • TypeScript. By default TypeScript compiles to JavaScript, meaning that it could in turn be compiled to WebAssembly. The AssemblyScript project reduces the number of steps involved, allowing strictly typed TypeScript to be compiled to WebAssembly.

Several other languages are starting to target WebAssembly, but they’re in the very early stages. The following languages can be used to build WebAssembly components, but only in more limited ways than C, Rust, and TypeScript:

  • D. The D language recently added support for compiling and linking directly to WebAssembly.
  • Java. Java bytecode can be ahead-of-time compiled to WebAssembly via the TeaVM project. This means any language that emits Java bytecode can be compiled to WebAssembly—for instance, Kotlin, Scala, or Clojure. However, many of the Java APIs that cannot be implemented efficiently in WebAssembly are restricted, such as the reflection and resources APIs, so TeaVM—and thus WebAssembly—is only of use for a subset of JVM-based apps. 
  • Lua. The Lua scripting language has a long history of use as an embedded language, just like JavaScript. However, the only projects to turn Lua into WebAssembly involve using an in-browser execution engine: wasm_lua embeds a Lua runtime in the browser, while Luwa JIT-compiles Lua to WebAssembly.
  • Kotlin/Native. Fans of the Kotlin language, a spinoff of Java, have been eagerly awaiting the full release of Kotlin/Native, an LLVM back end for the Kotlin compiler that can produce stand-alone binaries. Kotlin/Native 0.4 introduced support for WebAssembly as a compilation target, but only as a proof of concept.
  • .Net. The .Net languages do not have full-blown WebAssembly support yet, but some experiments have begun. See Blazor, which can be used to build single-page web apps in .Net via C# and Microsoft’s “Razor” syntax.
  • Nim. This up-and-coming language compiles to C, so in theory one could compile the resulting C to WebAssembly. However, an experimental back end for Nim called nwasm is under development.
  • Other LLVM-powered languages. In theory, any language that leverages the LLVM compiler framework can be compiled to WebAssembly, since LLVM supports WebAssembly as one of many targets. However, this does not necessarily mean that any LLVM-compiled language will run as-is in WebAssembly. It just means that LLVM makes targeting WebAssembly easier.

All of the above projects convert the original program or generated bytecode into WebAssembly. But for interpreted languages like Ruby or Python, there’s another approach: Instead of converting the apps themselves, one converts the language runtime into WebAssembly. The programs then run as-is on the converted runtime. Since many language runtimes (including Ruby and Python) are written in C/C++, the conversion process is fundamentally the same as with any other C/C++ application.

Of course this means that the converted runtime must be downloaded to the browser before any applications can be run with it, slowing down load and parsing times. A “pure” WebAssembly version of an app is more lightweight. Thus runtime conversion is at best a stopgap measure until more languages support WebAssembly as an export or compilation target.

Integrate WebAssembly with JavaScript

The next step is to write code in the language you’ve chosen, with some attention to how that code will interact with the WebAssembly environment, then compile it into a WebAssembly module (a WASM binary), and finally integrate that module with an existing JavaScript application.

The exact steps for how to export the code to WebAssembly will vary enormously depending on the toolchain. They will also deviate somewhat from the way regular native binaries are built for that language. For instance, in Rust, you’ll need to follow several steps:

  1. Set up the nightly build for Rust, with the wasm32-unknown-unknown toolchain.
  2. Write your Rust code with external functions declared as #[no-mangle].
  3. Build the code using the above toolchain.

(For a detailed rundown of the above steps, see the The Rust and WebAssembly Book on GitHub.)

It’s worth noting that whatever language you’re using, you’ll need to have at least a minimal level of proficiency in JavaScript for the sake of integrating the code with an HTML front end. If the in-page JavaScript snippets in this example from The Rust and WebAssembly Book seem Greek to you, set aside some time for learning at least enough JavaScript to understand what’s happening there.

The integration of WebAssembly and JavaScript is done by using the WebAssembly object in JavaScript to create a bridge to your WebAssembly code. Mozilla has documentation on how to do this. Here is a separate WebAssembly example for Rust, and here is a WebAssembly example for Node.js.

Right now, the integration between the WebAssembly back end and the JavaScript/HTML front end is still the most cumbersome and manual part of the whole process. For instance, with Rust, bridges to JavaScript still have to be created manually, via raw data pointers.

However, more pieces of the toolchain are starting to address this issue. The Cheerp framework allows C++ programmers to talk to the browser’s APIs via a dedicated namespace. And Rust offers wasm-bindgen, which serves as a two-way bridge between JavaScript and Rust, and between JavaScript and WebAssembly.

In addition, a high-level proposal for how to handle bindings to the host is under consideration. Once finalized, it will provide a standard way for languages that compile to WebAssembly to interact with hosts. The long-term strategy with this proposal also encompasses bindings to hosts that aren’t browsers, but browser bindings are the short-term, immediate use case.

Debugging and profiling WebAssembly apps

One area where the WebAssembly tooling is still in the earliest stages is support for debugging and profiling. 

Until JavaScript source maps came along, languages that compiled to JavaScript were often difficult to debug because the original and compiled code couldn’t be correlated easily. WebAssembly has some of the same problems: If you write code in C and compile it to WASM, it’s hard to draw correlations between the source and the compiled code.

JavaScript source maps indicate which lines in the source code correspond to which regions of the compiled code. Some WebAssembly tools, like Emscripten, can also emit JavaScript source maps for compiled code. One of the long-term plans for WebAssembly is a source map system that goes beyond just what’s available in JavaScript, but it’s still only in the proposal stage.

Right now, the most direct way to debug WASM code in the wild is by using the web browser’s debug console. This article at WebAssemblyCode shows how to generate WASM code with a source map, make it available to the browser’s debugging tools, and step through the code. Note that the steps described depend on using the emcc tool to emit the WASM. You may need to modify the steps depending on your particular toolchain.

Copyright © 2018 IDG Communications, Inc.