Tsonic GitHub
Edit on GitHub

Build Output

Understanding what Tsonic generates during compilation.

Build Pipeline

TypeScript → IR → C# → .NET → Native Binary
    │         │     │      │        │
  Parse    Build  Emit  Publish   Copy
  1. Parse: TypeScript Compiler API reads source files
  2. Build IR: Create intermediate representation
  3. Emit C#: Generate C# code from IR
  4. Publish: Run dotnet publish (NativeAOT by default for executables)
  5. Copy: Copy binary to out/ directory

Directory Structure

After building:

workspace/
├── tsonic.workspace.json
├── libs/
└── packages/
    └── my-app/
        ├── tsonic.json
        ├── src/
        │   └── App.ts           # Your source
        ├── generated/           # Generated C# code
        │   ├── src/
        │   │   └── App.cs       # C# from your TypeScript
        │   ├── Program.cs       # Entry point wrapper
        │   ├── tsonic.csproj    # .NET project file
        │   ├── bin/             # .NET build output
        │   └── obj/             # .NET intermediate files
        └── out/
            └── my-app           # Final executable

Generated Files

C# Source Files

Each .ts file becomes a .cs file:

src/App.ts          → generated/src/App.cs
src/utils/Math.ts   → generated/src/utils/Math.cs
src/models/User.ts  → generated/src/models/User.cs

__tsonic_json.g.cs (Conditional)

Generated only when your code uses JsonSerializer. Contains NativeAOT-compatible JSON serialization context:

// __tsonic_json.g.cs
[JsonSerializable(typeof(User))]
[JsonSerializable(typeof(List<User>))]
internal partial class __TsonicJsonContext : JsonSerializerContext { }

internal static class TsonicJson
{
    internal static readonly JsonSerializerOptions Options = new()
    {
        TypeInfoResolver = __TsonicJsonContext.Default
    };
}

This file is automatically generated based on types used with JsonSerializer.Serialize() and JsonSerializer.Deserialize<T>() calls in your code.

Program.cs

Entry point wrapper that calls your main():

// Program.cs
public static class Program
{
    public static void Main(string[] args)
    {
        global::MyApp.App.main();
    }
}

For async main:

public static class Program
{
    public static async Task Main(string[] args)
    {
        await global::MyApp.App.main();
    }
}

tsonic.csproj

.NET project file with NativeAOT configuration:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <RootNamespace>MyApp</RootNamespace>
    <AssemblyName>app</AssemblyName>
    <PublishAot>true</PublishAot>
    <PublishSingleFile>true</PublishSingleFile>
    <PublishTrimmed>true</PublishTrimmed>
    <StripSymbols>true</StripSymbols>
  </PropertyGroup>
</Project>

Output Types

Executable (Default)

Single native binary:

packages/<project>/out/<name>      (Linux/macOS)
packages/<project>/out/<name>.exe  (Windows)

Configuration:

{
  "output": {
    "type": "executable"
  }
}

Library

Managed (.NET) library

Standard managed library build:

dist/
├── net10.0/
│   ├── MyLib.dll
│   ├── MyLib.xml     # Documentation
│   └── MyLib.pdb     # Symbols
└── net8.0/
    └── ...           # Multi-target

Library builds also emit shippable CLR bindings under:

dist/tsonic/bindings/

NativeAOT (native library)

When output.nativeAot: true for a library, Tsonic runs dotnet publish -r <rid> and copies the full publish output into dist/:

dist/
└── net10.0/
    ├── MyLib.dll                  # Managed DLL (copied for reflection/bindings)
    └── linux-x64/
        └── publish/
            ├── libMyLib.so        # Native shared library (Linux)
            ├── MyLib.pdb          # Optional symbols (if emitted)
            └── ...                # Runtime/native deps as produced by dotnet publish

Notes:

  • The native output is RID-specific, so it is placed under dist/<tfm>/<rid>/publish/.
  • Use output.nativeLib to pick "shared" (default) vs "static".

Configuration:

{
  "entryPoint": "src/index.ts",
  "output": {
    "type": "library",
    "targetFrameworks": ["net10.0", "net8.0"],
    "nativeAot": false
  }
}

NativeAOT library example:

{
  "output": {
    "type": "library",
    "targetFrameworks": ["net10.0"],
    "nativeAot": true,
    "nativeLib": "shared"
  }
}

Build Options

NativeAOT Settings

These defaults apply to output.type: "executable" unless specified in tsonic.json.

Option Default Description
nativeAot true Enable NativeAOT publish for executables
singleFile true Single executable (NativeAOT executables only)
trimmed true Remove unused code (NativeAOT executables)
stripSymbols true Remove debug info
selfContained true Include runtime

For output.type: "library", only these keys apply:

Option Default Description
nativeAot false Enable NativeAOT publish for libraries
nativeLib shared Native library kind (shared or static)

Optimization

{
  "optimize": "speed" // or "size"
}
Mode Binary Size Performance
speed Larger Faster
size Smaller Slightly slower

Binary Size

Typical sizes for "Hello World":

Platform Size
Linux x64 ~3 MB
macOS ARM64 ~3 MB
Windows x64 ~3.5 MB

Size increases with:

  • More code
  • More .NET dependencies
  • Disabled trimming
  • Debug symbols

Keeping Build Artifacts

For debugging, keep generated files:

tsonic build --keep-temp

Or inspect manually:

tsonic generate             # Generate C# only
cd packages/<project>/generated
dotnet build            # Build manually

Cross-Compilation

Build for different platforms:

# Linux x64
tsonic build src/App.ts --rid linux-x64

# macOS ARM64
tsonic build src/App.ts --rid osx-arm64

# Windows x64
tsonic build src/App.ts --rid win-x64

Cross-compilation requires .NET SDK with target runtime.

Gitignore

Recommended .gitignore:

# .NET build artifacts (per-project)
packages/*/generated/bin/
packages/*/generated/obj/

# Output executables
packages/*/out/

# .NET artifacts
bin/
obj/

# Dependencies
node_modules/

# Internal tooling artifacts (restore scratch, caches)
.tsonic/

Troubleshooting Builds

Check Generated C#

tsonic generate src/App.ts
cat generated/src/App.cs

Build Manually

cd generated
dotnet build
dotnet run

Verbose Output

tsonic build src/App.ts --verbose

Shows:

  • Files being processed
  • Generated file paths
  • dotnet command output