How WebAssembly influences existing JavaScript frameworks

WebAssembly is a compiler target for programs on the Web. It allows developers to compile C/C++ or other static-typed languages in a format that can be run in the browser or a Node.js environment without the need to install any 3rd-party plugins. WebAssembly enables developers to reuse code and create highly efficient web applications with near-Native performance. WebAssembly has been supported by all major browsers since October 2017.

The fact that we can run other languages in the browser breaks the JavaScript monopoly and opens new doors for improvement. WebAssembly is not going to replace JavaScript (at least in the near future), but rather complement it. WebAssembly will change the way we do web development today though. The Web as we know it today consists of many JavaScript frameworks (Angular, React, Vue.js to name a few). In this post I want to discuss different scenarios of how WebAssembly could (or is already doing so) influence the current JavaScript development environment on the Web. Lin Clark talked at React Europe in 2017 about how WebAssembly could influence React and this talk has inspired me to try to generalize all the possibilities.

Scenario 1: Rewrite existing JavaScript frameworks in a language that can be compiled to WebAssembly

One possibility for existing JavaScript frameworks is to be rewritten into a language, say C/C++/Rust, that can be compiled to WebAssembly to gain better performance. This, however, will change the way we use these frameworks. Today we write our applications in JavaScript, we compile them (apply concatenation and minification) to produce a single app.js output file. The browser then downloads the JavaScript framework’s files and our output application file.

If we were to rewrite the framework in another language, then we would probably be writing our applications in that same language or a language that can be interopped to it. We would compile all sources into one WebAssembly module app.wasm file and the browser would need some JavaScript glue code to load our this module.

Furthermore, this scenario provides other variants for the choice of programming language. We could have the framework written in one language and our application code in another one. We would then need some kind of interoperability between these languages in order to compile them together. For example, if we had the framework written in C, then we could write our application in Ruby and use MRuby to compile it to C too. Alternatively, the compiled framework could load separately other WebAssembly modules, which could be written in any language that can be compiled to WebAssembly.

There are some consequences with this scenario.

Less JavaScript

In the past 20 years we have spent a lot of efforts to learn JavaScript and improve it as a language. Rewriting existing JavaScript frameworks in another language means less JavaScript, at least on the front-end. At this point, however, this is not possible due to several reasons:

WebAssembly-to-JavaScript communication overhead

At this moment WebAssembly does not have direct DOM access. The way to achieve it is to inject JavaScript functions when instantiating the WebAssembly module. Your source could look like this:

Then when instantiating the compiled WebAssembly module, you would provide an implementation of the required function.

Basically you need to provide a rich API of functions to deal with the DOM. Fortunately, Emscripten already does this for you, so when one day the direct DOM support lands to WebAssembly, you would not need to update your source code. The real consideration here is about the WebAssembly-to-JavaScript communication, which at this time adds some overhead. It is nanoseconds we are talking about, but as frameworks are expected to manipulate the DOM often, it can easily add up and defeat the desired benefit of better performance. For example, prior to Firefox 60 JavaScript-to-WebAssembly calls were about 20 times slower than JavaScript-to-JavaScript calls. In Firefox 60+ the difference is only ca. 2.3 times. Firefox is already tackling this issue and other browsers are expected to follow too.

Improved native mobile apps

Frameworks like NativeScript, Ionic, and React Native allow you to reuse your JavaScript framework programming skills when writing native mobile applications for Android and iOS. You get access to an extended mobile API, which you can use in the JavaScript framework of your choice. At the end you get a native mobile application, rather than a mobile-friendly website. Although all these frameworks do it differently, they all wrap your JavaScript code and provide a bridge between JavaScript and the mobile platform. React Native, for example, uses JavaScriptCore VM to run your JavaScript code and a bridge to translate the communication between JavaScript and the mobile platform.

If we rewrote the JavaScript frameworks to, say C++, then the entire architecture would be simplified as one would not need to use a JavaScript VM – it would be C++ all! We would still need to access the mobile API, but the performance would be improved as we would do it as Native-to-Native calls, rather than a JavaScript-to-Native translation.

Scenario 2: Rewrite parts of existing JavaScript frameworks in a language that can be compiled to WebAssembly

Instead of rewriting the entire JavaScript framework to a static-type language that can be compiled to WebAssembly, we could select individual parts of the framework. This could be helper libraries or separate components that perform isolated tasks. The desired benefits would be better performance and code reusability.

To gain maximum benefit from this approach, we should focus on parts that can run independently, rather than such with considerable communication with JavaScript. Although not part of a JavaScript framework, the SASS preprocessor is widely popular among web developers. Originally written in Ruby, it got reimplemented in other languages too – C#, Java, Go, C++ to name a few. One of the most popular implementations recently is the C++ LibSass due to its optimized performance. Now, given that one uses Node.js, one could use a WebAssembly-compiled SASS preprocessor instead of rewriting the entire library again.

JavaScript frameworks are indeed looking into what they can gain from WebAssembly. Vue.js, for example, is experimenting with refactoring its core into two parts – a JavaScript one and a WebAssembly-compiled one, to boost performance. Glimmer, the rendering engine of Ember.js, is looking into rewriting parts to Rust and compile them to WebAssembly afterwards to boost performance too.

Scenario 3: Integrate individual WebAssembly components into existing JavaScript frameworks

The is the most natural scenario as it does not require any changes to the existing JavaScript frameworks. Most of them are created around the concept of components – individual pieces coupled together to form an entire application. Programs written in C/C++/Rust then can be compiled to WebAssembly and wrapped in a framework-specific component. The fact that webpack supports WebAssembly makes the integration even easier.

I created a simple project with Angular to demonstrate how it feels to integrate WebAssembly-compiled components. I compiled different programs written in C/C++ and wrapped them as Angular components. These are programs ranging from simple printing to the console, access to the file system (though a virtual one when running in the browser), image manipulation, and WebGL. Here is an example of loading WebAssembly from an Angular component:

Although my project is written with Angular, it does not need to be so – any JavaScript framework would do the work too. No JavaScript framework is fine as well.

Scenario 4: The rise of non-JavaScript frameworks

C/C++/Rust are the languages with strongest toolchain support in terms of compilation to WebAssembly. However, other languages are getting support too – Kotlin, Go, C# to name a few. In 2017 at NDC Oslo Steve Sanderson demoed an experiment about bringing .NET to the browser using WebAssembly. He called it Blazor and since then the project has grown a lot, though still very experimental and not ready for production. Blazor uses Mono – an open source .NET runtime written in C++, that is compiled to WebAssembly. Mono under WebAssembly can run in two modes:

  • Ahead-of-time (AOT) mode is about compiling your .NET code, along with some parts of the Mono runtime, directly to WebAssembly.
  • Interpreted mode is about compiling the Mono runtime to WebAssembly and your .NET code to CIL (.NET DLL) as you usually do. The compiled Mono runtime interprets your CIL code directly in the browser. This is similar to the just-in-time (JIT) compilation process for .NET Desktop apps.

So far Blazor employs the interpreted mode, but further performance tests need to be done to determine the better approach. To manipulate the UI client-side, Blazor uses the Razor syntax (as in ASP.NET MVC).

Although Blazor is still experimental, the development experience is very close to existing JavaScript frameworks. I created a demo to compare and contrast the same application written in Blazor and React, and I think Blazor is very close to what we know today in terms of development experience.

Blazor is the pioneer in bringing other non-JavaScript frameworks to the browser, but it has further inspired others to do the same. Brian Ketelsen created a proof-of-concept of a Go framework for building client-side applications using WebAssembly. Denis Kolodin created Yew – a modern Rust framework for creating frontend applications with WebAssembly. Moreover, the framework supports multi-threading and concurrency out of the box using Web Workers API. Even successors of Silverlight have appeared – Frogui and Uno Platform rely on WPF/Silverlight UI API to create client-side applications running in the browser with WebAssembly.

Conclusion

WebAssembly allows static-typed languages to be compiled and run in the browser and hence allows developers to reuse code and create highly performant applications. WebAssembly breaks the JavaScript monopoly on the browser and opens new possibilities for the Web. Existing JavaScript frameworks are going to change and they are already experimenting with how to benefit most from the new standard. New non-JavaScript frameworks arise too, bringing the richness of other languages to the front-end development scene. I do not believe existing JavaScript frameworks will disappear (soon), but I do believe they will change and new players will be established too. The community is the one to decide what should go on and at this point of time the web community is created around JavaScript.

Thanks to Kevin Simper and Jay Phelps for proofreading and helping me improve this post!