一个只输出42的 C# 程序,编译成 WebAssembly 要多大?
Console.WriteLine(42);用 .NET 11 RC1 的 Blazor WebAssembly AOT 和 NetWasm 各测一遍,两边应用代码都只输出这个数字:
| 编译方案 | 未压缩 Wasm 体积 | 换算 |
|---|---|---|
| NetWasm | 84,513 字节 | 82.5 KiB |
| .NET 11 RC1 Blazor WebAssembly AOT | 15,289,241 字节 | 14.58 MiB |
差了 181 倍。NetWasm 的 82.5 KiB 里已经包含运行时和精确 GC。
不是没开裁剪。Blazor 用的是 Release 发布,开了 AOT、完整托管代码裁剪、AOT 后 IL 剥离,原生编译和链接用-Oz,还开了InvariantGlobalization。
14.58 MiB,就是全开之后的结果。
测试范围:Blazor 用空根组件,统计浏览器实际加载的原生 Wasm 和 Webcil 程序集;NetWasm 输出 WASI Preview 2 组件。两边都按未压缩体积算,不含 JavaScript、HTML 和 Wasm 宿主。这里比的是同一段输出逻辑在两种技术栈下的 Wasm 体积,不是 UI 框架功能。Blazor 用的是 Mono WebAssembly AOT,不是 CoreCLR Native AOT。
具体版本、发布参数和验证过程在体积测试与复现文档,核心配置附在文末。
真正的问题是:应用只要输出一个整数,为什么还得带这么多东西?
一、裁剪之后为什么还是这么大
裁剪器能删代码,但改不了基础库和运行时的结构。
一段代码能不能删,看的是现有实现的依赖关系,不是应用有没有直接调用。应用用不到,但框架、基础库或运行时还依赖它,裁剪器就得留下。
“应用不需要”和“当前实现能删”是两回事。
微软的官方文档也说了,Blazor AOT 之后仍然需要托管程序集,用来支持反射元数据和部分运行时功能。只要还有这些运行时需求,裁剪器就不可能全删。
所以想缩小最小应用,光调发布参数没用,得看 CoreLib、BCL、运行时和框架之间怎么组织。哪些依赖能拆、哪些工作能挪到编译时、哪些能力不该默认提供,都得重新想。
NetWasm 就是从这儿下手的。没继续裁剪同一套实现,而是重建了编译器、CoreLib 和运行时:先把底层硬依赖减下来,再让编译器只保留应用实际用到的部分。
二、为什么要自己重建
这个想法不是看到这次体积对比才有的。
2008 年就试过自己写分代并发垃圾回收器,配 Borland C++ 编译器,探索带自动内存管理的独立程序。后来做过基于 C++ 模板元编程的托管编程模型,让 Boehm GC 以精确模式跑,用 GCC 插件生成 GC 映射。
想要的一直很简单:保留 C# 的泛型、异常处理和自动内存管理,同时像写本机程序那样,对最终产物有更多控制。
后来看到 Kotlin/Wasm,又开始琢磨:既然能保留一门语言、针对新目标平台设计运行方式,C# 为什么不行?
想试的不只是让 C# 多一种输出格式,而是围绕 WebAssembly 重新设计一套基础库和运行时。这也是英文原文里的初衷。
三、编译器、CoreLib、运行时一起设计
NetWasm 没有重新设计 C# 语法。前端还是 Roslyn,把 C# 编译成 CIL。之后由 NetWasm 接手:
C# 源码 ↓ Roslyn CIL ↓ NetWasm:可达性分析、泛型特化、编译 WebAssembly + 按需链接的 CoreLib 与运行时支持 ↓ WASI 组件CoreLib 提供基础类型和核心 API。NetWasm 用的是独立实现的 CoreLib,和编译器、运行时一起设计,而不是先沿用现有实现、最后再想办法压缩。
编译器从入口出发,分析哪些代码和类型可能被用到,生成 Wasm,再链接所需支持代码。最终产物不依赖 CoreCLR 或 Mono,也不带dotnet.js和独立应用 DLL。
小体积不是靠砍 GC 换来的。NetWasm 用精确模式的 Boehm GC,编译器提供精确根信息。前面那 82.5 KiB 已经包含 GC 支持,不需要手动管理对象生命周期。
目标也不只是浏览器。当前主要产物是 WASI Preview 2 组件,系统能力由兼容宿主提供;浏览器环境通过适配层接入。
开发还是用 .NET SDK,生成的组件交给兼容 Wasm 宿主执行,无需预装 .NET。架构和宿主要求见项目文档。
四、用到什么才带什么
这一点不能只靠最后裁一刀,得落实到库和运行时的设计里。
通用运行时反射,明确不支持
NetWasm 用闭世界编译:程序需要的代码和类型在编译时确定,不在运行时加载新程序集。
通用运行时反射、dynamic、运行时程序集加载,都是明确不支持的。这是设计取舍,不是以后要补的功能。
能在编译时做完的事,尽量提前做。序列化用源生成器生成成员访问代码;依赖注入提前生成对象创建逻辑。调用关系在编译时就是明确的,不用等启动后再扫类型、找成员。
库功能变多,不等于每个程序都变大
只解析 JSON、只做源生成序列化、做类型化反序列化,需要的代码不一样。引用同一个包,不代表包里所有实现都得打进去。
时区数据也一样。只用 UTC 的程序不需要时区数据库;需要本地时区行为时,再显式部署资源。具体见体积与按需链接说明。
希望 NetWasm 支持的库越来越多,但没用到的部分不该成为每个应用的固定开销。
不支持的地方,编译时就说清楚
另一项要完善的是编译阶段兼容性检查。
如果实际保留的调用路径依赖了不支持的功能,编译器应该直接指出:问题在哪段代码、由哪个依赖引入、有没有源生成等替代方案。
检查也要针对实际用到的路径。不能因为某个 NuGet 包里有一段没用到的反射代码,就把整个包拒掉。
限制可以明确,但不能只丢一句“不支持”,让人自己猜。
五、不止 Hello World:正则应用压缩后约 325 KB
光靠 Hello World 说体积,还不够。也做了一个能直接用的应用:Regex Storm with NetWasm。
它把 .NET 正则表达式引擎编译成 Wasm,在浏览器里提供匹配、捕获组查看、替换和分割。用的是真正的 .NET 正则引擎,不是拿 JavaScript 正则模拟。
这个应用的 Wasm 文件经 Brotli 压缩后约 325 KB。这里只算 Wasm 文件,不含页面其他资源;开头 Hello World 统计的是未压缩体积。
正则和待处理文本都在浏览器本地处理,不上传后端。整个应用可以部署成静态网站,用户打开的是已经编译好的程序,不用加载 C# 编译器。
在线体验:Regex Storm with NetWasm
想做的就是让已有的 C# 代码在更多环境里直接跑,不必为了换个运行环境就用另一门语言重写。
另外还有 NetWasm Playground。可以直接编辑、编译、运行 C#,编译也在浏览器本地完成,不会把代码提交到服务器。打开就能试,不用先装工具链。
六、本地开发还是熟悉的 dotnet 命令
按快速入门文档装好支持的 .NET SDK 后,直接创建项目:
dotnet newinstallNetWasm.Templates dotnet new netwasm-app-nNetWasmAppcdNetWasmApp dotnet restore dotnet run-cRelease dotnet publish-cRelease-opublish/local模板程序输出42。普通应用需要的宿主工具会在 NuGet 还原时自动下载,不用手动配 Emscripten、LLVM/LLD 或 Binaryen。
装好兼容的 Wasmtime 后,直接运行发布产物:
wasmtime run publish/local/NetWasmApp.wasm要复现开头的精确字节数,请用测量文档里记录的项目名、SDK 和工具链版本。
七、目前能用多少
目前 LINQ、HTTP、JSON、XML、正则表达式、哈希、无反射依赖注入等库已有移植,也能用普通dotnet test跑 TUnit。不同库支持范围不一样,具体看项目文档。
下一步继续围绕实际用到的库和应用完善兼容性,包括推进 .NET Standard 2.1 支持,让更多现有 NuGet 包能用。
但支持某个目标框架,不代表该框架下所有包都能直接跑。能不能跑,还得看应用实际用了包里哪些 API、这些代码依赖哪些运行时行为。依赖通用反射的代码,仍然得换实现。
NetWasm 目前还没到 1.0,API、包和 ABI 都可能调整。通用托管文件和目录 API、底层 Socket、子进程、托管多线程目前还不支持。支持异步任务,不代表支持在线程池上并行执行。移植前先查支持清单。
许可方面,CoreLib、运行时库和模板等用 MIT;编译器和开发工具用 NetWasm Community License,源码可看但不是 MIT。第三方代码保留各自许可,具体见许可文件。
写在最后
想保留的是 C# 的语言能力和开发体验,想改变的是它必须依赖哪套运行时、发布时要带多少东西。
这次体积测试说明,重建 CoreLib 和运行时之后,一个保留 GC 的 C# 程序,基础体积能压得更小。接下来要做的,是在这个基础上把库、工具和实际应用做好,同时别让新功能变成所有程序的负担。
欢迎拿一段实际用的 C# 代码来试。能跑起来的、移植不了的、看不懂的报错,都能帮我们确定下一步做什么。
喜欢 C#,也希望它有更多运行和部署的选择。这就是 NetWasm 想做的事。
项目主页:netwasm.com
源码与文档:zion-sati/NetWasm
在线编译:NetWasm Playground
应用示例:Regex Storm with NetWasm
附:Blazor 对照测试配置
Blazor 对照测试记录于 2026 年 9 月 30 日,.NET 11 RC1,Release 发布。核心设置:
<PropertyGroup><RunAOTCompilation>true</RunAOTCompilation><PublishTrimmed>true</PublishTrimmed><TrimMode>full</TrimMode><WasmStripILAfterAOT>true</WasmStripILAfterAOT><InvariantGlobalization>true</InvariantGlobalization><WasmNativeStrip>true</WasmNativeStrip><EmccCompileOptimizationFlag>-Oz</EmccCompileOptimizationFlag><EmccLinkOptimizationFlag>-Oz</EmccLinkOptimizationFlag></PropertyGroup>这不是所有 .NET Wasm 应用的理论最小体积,是本文程序、配置和工具链下的实测结果。完整项目配置、版本和浏览器验证记录见体积测试与复现文档。