Chapter 1. The Interactive Web, and Why We Need WebAssembly

JavaScript, despite facing stiff competition from Flash and Silverlight plug-ins, has been the language of the web for more than 25 years. The first version of JavaScript was developed very quickly, over the course of just two weeks, which has led to some of the language quirks that are still present today. However, in the quarter century since, the language and the browser runtimes have evolved and matured considerably. We are now using JavaScript, often in combination with advanced user interface (UI) frameworks such as React, to create complex browser-based applications that contain tens of thousands of lines of code.

This chapter looks at the origins of JavaScript, and how it fought off competition from the plug-ins. However, despite emerging as the winner and becoming the de facto technology for delivering applications over the web, it is not without issues. We touch on some of these and see how WebAssembly provides a much more optimal solution for web applications.

The Birth of JavaScript

JavaScript was somewhat famously invented over a two-week period in 1995 by Brendan Eich while working at Netscape, one of the early browser pioneers. It is fair to say that those who initially developed and supported JavaScript saw it as a simple scripting language, expecting developers to add a few small scripts to their pages to make them a little more interactive. It was certainly not expected to become the industry-leading language that it is today, with developers writing complex web (desktop, mobile, and server) applications comprising tens of thousands of lines of code. But we’re getting ahead of ourselves…

The JavaScript language of the late 1990s and early 2000s was a little quirky, and its runtime performance was pretty poor. Also, HTML and CSS (in the pre-HTML5 era) were very limited, making it a real challenge to create complex interactive web pages or applications with these technologies. A number of companies looked to create alternatives to meet this demand via the now redundant browser’s plug-in model. Most notable of these plug-ins were Adobe Flash, which had significant success in the early 2000s, and Microsoft’s Silverlight.

Plug-in technologies were very popular for online games and business applications. However, one event occurred that significantly changed the playing field and ultimately caused the demise of plug-ins, and that was the invention of the smartphone. With smartphones, and later tablets, the plug-in model is highly problematic. These devices have limited memory and processing power when compared to their desktop counterparts, and browser plug-ins were seen as inefficient. It was Steve Jobs’ “Thoughts on Flash,” an open letter that clearly stated that the iPhone and iPad would never support Adobe Flash, that famously triggered the end of the plug-in era.

For 25 years, JavaScript has been the only widely supported and accepted language of the web. Furthermore, it has gone through quite a transformation, from a quirky language loved by few, to one of the most widely used and popular languages. Innovation within JavaScript has continued, with popular libraries like React introducing new UI paradigms (unidirectional dataflows supported by a virtual Document Object Model [DOM]), to exceed the capability of the plug-ins that it fought off just a few years back.

So, with JavaScript’s transformation from underdog to the star of the web (and beyond), why do we need WebAssembly?

The Imperfect Delivery of JavaScript

Although the JavaScript we write has transformed significantly, with most developers using tools like Babel or TypeScript to provide them with a richer development environment, the way in which the resultant code is sent to the client’s browser and executed hasn’t changed all that much since its very beginning.

Consider a typical JavaScript application: the code that we write undergoes some quite sophisticated transformations—it is transpiled, tree-shaken, bundled, and minified. However, regardless of the specific tooling used, the code that is eventually “shipped” to the browser is typically compact and somewhat obfuscated, as shown in Figure 1-1.

Minified JavaScript code
Figure 1-1. Minified JavaScript code

When a user visits your website, this JavaScript code is requested using the HTTP protocol and streamed to their browser. When the streaming download is complete, the process of execution begins, as illustrated in Figure 1-2.

JavaScript execution  from parsing to compilation
Figure 1-2. JavaScript execution, from parsing to compilation

The JavaScript code is initially held in memory as a bunch of characters. The first step involves parsing these characters, based on the grammar of the language, into an Abstract Syntax Tree (AST), a format that is easier for the browser’s JavaScript execution engine to work with.

The simplest way to execute this code is to convert the AST into bytecode which is interpreted; however, this also results in poor runtime performance, which is one of the reasons why early browsers (which relied on interpreters) were slow.

To improve runtime performance, modern browsers monitor execution of the code, and by making certain assumptions (e.g., a simple assumption might be that a variable is always of a certain type), they are able to produce a compiled version of the code, which executes a lot faster. This process of making assumptions allows a more optimized compilation to be “tiered,” with code progressively promoted to higher levels of optimization. However, if these assumptions fail, the code drops down to the slower, less-optimized tiers.

In brief, the browser must work hard to strike a balance between the time it takes to start execution, and the runtime performance it delivers.

In general, we can compare interpreters against compilers as follows:

  • Interpreters ⇒ fast start time, slow execution speed

  • Compilers ⇒ slow start time, fast execution speed

JavaScript execution on a modern browser is really quite fast—it just takes a long time to reach that point.

Despite the optimizations at either end of the process, the advanced tooling that transforms and transpiles your code, and the tiered engine that executes it, the part in the middle—when your obfuscated JavaScript code is transferred over HTTP—results in a bottleneck that cannot be avoided.

If we look at this from a user’s perspective, they are most interested in the time it takes to download and execute your webpage or web app. Figure 1-3 presents a rough timeline of events leading up to execution.

A timeline of JavaScript execution
Figure 1-3. A timeline of JavaScript execution

The inherent delivery mechanism for JavaScript clearly has an impact on users and the Time To Interactive, a performance measure that records how long it takes before a user can meaningfully interact with a page.

The Birth of WebAssembly

asm.js was an important precursor to WebAssembly that originally started as a Mozilla project focused on tools and techniques for compiling C/C++ to JavaScript. With Mozilla’s Emscripten compiler, applications are compiled to a subset of the JavaScript language, with type information retained through various hints. The asm.js subset was carefully crafted to remove the dynamic behavior of JavaScript and remove memory allocation/deallocation (which results in garbage collection) in order to give a better runtime performance. Furthermore, when a browser downloads a JavaScript file that is written in asm.js, it is able to skip some of the monitoring and optimization steps discussed earlier, resulting in a faster execution time, as illustrated in Figure 1-4.

asm.js skips the monitor optimize steps
Figure 1-4. asm.js skips the monitor/optimize steps

asm.js goes a long way toward solving some of the inherent inefficiencies of JavaScript, yet it is still delivered as obfuscated JavaScript over HTTP that is parsed into an AST. This bottleneck has not been removed.1 While adoption of asm.js was quite limited, despite the project giving birth to some quite eye-catching demos (e.g., the Unreal game engine running entirely in the browser), it played a significant role in shaping WebAssembly, proving that it is possible to build a more efficient runtime that is embedded within the JavaScript virtual machine.

In 2015, work started on WebAssembly, with engineers from Google, Microsoft, Mozilla, and Apple working together to create a new runtime for the web. They succinctly described WebAssembly as a “new portable, size- and load-time-efficient format suitable for compilation to the web”—quite clearly tackling the JavaScript inefficiencies described earlier in this chapter.

The WebAssembly team, by building on the asm.js foundations and focussing on a minimal feature set (a Minimum Viable Product, or MVP), were able to launch the first version of WebAssembly just two years after the formation of the group. From working group formation to having the specification supported in “all major browsers” within just two years is an astonishing rate of progress for a web standard!

Notably, WebAssembly was not designed to be a complete replacement for JavaScript. In practice, applications make use of WebAssembly for execution of core business logic, interacting with the browser APIs via a JavaScript interop layer. This is in contrast to the plug-in model adopted by Flash and Silverlight, which sought to replace JavaScript entirely.

In Chapter 2, we take a more practical and closer look at WebAssembly modules and the runtime that executes them.

1 There is a TC39 proposal that describes a binary encoding of the AST that could be transferred over HTTP rather than the current textual representation.

Get What Is WebAssembly? now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.