The Role of JavaScript in Blazor WASM

kiss_code

kis.stupid

Posted on April 3, 2024

The Role of JavaScript in Blazor WASM

I have heard the following statement multiple times:

"Blazor's (C#) code is transpiled to JavaScript code to run in the browser..."

While this is not correct, that thought is not too far-fetched for multiple reasons:

  • In the past there have been recompilations of C# code to the web.
  • The JS interop feature, how else would that work?
  • WebAssembly can't manipulate the DOM, only JavaScript can.

I will cover the JS interop feature in an upcoming article and I will dedicate another YouTube video to it on my channel: Keep it simple, stupid. but let's gain some conceptual understanding first.

JavaScript

Every major web browser runs a "JavaScript engine" also known as a "ECMAScript engine" alongside their browser engine. The latter is also known as a rendering engine or a layout engine and transforms e.g. HTML documents into an interactive visual representation (DOM).

JavaScript engines started out as interpreters which would execute code line-by-line or statement-by-statement after translating the source code into machine code (intermediate code). Nowadays, JavaScript engines use just-in-time (JIT) compilation which arguably uses the best of both worlds, interpreter vs. compiler.

Do not confuse the above with the transpiling of e.g. ECMAScript 6 (ES6) into ES5 code. This is the conversion of JavaScript's ES6+ syntax into its ES5 equivalent to be backwards compatible with older browsers.

A well-known ECMAScript engine is Google's V8 JavaScript engine which is used in the browser but also for Node.js and Deno runtime systems. So, JavaScript engines are not solely used for browsers.

ECMAScript engines can execute code (Angular, React, Vue, ...) within the same (security) sandbox as regular JavaScript code.

Ever since (2017) some of these engines support WebAssembly and can now execute the assembly code in that same security sandbox.

Browser APIs

The major browser also provide a large number of Web APIs for your web app to interact with browser features. Commonly used web browser APIs are the "Fetch API", "Geolocation API", "File API", "Share API", "Local Storage", ...

The browser APIs are mostly called from JavaScript code but are not limited to JavaScript. That said, to use these in a Blazor app, we can use the JavaScript Interop feature (which was supposed to be the subject of this article).

WebAssembly

WASM brings high-performing, assembly-like programming to the web as portable, binary-code to be executed client-side. WASM implementations typically either use ahead-of-time (AOT) or just-in-time (JIT) compilation.

WASM executes apps / app modules within a sandboxed environment separated from the host runtime. Interactions between the apps and its hosting environment can only happen through set APIs.

More than 40 programming languages can be compiled to WebAssembly executables. Most WASM implementations aimed at the web and some are general-purpose implementations.

"If WASM+WASI existed in 2008, we wouldn't have needed to create Docker. That's how important it is. WebAssembly on the server is the future of computing." - Solomon Hykes, a co-founder of Docker.

Lastly, interesting to note is that it:

  • supports multithreading and garbage collection ;
  • cannot directly manipulate the browser's DOM, requires JavaScript;

By reading all of the above, we can make assumptions on how Blazor and JavaScript work together but let's dig in.

Blazor WebAssembly

Blazor WASM allows for running highly-interactive, single-page C# web applications in the browser on a WASM-based .NET runtime. The runtime and other dependencies needs to be downloaded and initialized to run a standalone Blazor WASM app. All this is handled by the Blazor script: blazor.web.js.

This WASM-based .NET runtime has a rather large file size to download which does not result in a great user experience. Blazor optimizes this by:

  • caching these dependencies for future runs;
  • strips away unused code (IL trimmer);
  • using the just-in-time (JIT) interpreter at execution;
  • ...

By default, ahead-of-time (AOT) compilation is disabled. Enabling this comes down to the need to optimize for package size, performance or supported features.

  • With AOT disabled, the Blazor app runs in the browser by using a .NET intermediate language (IL) interpreter implemented in WebAssembly with partial just-in-time (JIT) runtime support.

  • With AOT compilation enabled, the Blazor app's C# code precompiles into binary-code (WebAssembly) before browser execution.

What have(n't) we learned so far?

The C# code compiled as assembly code can be executed in the same (security) sandboxed environment as regular JavaScript code but (still) requires JavaScript to manipulate the browser's DOM.

How does Blazor WASM manipulate the DOM?

We learned that the Blazor script: blazor.web.js is responsible for downloading and initializing the (WASM-based) .NET runtime. It's not clear whether this is also responsible for DOM interactions.

Blazor maintains render-trees (representations) of the DOM in-memory to render its components. On component update or a re-render, an old and new render-trees are compared to one another, the difference is communicated to JavaScript (using interop?) functionality to update the DOM. This sound similar like React.js' shadow DOM or a virtual DOM.

Why would we need the JS interop?

JavaScript is the front runner for building interactive, client-side web apps and has grown a large, fast-evolving ecosystem around it. I have been an active participant in using JavaScript-heavy projects from hobby to professional.

The JavaScript ecosystem has an enormous amount of already built functionality and it can do anything from dynamically updating the layout on static web pages to full-fledged, highly-interactive, single-page web apps. So, there is a lot of knowledge and function to be learned, borrowed, re-used, ...

Thanks to the modern-web framework like React, Angular, Vue, ... and its data-binding functionality, we don't have to concern ourselves too much with manually manipulating the DOM. This allows us to focus on building features, organizing our project structure etc. and to increase our productivity with all its tooling.

While building awesome web apps with Angular, React and Svelte, I often wondered why I was still using JavaScript since the code didn't resemble Vanilla JavaScript or jQuery anymore. But, I had a strong preference for the structure that Angular and Nest.js offered and soon after for reasons that I described in an earlier blog post, I switched to Blazor WASM & .NET Web API.

While using Angular, I sometimes used jQuery or other JS libraries and a ton of NPM packages. Among other functionality, I didn't rebuild the already provided Bootstrap jQuery functionality and not all JS libraries worked smoothly with Angular. Just like it is with Blazor, while I can easily rebuild e.g. Bootstrap's dropdown functionality, the accordions and maybe the carousels, I don't have to. Especially that last one can be time-consuming to figure out and rebuild so I rather leverage the already built functionality while not having it scattered across my codebase.

Long story short, I implement the JS interop feature for e.g. auto-starting carousels and other components that would be too time-consuming to rebuild.

Another reason I implement the JS interop is to access the browser APIs like navigator.share or the local storage and more useful client-side, browser functionality.

I have covered the JS interop feature multiple on my YouTube channel: Keep it simple, stupid. and I will dedicate another upcoming video and article to it.

Or you can grab yourself a copy of my .NET 8 Blazor brand website which contains multiple uses of the JS interop.


Thank you for taking the time & interest in my work.

Kind regards, Auguste @ kiss-code


Sources:

💖 💪 🙅 🚩
kiss_code
kis.stupid

Posted on April 3, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related