NetWasm
Independent .NET for WebAssembly and WASI.
Write C#. Deploy small, standalone Wasm.
NetWasm is an independent compiler, CoreLib, runtime and SDK that compiles C#
and .NET CIL directly to WebAssembly. It does not carry the desktop CLR, Mono,
dotnet.js or application DLLs into production.
Compile C# to WebAssembly · Try it in the browser · Install and build · See supported APIs · Read the documentation
C# now beats Rust on Wasm size
NetWasm + precise GC: 39,151 bytes
Rust 1.99.1 size-oriented profile: 52,569 bytes
For the same print-42 workload, NetWasm's result includes the runtime and
precise Compact garbage collector. Both artifacts are standalone WASI Preview 2
Components, built the way a developer gets them out of the box: each
toolchain's standard library and ordinary build, with no hand-added tools.
25.5% smaller raw. 19.8% smaller with Brotli-11.
| Toolchain | Raw Wasm | Brotli 11 | Comparison role |
|---|---|---|---|
| NetWasm, Compact GC | 39,151 B | 13,355 B | WASI P2 Component; precise GC included |
| Rust 1.99.1, size profile | 52,569 B | 16,648 B | Matched WASI P2 Component reference |
Kotlin 2.4.0 wasmWasi | 79,293 B | 22,655 B | P1 context; GC supplied by the host engine |
| TinyGo 0.39.0 | 93,043 B | 26,931 B | P1 context; precise GC configured |
Rust is the headline comparison because it uses the same workload and output form. Kotlin/Wasm and TinyGo show the broader footprint landscape, but their targets and runtime boundaries differ. This is a measured workload/profile result, not a language-wide size ranking.
NetWasm's figure is the template application's default Release publish. Rust's
adds Cargo's documented size settings (opt-level = "z", LTO, one codegen
unit, panic = "abort" and stripped symbols) to an ordinary cargo build.
Binaryen's wasm-opt is not a post-processing step here: it is part of NetWasm's
compiler pipeline and runs in every ordinary build, with nothing to install or
enable. Rust's wasm32-wasip2 output is a component, so applying wasm-opt to it
means extracting the core module, optimizing it and re-encoding the component
by hand. Done that way for Rust 1.99.1, with every custom section stripped as
well, the component is 40,488 bytes (14,390 with Brotli-11): still larger than
NetWasm's ordinary publish.
Both languages can go lower, but that is a different class of program: Rust
with no_std, or C# with a minimal library such as bflat's zero library, each
giving up most of the standard library and, for C#, the garbage collector.
Exact sources, versions, settings and reproduction steps
Faster than Mono AOT and the desktop JIT
NetWasm runs our allocation and garbage-collection benchmarks faster than Mono AOT, compiled from the same C# source and run by the same Wasmtime. It is also faster than desktop .NET 11's JIT on all three, although desktop .NET runs native code outside any Wasm sandbox:
| Workload | NetWasm, NetWasmGC | Mono AOT, .NET 11 | Desktop .NET 11 JIT | Desktop .NET 11 NativeAOT |
|---|---|---|---|---|
| Full transaction | 28.2 ms | 56.9 ms | 50.2 ms | 32.6 ms |
| Retained object graph | 4.8 ms | 14.3 ms | 9.7 ms | 7.4 ms |
| UI churn | 7.7 ms | 27.0 ms | 8.7 ms | 6.7 ms |
These are medians of 15 runs on an Apple M1 Max, with every Wasm build under
Wasmtime 49.0.3 and each run checked against desktop .NET's result. Mono AOT is
Microsoft's wasi-wasm target on .NET 11 RC3 in Release, with Wasm SIMD.
NetWasmGC takes 51% to 72% less time than Mono AOT and 11% to 51% less than the
desktop JIT. It is also faster than desktop NativeAOT on the full transaction
and the retained object graph; NativeAOT is still faster on UI churn.
Without allocation in the timed region, NetWasm takes 41% less time than Mono AOT on the transaction's library work and 6% less on a quicksort, and matches it on n-body and CRC-32 kernels. Desktop .NET is faster on the library work and the quicksort without allocation.
Versions, settings, desktop figures and reproduction
More than Hello World
Hello World is just the smallest example. NetWasm builds real applications too.
C# 15 · JavaScript modules and Web Workers · static .a interop · WIT/WASI P2 ·
expression trees · HTTP/JSON/XML/Regex · reflection-free DI ·
TUnit/VSTest · wasm64 · three allocation strategies
Today NetWasm can:
- build, run, publish and test projects through the ordinary
dotnetCLI; - publish C# exports as typed JavaScript modules that run in the page or in a Web Worker;
- link compatible Wasm
.aarchives and generate bounded C ABI marshalling; - generate C# bindings from WIT and produce standalone WASI P2 Components;
- construct and inspect expression trees without general runtime reflection;
- use independently packaged LINQ, HTTP, JSON, XML, Regex, Hashing and dependency-injection libraries;
- dual-target libraries for
netwasm0.1and desktop .NET.
The profile is deliberately smaller than desktop .NET. It already supports complete applications, portable libraries, test projects and browser modules.
Built with NetWasm:
- DoomSharp by David Fowler, a
clean-room classic WAD game engine in C#, has a NetWasm browser build you can
play. Built with NetWasm
0.11.1 at
O3with NetWasmGC (davidfowl/doomsharp#60), it renders 120 frames 1.28 times faster than DoomSharp's Blazor AOT build in headless Chromium (1,750 ms against 2,236 ms on an Apple M1 Max), from 0.69 MiB of Brotli-compressed files against 3.67 MiB. - NetWasm.UI by Mattias Engman,
a signal-driven C# UI framework for the browser in the style of Solid and
Svelte: components build their DOM once, and signals patch only the nodes
that changed. Its
.nwuitemplates compile at build time, and its website is a NetWasm.UI app. - FUI-CS, the C# SDK for EffinDOM's retained-mode canvas UI, compiles every route of its live demo to WebAssembly with NetWasm.
Try it
Install .NET SDK 10.0.303 or newer, then run:
dotnet new install NetWasm.Templates
dotnet new netwasm-app -n HelloNetWasm
cd HelloNetWasm
dotnet restore
dotnet run
dotnet publish -c Release -o publish/localRun the published component with a compatible WASI Preview 2 host, such as Wasmtime:
wasmtime run publish/local/HelloNetWasm.wasm42The published component is self-contained with respect to .NET. A WebAssembly host is still required. Build with .NET; run without .NET.
Projects using C# 15 select the .NET 11 SDK; earlier language versions can stay on .NET 10. The SDK restores its pinned Node, LLD and Binaryen tools through NuGet, so an ordinary app does not require a separate Emscripten, Git or Python installation. See the complete quickstart for host, restore, Windows, browser and dual-targeting details.
How it stays small
Roslyn produces CIL. NetWasm compiles the reachable program directly to Wasm, specializes its generics and links only the runtime support it needs. The linked Wasm component is the program.
- Closed-world compilation. Unreachable code, imports and runtime features do not enter the artifact.
- A smaller platform contract. NetWasm supports portable APIs instead of reproducing every desktop runtime assumption.
- No broad runtime reflection. Runtime type names, dynamic assembly loading
and the
dynamickeyword are outside the profile. - Generated boundaries. JSON, DI, WIT and native interop use known contracts rather than runtime discovery.
- Optional data and diagnostics. Timezone data, rich stack traces and other deployment data are included only when selected.
- One collector. Only the chosen allocation strategy is linked.
This is a new .NET platform, not desktop .NET squeezed into a Wasm sandbox. That boundary is what makes native-Wasm-sized C# possible.
How JSON and optional features affect size
Choose your allocation strategy
Compact is the default: a size-first, precise, non-moving mark-and-sweep collector. It traces compiler-published roots and reference layouts without conservative scanning. Its name refers to implementation size, not moving heap compaction.
| Strategy | Best fit | Raw component | Brotli 11 |
|---|---|---|---|
| Compact (default) | Small applications and low-churn UI | 39,151 B | 13,355 B |
| NetWasmGC | Higher allocation throughput | 42,298 B | 14,778 B |
| Bump | Bounded, one-shot jobs | 35,090 B | 11,650 B |
Select a strategy in the executable project's .csproj:
<PropertyGroup>
<NetWasmGarbageCollector>NetWasm</NetWasmGarbageCollector>
</PropertyGroup>Leaving the property unset selects Compact. Libraries remain collector-neutral; all four backends are packaged for wasm32 and wasm64. See Choosing a garbage collector for the tradeoffs and diagnostics.
Libraries should travel upward
A library written for the smaller NetWasm profile should also be useful on desktop .NET, rather than trapped in a Wasm-only ecosystem.
dotnet new netwasm-lib -n MyLibraryThe template targets netwasm0.1 and net10.0, producing NuGet assets for
both platforms. This is explicit multi-targeting, not a promise that arbitrary
desktop packages work in reverse.
NetWasm.Libraries provides the
ported framework and extensions packages.
TUnit-NetWasm provides ordinary
dotnet test integration through the generic VSTest bridge.
How does this compare to conventional .NET Wasm?
For an empty-UI Blazor WebAssembly application that prints 42, with AOT,
managed trimming, IL stripping, invariant globalization and size-focused native
linking enabled:
| AOT build | Uncompressed Wasm | Relative to NetWasm |
|---|---|---|
| NetWasm, Compact GC | 39,151 B | 1× |
| Blazor WebAssembly, .NET 10 | 12,276,684 B | 314× |
| Blazor WebAssembly, .NET 11 RC1 | 15,289,241 B | 391× |
The Blazor total includes its native runtime and required Webcil assemblies; NetWasm includes its runtime, garbage collector and WASI component. This is a console component versus an empty-UI application, not an equivalent-framework comparison or a claim about every .NET workload.
Exact Blazor versions, settings and reproduction
Current boundary
NetWasm is pre-1.0. Its supported profile is usable today and covered by published qualification, but source, package, target-profile and ABI contracts may change between minor releases.
The normal portable output is a wasm32 WASI Preview 2 Component. Raw wasm32, raw wasm64 and JavaScript-host integration are advanced alternatives; wasm64 Components remain blocked by upstream tooling.
Runtime reflection, dynamic, runtime assembly loading and managed threading
are outside the profile. General System.IO.File/Directory/FileStream, raw
sockets and subprocess APIs are not yet implemented. Existing NuGet packages
work only when their reachable code and API dependencies fit the supported
profile.
Supported APIs and limitations · Documentation index
Road to 1.0
NetWasm 1.0 means a useful, documented and stable smaller platform. It does not mean “desktop .NET, but in Wasm.” The main themes are:
- WASI 0.3, with broader WASI coverage;
- globalization modes and optional culture data;
- expression-tree completion;
- a faster incremental development loop (done in NetWasm 0.11.0);
- a stable
netwasm1.0platform and runtime contract; - production diagnostics and broader host qualification.
New in NetWasm 0.11.2: faster delegate calls and span loops, and a
compiler that needs half the memory: a delegate call takes 38% less time, a
multicast invoke 44% less, and a fold over a span runs as Wasm SIMD vectors.
Compiling DoomSharp's browser build at O3 peaks at 1.5 GB where 0.11.1 took 3.4 GB.
Release notes. NetWasm 0.11.0
made Debug builds keep each method's compiled code, so a rebuild compiles only
what an edit changes: on DoomSharp's browser build, an edit to one method
rebuilds in 1.6 s where 0.10.8 took 29.3 s, and dotnet watch runs on NetWasm
projects. Release notes
In progress: broader compatibility with existing .NET Standard 2.1 library binaries and NuGet assets. The target is to consume libraries unchanged when their reachable code fits the NetWasm profile, without carrying unused unsupported paths into the application.
This does not turn NetWasm into desktop .NET. Reachable general runtime
reflection, dynamic, runtime assembly loading, managed threading, unsupported
host APIs and named-culture globalization remain outside the initial
compatibility slice and fail at compile time.
Packages
Compiler, SDK, runtime and hosting packages
NetWasm.Compiler.Browser · NetWasm.Compiler.Tasks · NetWasm.Hosting · NetWasm.Hosting.Build · NetWasm.Ref · NetWasm.Runtime.Pack · NetWasm.Runtime.Wasm32 · NetWasm.Runtime.Wasm64 · NetWasm.Sdk · NetWasm.Templates · NetWasm.Testing.VSTest · NetWasm.Toolchain · NetWasm.Wit.Bindings.Tool
License
CoreLib, runtime libraries, templates and generated support code are MIT, subject to preserved upstream notices. The compiler and developer tooling use the NetWasm Community License 1.0. The Compact and NetWasmGC collectors are Apache-2.0.
The Community License is free for individuals, education, open-source projects, evaluation, and small and medium-sized businesses: any organization with fewer than 250 employees and less than USD 10,000,000 in annual revenue. Larger organizations pay for internal commercial use by sponsoring a matching GitHub Sponsors tier, from USD 149 a month for five developers up to USD 2,499 a month for unlimited developers. OEM and Commercial Offering use require a separate agreement. Generated applications may use their authors' chosen licenses. The sponsorship itself is the license: there is no license key, sign-in, activation, telemetry or technical enforcement.
Licensing guide · Third-party notices · Commercial licensing: zion.sati@netwasm.com
Bring a workload
Start with Console.WriteLine(42), then try the smallest real workload where
deployment size, sandboxing or portability matters. If something is missing,
open an issue. Concrete workloads
are how the 1.0 support boundary gets decided.