Skip to content

s97712/BlazorTS

Repository files navigation

BlazorTS

πŸš€ A tool for seamless Blazor-TypeScript integration


BlazorTS is a source generator library that uses Tree-sitter syntax tree parsing to analyze TypeScript code and automatically generate strongly typed C# wrappers, allowing Blazor applications to call TypeScript functions directly without hand-written JavaScript interop glue.

✨ Features

  • πŸ”„ Compile-time generation: Analyze TypeScript files and generate C# wrappers automatically
  • 🎯 Type safety: Strongly typed C# APIs for exported TypeScript functions
  • πŸš€ Zero configuration: Minimal setup, works out of the box
  • πŸ”§ Automatic registration: Generated interop services are registered through AddBlazorTSScripts()
  • 🌳 Precise parsing: Uses Tree-sitter for accurate TypeScript syntax handling

πŸ“¦ Installation

Core Libraries

NuGet Package Description
BlazorTS Core runtime library
BlazorTS.SourceGenerator Source generator library
BlazorTS.Assets Asset management with dynamic URLs
Microsoft.TypeScript.MSBuild TypeScript compilation support

Installation and Configuration

dotnet add package BlazorTS
dotnet add package BlazorTS.SourceGenerator
dotnet add package BlazorTS.Assets # optional
dotnet add package Microsoft.TypeScript.MSBuild # optional
<ItemGroup>
  <AdditionalFiles Include="**/*.razor.ts" Exclude="**/node_modules/**" />
  <AdditionalFiles Include="**/*.entry.ts" Exclude="**/node_modules/**" />
</ItemGroup>

πŸš€ File Naming Conventions

1. Razor Component Scripts (.razor.ts)

These files are bound to a specific Razor component.

  • Naming: MyComponent.razor.ts must pair with MyComponent.razor
  • Generated output:
    • a partial class for the component
    • a top-level *TSInterop service for that component
    • an auto-injected Scripts property on the component
  • Usage: call exported TypeScript functions through the generated Scripts property

Example

// Components/Pages/Counter.razor.ts
export function increment(count: number): number {
    return count + 1;
}
@page "/counter"

@code {
    private int currentCount = 0;

    private async Task HandleClick()
    {
        currentCount = await Scripts.increment(currentCount);
    }
}

Conceptually, BlazorTS generates code like this:

public partial class Counter
{
    [Inject] public CounterTSInterop Scripts { get; set; } = null!;
}

public class CounterTSInterop(ScriptBridge invoker)
{
    private readonly string url = invoker.ResolveNS("MyApp.Components.Pages.Counter", ".razor");

    public async Task<double> increment(double count)
    {
        return await invoker.InvokeAsync<double>(url, "increment", new object?[] { count });
    }
}

2. Standalone Feature Modules (.entry.ts)

These files define reusable TypeScript modules that can be injected as services.

  • Naming: my-utils.entry.ts or api.entry.ts
  • Generated output: a normal C# service class
  • Usage: register with AddBlazorTSScripts() and inject where needed
// Services/Formatter.entry.ts
export function formatCurrency(amount: number): string {
    return `$${amount.toFixed(2)}`;
}
builder.Services.AddBlazorTSScripts();
@inject TestApp.Services.Formatter Formatter

<p>@await Formatter.formatCurrency(123.45)</p>

Configure tsconfig.json

{
  "compilerOptions": {
    "noImplicitAny": false,
    "noEmitOnError": true,
    "removeComments": false,
    "target": "es2015",
    "rootDir": ".",
    "outDir": "wwwroot/js"
  },
  "include": [
    "**/*.razor.ts",
    "**/*.entry.ts"
  ]
}

Register Services

using BlazorTS.SourceGenerator.Extensions;

builder.Services.AddBlazorTS();
builder.Services.AddBlazorTSScripts();

πŸ› οΈ Custom Path Resolution

By default, BlazorTS maps a logical component or module name to /js/....

builder.Services.AddBlazorTS((type, suffix) =>
{
    var path = type.FullName!.Replace('.', '/');
    return $"/scripts/{path}{suffix}.js";
});
public class CustomResolver : INSResolver
{
    public string ResolveNS(Type tsType, string suffix)
    {
        var path = tsType.FullName!.Replace('.', '/');
        return $"/lib/{path}{suffix}.js";
    }

    public string ResolveNS(string fullName, string suffix)
    {
        var path = fullName.Replace('.', '/');
        return $"/lib/{path}{suffix}.js";
    }
}

builder.Services.AddBlazorTS<CustomResolver>();

Suffix Parameter

  • Razor components (.razor.ts) use suffix ".razor"
  • Entry modules (.entry.ts) use suffix ".entry"
  • Other module styles can use any custom suffix

πŸ”§ Supported Types

TypeScript C# Parameter Return Type
string string Task<string>
number double Task<double>
boolean bool Task<bool>
any object? Task<object?>
void - Task
Promise<T> - Task<T>

Real Blazor Regression Coverage

The repository now includes a minimal real Blazor app at BlazorTS.RealBlazorApp/ to validate behavior beyond Roslyn-only generator tests.

  • BlazorTS.RealBlazorApp/: a minimal real Blazor Web App that keeps only the files required for interop verification
  • BlazorTS.RealBlazorApp/Components/Pages/GenericProbe.razor: a real generic Razor component
  • BlazorTS.RealBlazorApp/Components/Pages/GenericProbe.razor.ts: the paired script that generates the top-level GenericProbeTSInterop
  • BlazorTS.RealBlazorApp.Tests/: regression tests that assert the finalized model through reflection, DI registration, and real compiled artifacts

This project specifically covers:

  • generic parameters stay on the component partial class
  • Scripts auto-injection still works in a real Razor compilation flow
  • interop services remain top-level non-generic *TSInterop types
  • AddBlazorTSScripts() registers the generated interop type for DI

πŸ“– Documentation

🀝 Contributing

Issues and Pull Requests are welcome.

πŸ“„ License

MIT License

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors