├── .gitignore ├── ConsoleApp ├── Program.cs └── ConsoleApp.csproj ├── StandaloneLibrary ├── Calculator.cs └── StandaloneLibrary.csproj ├── nuget.config ├── MinimalDotNetWasmNativeAOT.sln ├── README.md └── Directory.Build.targets /.gitignore: -------------------------------------------------------------------------------- 1 | .vs/ 2 | bin/ 3 | obj/ 4 | -------------------------------------------------------------------------------- /ConsoleApp/Program.cs: -------------------------------------------------------------------------------- 1 | // See https://aka.ms/new-console-template for more information 2 | Console.WriteLine("Hello, World!"); 3 | -------------------------------------------------------------------------------- /StandaloneLibrary/Calculator.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace MyApp; 4 | 5 | public static class Calculator 6 | { 7 | [UnmanagedCallersOnly(EntryPoint = "add")] 8 | public static int Add(int a, int b) => a + b; 9 | 10 | [UnmanagedCallersOnly(EntryPoint = "subtract")] 11 | public static int Subtract(int a, int b) => a - b; 12 | } 13 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ConsoleApp/ConsoleApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | exe 5 | net8.0 6 | enable 7 | enable 8 | true 9 | true 10 | false 11 | true 12 | wasi-wasm 13 | false 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /StandaloneLibrary/StandaloneLibrary.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | net8.0 6 | enable 7 | enable 8 | true 9 | true 10 | false 11 | true 12 | wasi-wasm 13 | false 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /MinimalDotNetWasmNativeAOT.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.002.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp", "ConsoleApp\ConsoleApp.csproj", "{5A9121E0-8590-4D2F-98FB-A2DBB2B7260A}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StandaloneLibrary", "StandaloneLibrary\StandaloneLibrary.csproj", "{441CB253-0F77-44F2-81F4-C24864CCE521}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {5A9121E0-8590-4D2F-98FB-A2DBB2B7260A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {5A9121E0-8590-4D2F-98FB-A2DBB2B7260A}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {5A9121E0-8590-4D2F-98FB-A2DBB2B7260A}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {5A9121E0-8590-4D2F-98FB-A2DBB2B7260A}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {441CB253-0F77-44F2-81F4-C24864CCE521}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {441CB253-0F77-44F2-81F4-C24864CCE521}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {441CB253-0F77-44F2-81F4-C24864CCE521}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {441CB253-0F77-44F2-81F4-C24864CCE521}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {BA44930B-C0E3-41DC-B0F6-3CCFE423B3DC} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minimal .NET -> WebAssembly via NativeAOT-LLVM 2 | 3 | This repo contains two examples. 4 | 5 | They are minimal in the sense of doing the smallest amount of stuff in the source code. However they are **not** minimal in terms of size - various things could be done to reduce the output size dramatically, such as stripping debug info or running `wasm-opt`. 6 | 7 | ## Prerequisites 8 | 9 | * .NET 8 SDK 10 | * To run the output on the command line, get [wasmtime](https://wasmtime.dev/) and put it on your PATH 11 | 12 | ## ConsoleApp 13 | 14 | The default "Hello World" app 15 | 16 | To build: 17 | 18 | * `cd ConsoleApp` 19 | * `dotnet build` (note: takes a few mins on the first run to acquire the SDKs, but is fast after) 20 | * You now have `bin/Debug/net8.0/wasi-wasm/publish/ConsoleApp.wasm` 21 | 22 | To run: 23 | 24 | * `wasmtime bin/Debug/net8.0/wasi-wasm/publish/ConsoleApp.wasm` 25 | 26 | ## StandaloneLibrary 27 | 28 | This produces a wasm module with an export called `add` you can invoke from some host environment. 29 | 30 | To build: 31 | 32 | * `cd StandaloneLibrary` 33 | * `dotnet build` 34 | * You now have `bin/Debug/net8.0/wasi-wasm/publish/StandaloneLibrary.wasm` 35 | 36 | Note that this is a "reactor" module, i.e., one with imports and exports but no entrypoint. So to run code from it, you have to call a named export and pass whatever parameters it wants, e.g., to run on the CLI: 37 | 38 | * `wasmtime bin/Debug/net8.0/wasi-wasm/publish/StandaloneLibrary.wasm --invoke add 123 456` 39 | * `wasmtime bin/Debug/net8.0/wasi-wasm/publish/StandaloneLibrary.wasm --invoke subtract 123 456` 40 | 41 | ... but more commonly, reactor modules are consumed by some hosting environment such as a web server that makes calls to your module's exports. 42 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 20.0 11 | https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/wasi-sdk-20.0.m-mingw.tar.gz 12 | https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/wasi-sdk-20.0-linux.tar.gz 13 | https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-20/wasi-sdk-20.0-macos.tar.gz 14 | $([System.IO.Path]::Combine("$([System.Environment]::GetFolderPath(SpecialFolder.UserProfile))", ".wasi-sdk", "wasi-sdk-$(WasiSdkVersion)")) 15 | 16 | 3.1.23 17 | https://github.com/emscripten-core/emsdk/archive/refs/tags/$(EmSdkVersion).zip 18 | $([System.IO.Path]::Combine("$([System.Environment]::GetFolderPath(SpecialFolder.UserProfile))", ".emsdk", "emsdk-$(EmSdkVersion)")) 19 | 20 | 21 | $(CopyWasmToOutputDependsOn); 22 | ObtainWasiSdk; 23 | PrepareBuildWasmInputs; 24 | BuildWasm; 25 | 26 | 27 | 28 | 29 | 30 | $(EmscriptenRoot) 31 | $(WasiSdkRoot) 32 | 33 | 34 | 35 | 36 | 37 | $(IntermediateOutputPath)\emsdk-temp 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | $(IntermediateOutputPath)\wasi-sdk-temp 60 | 61 | 62 | 63 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | --------------------------------------------------------------------------------