Learn how just-in-time (JIT) and ahead-of-time (AOT) compilation work in context of Blazor WebAssembly applications.
Since the introduction of .NET Framework in 2002 to the early days of .NET (Core) in 2016, just-in-time (JIT) compilation is how the .NET platform worked.
With JIT, we write our applications using one of the available .NET programming languages, mostly C#, F# or Visual Basic .NET. The compiler turns the source code into Intermediate Language (IL) code stored in .dll files. At program startup, the .NET runtime loads the .dll files containing the IL code, and just-in-time (JIT) compiles them into machine code.
Available since .NET 7 and improving with .NET 8 and .NET 9, we have another option to compile our .NET application using native ahead-of-time (AOT) compilation.
Before we learn about AOT compilation, let’s look at the strengths of JIT compilation and why it has been the only option for a long time.
When using JIT compilation, the Common Language Runtime (CLR) executes the Intermediate Language (IL) code by compiling it into native machine code at runtime.
This means that we can ship the same DLLs to all supported platforms, and the CLR will compile the application code into the native machine code during the application’s startup. Not only does that help with the portability of the DLLs, but it also speeds up the application because the CLR can dynamically optimize the execution to the processor architecture and the platform it runs on.
JIT is also the foundation for Reflection. Without JIT, we couldn’t use Reflection because we wouldn’t have the .NET type information available at runtime.
With ahead-of-time (AOT) compilation, we compile the C#, F# or Visual Basic .NET code to machine code instead of Intermediate Language (IL) code.
Using AOT, we get a fully self-contained executable. The applications can run on systems that don’t have the .NET runtime installed.
We also get faster startup times because no runtime compilation is required—the step of compiling IL code to native code omits it. The application also consumes less memory because of the lack of runtime compilation.
With trimming and other optimizations, AOT-compiled applications can be smaller than JIT-compiled applications. This benefits cloud deployments because less bandwidth is consumed when deploying the application. It is usually only relevant in high-scale situations where you must deploy multiple application instances.
AOT is often ideal for large-scale cloud deployments or environments with limited resources, such as embedded systems or IOT devices.
In the context of Blazor web applications using the WebAssembly interactivity mode, the application code is compiled into WebAssembly, the native code for the browser’s platform. It makes the application execute faster in the browser but often comes with the downside of larger sizes, resulting in longer download duration.
Now, say we want to use AOT compilation for a Blazor WebAssembly application. Let’s go through the required steps one by one.
First, we need to install the .NET WebAssembly Build Tools using the Visual Studio Installer, or execute the following command in a shell:
dotnet workload install wasm-tools
Next, we open the project file of the Blazor WebAssembly project, and add the following configuration:
<PropertyGroup>
<RunAOTCompilation>true</RunAOTCompilation>
</PropertyGroup>
The RunAOTCompilation
setting enables native AOT compilation for Blazor WebAssembly applications.
Now, when publishing the application using the Release
configuration, we should get a single executable containing WebAssembly code.
You can compile and publish using the following command from the command line:
dotnet publish -c Release
You can also use Visual Studio and right-click publish the WebAssembly project.
Notice that compilation and publishing will take considerably longer than regular JIT-compiled Blazor WebAssembly applications. A simple project takes at least two to three minutes on a modern computer.
That’s also why AOT compilation is only performed when publishing the application and only in the Release
configuration.
If the application does not run as expected, it could be that trimming the .NET IL code after the AOT compilation is the problem. It’s a technique used to reduce the size of the _framework
folder.
You can disable trimming IL code in the project configuration with the following setting:
<PropertyGroup>
<RunAOTCompilation>true</RunAOTCompilation>
<WasmStripILAfterAOT>false</WasmStripILAfterAOT>
</PropertyGroup>
The WasmStripILAfterAOT
setting is true
by default, and you can set it to false
to prevent the trimming.
AOT promises faster startup times, lower memory footprint and larger application sizes. However, depending on the application code, startup time might not improve as much, or the application size might not grow as much compared to other applications. You have to try it with your application to see how the result is in a particular use case.
When using AOT, there are some limitations and tradeoffs we need to be aware of.
For example, we cannot dynamically load assemblies using Assembly.LoadFile
, use Reflection, such as .GetType().GetProperty("Name")
, or emit code using System.Reflection.Emit
.
Since the compiled executables include the runtime libraries, they considerably increase their size compared to JIT-compiled applications that run on the target system’s .NET runtime.
When shipping the .NET applications self-contained, the opposite is often true. The size decreases because much of the code is trimmed during compilation.
Another often overlooked disadvantage of AOT is that System.Linq.Expressions
always use the interpreted form instead of runtime-generated compiled code. It makes evaluating those expressions slower.
Some of the ASP.NET Core features aren’t supported with .NET 9. For example, Blazor Server and MVC are not supported, while Minimal APIs and SignalR are partially supported. However, this might change in the future. Learn more about it in the official Microsoft documentation about Native AOT in ASP.NET Core.
Suppose you want to support multiple platforms and use the cross-platform ability of .NET. In that case, you need to provide multiple executables, one for each target platform, making the build process and deployment more complex compared to sharing a portable .NET application.
However, we still get cross-platform advantages using AOT compilation for Blazor WebAssembly applications. WebAssembly is already a cross-platform format because the browsers provide the platform.
With modern .NET, we now have the option to continue using the tried and proven just-in-time (JIT) compilation or use native ahead-of-time (AOT) compilation for Blazor WebAssembly applications.
It depends on several factors, including your application code and deployment strategy, which of the two options makes the most sense for your specific situation.
Enabling AOT compilation for Blazor WebAssembly is as simple as adding the RunAOTCompilation
setting to the project file. However, be sure to install the .NET WebAssembly Build Tools on the machine you want to perform AOT compilation.
AOT compilation is slow and, therefore, only executed when publishing the application using the Release
configuration and not when starting the application in development mode.
There are tradeoffs when enabling AOT compilation that you should know. This article gives you a great starting point to try to figure out whether your project will benefit from AOT compilation or if JIT compilation remains the better strategy in the future.
From my experience, primarily cloud-native and highly scalable applications (such as .NET APIs) running in the cloud benefit from AOT. However, in some instances, it’s also beneficial for Blazor WebAssembly applications.
If you want to learn more about Blazor development, you can watch my free Blazor Crash Course on YouTube. And stay tuned to the Telerik blog for more Blazor Basics.
Claudio Bernasconi is a passionate software engineer and content creator writing articles and running a .NET developer YouTube channel. He has more than 10 years of experience as a .NET developer and loves sharing his knowledge about Blazor and other .NET topics with the community.