
一个只输出42的 C# 程序编译成 WebAssembly 要多大Console.WriteLine(42);用 .NET 11 RC1 的 Blazor WebAssembly AOT 和 NetWasm 各测一遍两边应用代码都只输出这个数字编译方案未压缩 Wasm 体积换算NetWasm84,513 字节82.5 KiB.NET 11 RC1 Blazor WebAssembly AOT15,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.0API、包和 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 RC1Release 发布。核心设置PropertyGroupRunAOTCompilationtrue/RunAOTCompilationPublishTrimmedtrue/PublishTrimmedTrimModefull/TrimModeWasmStripILAfterAOTtrue/WasmStripILAfterAOTInvariantGlobalizationtrue/InvariantGlobalizationWasmNativeStriptrue/WasmNativeStripEmccCompileOptimizationFlag-Oz/EmccCompileOptimizationFlagEmccLinkOptimizationFlag-Oz/EmccLinkOptimizationFlag/PropertyGroup这不是所有 .NET Wasm 应用的理论最小体积是本文程序、配置和工具链下的实测结果。完整项目配置、版本和浏览器验证记录见体积测试与复现文档。