Tsonic GitHub
Edit on GitHub

Getting Started

Requirements

  • Node.js 22+
  • .NET 10 SDK
  • NativeAOT platform toolchain:
    • Linux: clang or gcc on PATH
    • macOS: Xcode Command Line Tools
    • Windows: Visual Studio C++ build tools

Source checkout for contributors

For Tsonic compiler work, the supported developer layout is a sibling checkout under one parent directory:

~/repos/tsoniclang/
  tsonic/
  runtime/
  core/
  dotnet/
  globals/
  js/
  nodejs/
  aspnetcore/
  efcore/
  efcore-sqlite/
  microsoft-extensions/

runtime is the only required sibling for the compiler's runtime DLL sync:

cd ~/repos/tsoniclang/runtime
dotnet build -c Release

cd ../tsonic
npm ci
npm run build
./test/scripts/run-all.sh

The full compiler gate builds NativeAOT outputs. If the platform linker is missing, the gate fails the NativeAOT preflight with the linker prerequisite instead of reporting compiler correctness failures.

The other siblings are used by tests and local package-wave development when present. Resolution is deterministic: a sibling package is used only if its expected package.json exists; otherwise installed npm packages are used.

The repo-local tsonic command is the workspace package in npm/tsonic, which forwards to @tsonic/cli. There should not be another root workspace package named tsonic.

Install

npm install -g tsonic

Create a default CLR workspace

mkdir hello-clr
cd hello-clr
tsonic init
tsonic run

Generated sample:

import { Console } from "@tsonic/dotnet/System.js";

export function main(): void {
  Console.WriteLine("Hello from Tsonic.");
}

This is the simplest possible CLR-first workspace:

  • default surface is clr
  • CLR APIs are imported explicitly
  • the default generated project is an executable

Create a JS workspace

mkdir hello-js
cd hello-js
tsonic init --surface @tsonic/js
tsonic run
export function main(): void {
  const value = JSON.parse<{ x: number }>('{"x": 1}');
  console.log(JSON.stringify(value));
}

This switches the workspace ambient world to @tsonic/js.

Add Node modules

tsonic add npm @tsonic/nodejs
import * as fs from "node:fs";
import * as path from "node:path";

export function main(): void {
  const file = path.join("src", "App.ts");
  console.log(file, fs.existsSync(file));
}

This is the package model in action:

  • ambient world from @tsonic/js
  • Node-style modules from @tsonic/nodejs

Add CLR packages

tsonic add nuget Microsoft.Extensions.Logging 10.0.0
tsonic restore

Then import the generated binding package:

import { ILogger_1 } from "@tsonic/microsoft-extensions/Microsoft.Extensions.Logging.js";

First-party source packages

tsonic init produces source-package-ready projects by default. Each project gets a tsonic.package.json manifest.

Example:

{
  "schemaVersion": 1,
  "kind": "tsonic-source-package",
  "surfaces": ["@tsonic/js"],
  "source": {
    "exports": {
      ".": "./src/App.ts",
      "./index.js": "./src/App.ts"
    }
  }
}

Installed source packages with that manifest are compiled transitively as part of the same Tsonic program.