WebAssembly will change the Web Experience

Rajesh Naroth
8 min readOct 30, 2021

JavaScript isn’t the only name in the game anymore…

They’re heeere! Other languages can now participate in shaping up the Web experience.

It has been eight years since React, currently the most widely adopted JavaScript framework for the web, was released. While Vue and Angular have tried to even the playing field, not much has changed since its inception. Eight years is a very long time in the frontend world. Checkout this wonderful infographic:

Here are some interesting markers:

1993 — Mosaic was introduced providing a GUI for browsing the Word Wide Web.

1996 — Java and JavaScript was born leading to another evolution in the way we created user experiences within a browser.

1999 — AJAX takes off leading to single page experiences such as Gmail.

2004 — Firefox is released providing relief to the domination of IE.

2006 — JQuery is born. For the next decade, it becomes a de-facto standard for handling DOM even influencing the DOM API design.

2009 — New standards such as CSS3 and WebWorkers are published.

2010 — Knockout.js is released leading to the introduction of the ViewModel concept. A few months later Angular.js would take this to an application level.

2013 — React is introduced and becomes the most widely adopted SPA framework. A year later, Vue.js is announced and Angular would be released two years later.

There has been a lull in the frontend development space, especially within JavaScript Frameworks. React dominates the space currently, the API has barely changed since its inception (thankfully) and there has not been any major updates with the exception of Hooks. This is not a bad thing — it just means that the React team got it right the first time around.

IMO, we’re due for another disruptive change in how we create browser experiences.

WebAssembly is here

The official site is plenty enough to get all the details but, I’ll summarize the keypoints here:

  • WebAssembly has full support in most browsers
  • It runs at near native speeds.
  • WebAssembly and JavaScript are interoperable — they can import and call functions from each other.
  • WebAssembly code can be written in (wait for it…) other languages.

💥 Boom!

The ability to write code that runs in a browser at native speeds in a language other than JavaScript is a game changer

What problem does WebAssembly solve?

Primarily, performance where intense computation is needed.

JavaScript is generally slow — it has fantastic asynchronous language support yet is single threaded. When you write applications using a framework like React, it becomes even slower. Web workers can alleviate the single-thread problem but still runs as JavaScript. JavaScript runtimes must constantly perform runtime optimizations and garbage collection(GC). Modern webapps bundle code in the order of megabytes that need to be parsed dynamically and optimized during runtime.

WebAssembly aka Wasm shines while performing heavy computations. For example, you can perform image compression or apply a fancy image filter using WebAssembly code without having to delegate it to a server. Wasm contains no run time optimizations or garbage collection thus there are no GC pauses.

Can WebAssembly make a “normal” website run faster?

A normal website here would be one that is text heavy, contains HTML, CSS, Fonts and Images — one that is WCAG compliant and SEO friendly. The answer is no. For a normal Web experience, JavaScript is the only option — for the foreseeable future.

JavaScript is not going away anytime soon.. or ever.

Desktop experience in the browser

Web is the most ubiquitous content delivery system, it is the largest computation platform. However, until recently it was not impossible to perform heavy computations in a browser without compromising performance. WebAssembly opens up a whole new world of opportunities within the browser.

Games

Both Unity and Unreal has WebAssembly support now. This is huge because now becomes possible to port massive pieces of existing software written in C++ to WebAssembly.

Time tested implementations of algorithms are now ready for the web.

When you are able to render your view into an image buffer that can be mapped onto an HTML canvas, you’re off to a great start. Differences between native and browser based play will narrow considerably once GPU rendering is implemented.

Desktop Applications

Figma is the poster child for this. The playground in Figma is an HTML canvas that is fed by an image buffer maintained by WebAssembly code — at least that’s what I understood from my limited capabilities. Adobe will deploy many of its CC applications to the web via WebAssembly starting with Photoshop, which they announced this week. The ability to port existing C/C++ code lines to the web opens up so many possibilities. Check out AutoCAD on WebAssembly or the many other POCs:

What does it mean for Frontend development?

Languages that never had a direct role in creating browser based experiences are coming to the web. WebAssembly will soon become an essential skill in a Frontend developer’s arsenal.

While there is broad support for WebAssembly from all languages, even Java, 🙄, there are two prime candidates to consider — IMO. But first we should look at what Wasm looks like.

Wasm Code

Let us remove the mystery by looking at actual code. Wasm code can be in two formats — the actual binary or wat. wat is a human readable form, albeit low level.

;; main.wat
(module
(
func $add (param $arg1 i32) (param $arg2 i32) (result i32)
get_local $arg1 ;; Push arg1 to stack
get_local $arg2 ;; push arg2 to stack
i32.add ;; Execute opcode i32.add
)
(export "add" (func $add))
)

wat files are “compiled” down to wasm files. A wat file exists only to aid us in reading and debugging wasm code. If you’re curious about what the binary wasm file looks like, here it is after a bit of cleanup to fit the page width:

> od -c main.wasm 
\0 a s m 001 \0 \0 \0 001 \a 001 ` 002 177 177 001
177 003 002 001 \0 \a \a 001 003 a d d \0 \0 \n \t
001 \a \0 \0 001 j \v \0 036 004 n a m e
001 006 001 \0 003 a d d 002 017 001 \0 002 \0 004 a
r g 1 001 004 a r g 2

Now how do we call “add” function from JavaScript, for this we need a bit of JS glue code.

async function init() {
const result = await WebAssembly
.instantiateStreaming(fetch("../out/main.wasm"));
const wasm = result.instance.exports;
document
.getElementById("container")
.textContent = wasm.add(19, 23); // wasm function
}
init();

Obviously, you should put your code in a try/catch block. But, this should explain how JavaScript can call Wasm functions.

Writing Wasm code

A “higher” language is essential because, trust me, I don’t want write wat code beyond add(). The main contenders here are Rust, C++ and AssemblyScript. The main use case for C++ is the amount of legacy code already developed over the years solving tough problems in desktop apps. Starting fresh, I recommend Rust or AssemblyScript.

AssemblyScript

Based on TypeScript, AssemblyScript is a strong contender for writing WebAssembly code. It feels very much like TypeScript, it is an easy transition for JavaScript and TypeScript programmers. I recommend AssemblyScript if:

  • your active code base, including libraries you want to bring into WeAssembly, is in JavaScript.
  • Training your team in a new language is out of scope.

TypeScript/JavaScript has in-built garbage collection but WebAssembly has none. This means that the compiled AssemblyScript code will contain code to perform some level of garbage collection. The JavaScript mindset of writing code may also lead to without memory optimizations in mind. I could be wrong here in my assumption but I am yet to try AssemblyScript in a larger scope.

Rust

According to Stack Overflow Survey, Rust is the most loved language in 2020 and 2021. Rust’s primary goal is efficiency and reliability which it tries to accomplish at compile time. IMO, this is the language that is closest in nature to WebAssembly. Rust has no automatic garbage collection and it accomplishes thread safety using a variable ownership model. In “higher” languages, GC pauses can potentially get really bad.

The language has active support from most of the large software companies. After discovering that 70% of bugs within all their products are memory related, Microsoft is considering rewriting many of the core OS components in Rust.

However, Rust is not an easy language to master (at all), the learning curve can be quite steep. Checkout Rust’s ownership model. Also, there is no null.

Rust vs AssembyScript

I am no expert in Rust but after taking a basic course, I can clearly see why Rust was designed differently without regard for ease of learning 😔. After years of trying to force developer discipline within languages by using best practices, design patterns and endless discussions to produce performant and reliable products, finally, here is a language that enforces it all semantically at compile time.

AssemblyScript is an easier route for frontend developers since it looks like TypeScript. However, you still need to do proper memory management since the basic types in AssemblyScript are very much mapped to Wasm types.

Adopting WebAssembly will require clear division of responsibilities between the source language and Javascript, with the former used for intense computations and the latter for Web interactivity. This division is important to remember because WebAssembly is not faster than JS in every scenario.

Invoking JavaScript functions from Wasm

We’re going to use Assembly script here to write our Wasm code.

The glue code looks very familiar to the one we did before but here, we’re also registering a function “sayHello” that the Wasm code can execute.

// The function that wasm can call
const sayHello = () => {
console.log("Hello from WebAssembly!");
}
async function init() {
const result = await WebAssembly
.instantiateStreaming(fetch("../out/main.wasm"), {
main: { sayHello }
}
);
const wasm = result.instance.exports;
document
.getElementById("container")
.textContent = wasm.add(19, 23);
}
init();

Now from our wasm AssemblyScript code, we can do this.

declare function sayHello(): void;sayHello(); // is defined in the browserexport function add(x: i32, y: i32): i32 {
return x + y;
}

Now with minimal boilerplate we’re able to communicate back and forth. Now, sharing memory.. that is a whole different thing.

WebAssembly beyond the Web: WASI

WASI aka WebAssembly System Interface is a standard focused on security and portability that lets you run a .wasm module in a WASI-compliant runtime. WASI currently is its infancy with full Rust toolchain and a custom support for C/C++.

Checkout Lucet from Fastly for an example on how WebAssembly is changing Serverless solutions.

Conclusion

WebAssembly opens up a plethora of possibilities that can change how we experience the browser. For products that need to co-exist in consoles, native apps and the web, it is a godsend. However, if you are only creating a web experience, adopting one of the modern SPA frameworks with only heavy computations delegated to WebAssembly code may be one way to go. JavaScript will still reign supreme on the web with WebAssembly opening up an avenue for other languages to participate. Crates of awesome goodies, algorithms and libraries that have been written in the past few decades, are ready to flow into the browser.

--

--