05Aug

artwork depicting WebAssembly logo and various web elements around it

The world of modern web development is an exciting thing to discover: new technologies are emerging every year, promising to change the way we build web projects. For decades, JavaScript has been the backbone of web interactivity, taking web development from “Structure the page and make it pretty” to “Make the user say ‘Wow!’”. Now, there comes a new challenger — WebAssembly (also called “Wasm” for short).

Although many web developers are perfectly satisfied with JavaScript and its ecosystem, an equally large amount of their peers often criticize JavaScript for its shortcomings. Over the years, the developer community has made numerous attempts at addressing these problems — and frameworks like React, Angular, and Vue have indeed improved the JavaScript workflow and its capabilities.

WebAssembly, however, might have the potential to reshape the entire web development landscape — just like JavaScript when it was introduced back in 1995. In this article, we will explore what WebAssembly is, why is it groundbreaking, and how it can be used.

So what is WebAssembly?

artwork depicting the WebAssembly logo and a colorful splash
I’m just an image caption, don’t ask me

The above sentence about having the potential to reshape the entire web development is a serious claim, so you eyebrow might be itching to raise skeptically. Like with all bleeding-edge technologies, WebAssembly often causes confusion among web developers. In order to define this term better, we can first address what WebAssembly is actually not:

  • WebAssembly isn’t a programming language.
  • WebAssembly isn’t just “C++ in web”.
  • WebAssembly isn’t a web technology per se even though it’s called, well, WebAssembly.

With this out of the way, let’s define this term: WebAssembly is a binary instruction format for a stack-based virtual machine. This definition, although brief, holds several key points which can help us:

  • It’s a virtual machine — a processor whose purpose is to compile code to real architectures, with portability in mind.
  • Binary instructions are represented via a two-symbol system.
artwork depicting how WebAssembly works
Wait, explain again!

As web development progressed, the need for such technology grows proportionally: more and more web developers were needing a tool that would get the job done, all the while satisfying a number of requirements:

  1. Fast. Faster than JavaScript and (ideally) on par with native code.
  2. Secure. Must not introduce new vulnerabilities.
  3. Cross-platform. Performs equally well on various desktop and mobile operating systems.
  4. Zero configuration. It just works.
  5. Development-ready (i.e. provides/supports dev and debugging tools).

Although a plethora of technologies attempted to win this competition, all of them failed — except JavaScript. Some of the contestants included ActiveX, Flash, Google’s Native Client, and asm.js — but since WebAssembly is the focus of this article, you can conclude that they didn’t quite live up to expectations.

Why is WebAssembly such a big deal?

artwork depicting how WebAssembly stands out among other technologies
Indeed, why?

A programming language can be powerful, but its true potential lies in its ecosystem. JavaScript is a striking example of this argument: thanks to massive tools like npm, it managed to transcend the status of “just another programming language” and… reach #1 position in dev.to’s “Top 100 tags”! That’s saying something, you know.

Still, even npm cannot sometimes provide the answer you’re looking for. Let’s say you’re looking for a library that solves your problem — but you only find those written in languages other than JavaScript. Before WebAssembly, you’d be forced to port this library to JavaScript by yourself — now, you can use it directly with almost the same functionality.

We’ve established that WebAssembly is the promise of unified binary format across platforms, which would even include browsers. This is arguably the most exciting novelty of this technology, similar to the emergence of browser-as-an-application paradigm.

One of Wasm’s development priorities is garbage collection support. Once it’s introduced alongside with native support for web API, developers will finally be able to create web apps in languages other than JavaScript — C, C++, Go, Rust, Java, Python… who knows, maybe even PHP if it implements the full-type system!

WebAssembly also offers predictable performance — the ability to reduce the difference in browser performance (in some cases from 400% to less than 50%).

“JavaScript vs. WebAssembly” debate

artwork depicting WebAssembly vs. JavaScript
The debate begins

Although the developer community is generally open-minded, sometimes it’s hard to resist starting another ”My favorite technology is better than your favorite technology!” debate. JavaScript and WebAssembly are no exception, so some web developers try to make these technologies look like opposing forces. Here’s an important takeaway: the relationship between JS and WASM are symbiotic, not adversarial.

In many cases, WebAssembly can address the shortcomings of JavaScript: one example is using JavaScript as a compile target (via asm.js, for example) — obviously enough, this would be a bad idea because of the resource costs associated with parsing and compiling JavaScript. WebAssembly, however, performs far better in this scenario — its binary format speed allows for faster startup and execution.

Generally, WebAssembly code is lighter compared to JavaScript code, all the while having the same functionality; WebAssembly modules, on the other hand, are generally heavy-weight and, more importantly, are harder to split compared to JavaScript modules.

It’s tempting to compare these technologies only by performance numbers, so here’s another curious finding: JavaScript and WebAssembly have equal peak performance. Still, preferring one technology over the other simply because of performance gains (if any) is short-sighted — there are a plethora of other factors that come into play.

However, there’s a caveat which was outlined by Nick Fitzgerald in his recent study Oxidizing Source Maps with Rust and WebAssembly: WebAssembly provides performance that is more predictable compared to that of JavaScript, which can suffer from deoptimization — a process which forces JavaScript execution flow to fall back to the interpreter.

screenshot of the benchmarks used to test the performance of WebAssembly against JavaScript
(click to open a larger version)

This curious phenomenon can be demonstrated by implementing image rotation functionality via canvas: let the user rotate a 4K image and test it under different browsers. Here are the findings of Google Chrome Labs:

  • Browser 1 took 400 milliseconds to rotate the image.
  • Browser 2 took 500 seconds.
  • Browser 3 took 2,5 seconds.
  • Browser 4 took 8 seconds.

Looking at this data, it’s tempting to come up with a conclusion like “Aha, Browser 4 is a prime example of bad engineering!” However, we need to remember that different browsers optimize differently, so Browser 4 isn’t Netscape Navigator v0.7 — it’s just a perfectly capable browser. Under other circumstances, perhaps, Browser 1 would be the slowest one.

As the graph given below shows, rewriting this code in WebAssembly, on the other hand, has helped these browsers achieve roughly the same performance and, most importantly, their performance becomes predictable — any development team will appreciate this. We can also see that the peak performance of JavaScript and WebAssembly is indeed equal.

statistics showing speed comparison per programming languages
(click to open a larger version)

We should also note that the performance equality of JavaScript and WebAssembly will probably disappear in the future: WebAssembly will soon receive support for threads and simd (single instruction, multiple data which will allow it to outperform JavaScript.

Disadvantages and caveats of WebAssembly

artwork depicting WebAssembly logo and red error messages
Error #232: image caption not found

Of course, WebAssembly isn’t the silver bullet — one of its disadvantages has to do with manual memory management: But wait, this is a perfectly normal feature in many languages! Therefore, it’s not necessarily a disadvantage per se; for some JavaScript developers, however, this may be an entire paradigm shift when they start to manage memory resources by themselves.

Another caveat: WebAssembly code is sandboxed in the browser, which means that its capabilities are limited to the level of JavaScript. Still, we can use a certain browser to features to imitate the functionality of native apps. Here’s how C++ features transition into WebAssembly and JavaScript:

  • FileSystem → Cookie, LocalStorage, IndexedDB.
  • Network → XHR, fetch, WebSocket.
  • Random → Math.random().
  • Async → Poll + setTimeout().
  • 3D → Canvas, WebGL.

We should also mention that WebAssembly isn’t fully supported across all modern browsers. Here’s the data provided by Mozilla Developer Network as of August 2019:

artwork depicting the status of WebAssembly browser compatibility
(click to open a larger version)

Some awesome use cases of WebAssembly

The projects we’re about to discuss right now teach us one important lesson: they aren’t designed to compete with JavaScript and waste user’s resources; instead, they address the typical bottlenecks of web apps.

Squoosh.app: Desktop-level image compression… in web!

logo of squoosh.app

In his recent talk WebAssembly for Web Developers, Surma Surma provided a great breakdown of how WASM helped the team build Squoosh, an image compression web app. On the surface, Squoosh functions like any other image compression solution: you drop some images in → they get compressed → you use them on your website → your users are happy that a small cat picture in the header doesn’t weigh several megabytes.

This functionality may seem redundant, though: doesn’t canvas already offer image encoding options alongside image encoding options and quality settings?

const canvas = document.querySelector(“canvas”);
canvas.toBlob(callback, “image/jpeg”, 0.5);

However, in-browser compression was designed to favor speed over quality. Additionally, you’d have to factor in the capabilities of each browser (for a long period of time, only Chrome could encode to WebP). The available JavaScript-based solutions for this problem were subpar, so the team turned to libraries from other language ecosystems, found one called MozJPEG, written in C, and compiled it via WebAssembly. Then, the in-browser limited encoder was replaced with MozJPEG running through WebAssembly.

artwork depicting the transformation of the MozJPEG library thanks to WebAssembly
Back to the future

The ability to change the default browser functionality is one of Wasm’s key elements: the MozJPEG library was able to enhance Squoosh with some expert encoding and compression options which were simply impossible to implement in the browser. Here’s how they did it utilizing in-browser JavaScript and Node.js (this would make a great addition to Node.js interview questions, don’t you think?)

Using Emscripten, we first compile the library:

echo "======================="
echo "Compiling mozjpeg..."
echo "======================="
(	
	cd node_modules/mozjpeg
	autorecong -fiv
	emconfigure ./configure --without-simd
	emmake make libjpeg.la
)

Notice the configure --without-simd part: threads and simd would greatly help to encode, but WebAssembly doesn’t have support for these features… for now, so they’re disabled for safety reasons.

Next, we can write a JavaScript function that will return a typed array buffer with a JPEG image that we used in the input:

#include "jpeglib.h"

val encode(
	std::string image_in,
	int image_width,
	int image_height
) {
	uint8_t* image_buffer = (uint8_t*) malloc();
	// ... Use MozJPEG ...
	return val(typed_memory_view(size, image_buffer));
}

EMSCRIPTEN_BINDINGS(mozjpeg_wasm) {
	function("encode", &encode);
}

After that, an Emscripten C compiler called EMCC will link everything together:

emcc \
	--bind \
	-I node_modules/mozjpeg \
	-o ./mozjpeg_enc.js \
	-x c++ -std=c++11 \
	mozjpeg_enc.cpp \
	node_modules/mozjpeg/ .libs/libjpeg.a

Golang is at its earliest stages of supporting WA. As Surma Surma, developer advocate for the Open Web Platform at Google, puts it: True! Golang has (very) experimental WebAssembly support. But Go is a garbage-collected language, so currently, their binaries are pretty big and rather slow, as they have to compile their entire runtime. Just like AssemblyScript, it will get much better once we have native garbage collection in WebAssembly.

ByteFog: Peer-to-peer video-sharing done right

logo of ByteFog

ByteFog is a technology designed to make peer-to-peer video sharing efficient and streamlined. As every streaming service performs poorly under heavy load, ByteFrog addresses this problem by making clients “share” parts of the video with their peers, helping the server and saving its resources. In browsers, this technology was implemented via a browser extension — but then the team decided to rewrite it in WebAssembly.

Here are the advantages of moving the service from browser extension to the web version:

  1. No installation. Convincing the prospective user to install an extension or an app is a challenge in and of itself. Moving the service to the web version skips this challenge entirely.
  2. Unified codebase.
  3. Debugging on every platform simultaneously. As ByteFog offers applications for various platforms (Windows, Linux, macOS, Android, iOS, and web), noticing bugs becomes far easier: while a particular bug may be hard to observe on Platform A, it can be much easier to spot on Platform B.
  4. Fast releases and updates. Unlike desktop/mobile applications, updating a web app is relatively streamlined.
  5. Fast feedback which stems from the previous point.

Of course, the number of WebAssembly projects is growing day by day and it’s impossible to cover all of them. Here are some honorable mentions:

  • Wasm-ImageMagick: It’s ImageMagick! In Webassembly!
  • wasmboy: Gameboy emulator written in WebAssembly. wasmboy is an awesome combination: a blast from the past which will soothe your ears with classic Gameboy sounds and a curious case of Wasm as a technology.
  • WOTInspector: This is a service which analyzes match replays from a popular online game World of Tanks. The calculations the service has to do are pretty heavy, so it could very well become a paid platform with all heavy-lifting done on the backend. WOTInspector’s author, however, went for a different route: he created the business logic in C++ and implemented it in WebAssembly, allowing users to carry these computations out on their own devices — and so the service managed to stay free!
  • Tried and tested libraries like FFmpeg (video encoding), opus (audio encoding), OpenCV (computer vision) are also available to be implemented in WebAssembly. The sky is the limit!

WebAssembly learning resources

At this point in the article, WebAssembly has finally worked its magic and you’re itching to start learning it. Here’s the catch: you don’t learn WebAssembly per se; instead, you learn a language that can be compiled to it. A good example is Rust which also features a great Getting Started resource.

For a quick dive-in, you can use WasmFiddle — as the name suggests, it’s a code playground which allows you to experiment with WebAssembly without putting a hazard suit on.

Conclusion

WebAssembly is promising to introduce a paradigm shift in the way we develop web apps. With support from web giants like Google and Mozilla, its future is looking even brighter. Let’s recap the article — WebAssembly can be used to…

  1. Decrease load times.
  2. Increase execution/calculations speed.
  3. Utilize C/C++ code in web apps.

Thankfully, you won’t miss out on any groundbreaking changes in the web development sphere — our blog got you covered! 🙂

2 Replies to “Introduction to WebAssembly: The Magic of Native Code in Web Apps”

  1. Alina Shumarina 5 years ago

    Cool, thanks!

  2. Josh McCullough 5 years ago

    C# also compiles to WebAssembly. Would be nice to mention the languages we can use to start testing this out. https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor

Leave a Reply