π A tool for seamless Blazor-TypeScript integration
English | δΈζ | π GitHub
BlazorTS is a source generator library that uses Tree-sitter syntax tree parsing to analyze TypeScript code, automatically generating C# wrapper code that enables you to call TypeScript functions directly in Blazor applications without manually writing JavaScript interop code.
- π Auto Generation: Uses Tree-sitter syntax tree parsing to analyze TypeScript code and automatically generates C# wrapper code
- π― Type Safety: Complete type mapping and compile-time checking
- π Zero Configuration: Minimal configuration, works out of the box
- π§ Smart Dependencies: Automatic service resolution and registration
- π³ Precise Parsing: Uses Tree-sitter for accurate TypeScript syntax structure parsing
Core Libraries
| NuGet Package | NuGet Version | Description |
|---|---|---|
BlazorTS |
Core runtime library | |
BlazorTS.SourceGenerator |
Source generator library | |
BlazorTS.Assets |
Asset management with dynamic URLs | |
Microsoft.TypeScript.MSBuild |
TypeScript compilation support |
1. Install NuGet Packages
Install via .NET CLI
dotnet add package BlazorTS
dotnet add package BlazorTS.SourceGenerator
dotnet add package BlazorTS.Assets # (optional)
dotnet add package Microsoft.TypeScript.MSBuild # (optional)Install via Package Manager Console
Install-Package BlazorTS
Install-Package BlazorTS.SourceGenerator
Install-Package BlazorTS.Assets # (optional)
Install-Package Microsoft.TypeScript.MSBuild # (optional)Alternative: If you prefer not to use
Microsoft.TypeScript.MSBuild, you can manually add aTargetto your.csprojfile to invoke the TypeScript compiler (npx tsc).<Target Name="CompileTypeScript" BeforeTargets="Build"> <Exec Command="npx tsc" /> </Target>
2. Configure Project File
Add the following to your .csproj file to ensure TypeScript files are processed correctly:
<!-- Add .razor.ts and .entry.ts files as additional files -->
<ItemGroup>
<AdditionalFiles Include="**/*.razor.ts" Exclude="**/node_modules/**" />
<AdditionalFiles Include="**/*.entry.ts" Exclude="**/node_modules/**" />
</ItemGroup>BlazorTS supports two types of TypeScript files to provide a flexible modularization scheme:
These files are bound to a specific Razor component for component-level script logic.
- Naming Convention:
MyComponent.razor.tsmust be paired withMyComponent.razor. - Generated Output: Automatically generates a
partial classforMyComponentand injects aTSInteropinstance namedScripts. - Usage: Directly call TypeScript functions via the
@injectedScriptsproperty within the component.
Example:
Components/Pages/Counter.razor.ts
// Dedicated module for the Counter.razor component
export function increment(count: number): number {
console.log("Incrementing count from TypeScript module!");
return count + 1;
}Components/Pages/Counter.razor
@page "/counter"
@rendermode InteractiveServer
@code {
private int currentCount = 0;
private async Task HandleClick()
{
// Directly call the `Scripts` property injected by BlazorTS
currentCount = await Scripts.increment(currentCount);
}
}These files are used to define common TypeScript modules that can be shared across multiple components or services.
- Naming Convention:
my-utils.entry.tsorapi.entry.ts. - Generated Output: Generates a standard C# class (e.g.,
MyUtilsorApi) that needs to be manually registered and injected. - Usage: Register the service in
Program.csand use it where needed via dependency injection.
Example:
Services/Formatter.entry.ts
export function formatCurrency(amount: number): string {
return `$${amount.toFixed(2)}`;
}Program.cs
// Automatically finds and registers all services generated from .entry.ts files
builder.Services.AddBlazorTSScripts();MyComponent.razor
@inject TestApp.Services.Formatter Formatter
<p>@Formatter.formatCurrency(123.45)</p>To enable Blazor to find the compiled JS file, we need to configure tsconfig.json to preserve the directory structure.
{
"compilerOptions": {
"noImplicitAny": false,
"noEmitOnError": true,
"removeComments": false,
"target": "es2015",
"rootDir": ".",
"outDir": "wwwroot/js"
},
"include": [
"**/*.razor.ts",
"**/*.entry.ts"
]
}With this configuration,
Components/Pages/Counter.razor.tswill be compiled towwwroot/js/Components/Pages/Counter.js.
Register the BlazorTS services in Program.cs.
// Program.cs
using BlazorTS.SourceGenerator.Extensions;
using Microsoft.Extensions.DependencyInjection;
// Register BlazorTS core services (includes default path resolver)
builder.Services.AddBlazorTS();
// Automatically finds and registers all services generated from .entry.ts files
builder.Services.AddBlazorTSScripts();Now, run your Blazor application. When you click the button:
- The
HandleClickmethod inCounter.razoris called. - It directly accesses the
Scriptsproperty, which is automatically generated by BlazorTS. - The
Scripts.incrementcall executes the corresponding function inCounter.razor.ts.
Behind the scenes, BlazorTS generates the following partial class code for you and merges it with your Counter.razor.cs:
// Code automatically generated by BlazorTS (conceptual)
public partial class Counter
{
// Automatically injects the TSInterop instance
[Inject]
public TSInterop Scripts { get; set; } = null!;
// Wrapper class responsible for JS interop
public class TSInterop(ScriptBridge invoker)
{
// ... implementation details ...
public async Task<double> increment(double count)
{
// ... calls JS ...
}
}
}In this way, BlazorTS perfectly integrates the TypeScript development experience with the Blazor component model, achieving true modularity.
BlazorTS maps MyApp.Components.Counter to /js/Components/Counter.js by default.
To customize paths, specify when registering services:
// Using custom function
builder.Services.AddBlazorTS((type, suffix) =>
{
var path = type.FullName!.Replace('.', '/');
return $"/scripts/{path}{suffix}.js";
});
// Using custom resolver class
public class CustomResolver : INSResolver
{
public string ResolveNS(Type tsType, string suffix)
{
var path = tsType.FullName!.Replace('.', '/');
return $"/lib/{path}{suffix}.js";
}
}
builder.Services.AddBlazorTS<CustomResolver>();The ResolveNS method now includes a suffix parameter to distinguish between different module types:
- Razor Components (
.razor.tsfiles): Use suffix".razor"Component.razor.tsβ/js/Component.razor.js
- Entry Modules (
.entry.tsfiles): Use suffix".entry"Module.entry.tsβ/js/Module.entry.js
- Custom Suffixes: Any string can be used as suffix
Examples:
// The default resolver automatically handles suffixes
var resolver = new DefaultNSResolver();
resolver.ResolveNS(typeof(MyComponent), ".razor"); // "/js/MyComponent.razor.js"
resolver.ResolveNS(typeof(MyModule), ".entry"); // "/js/MyModule.entry.js"
resolver.ResolveNS(typeof(MyClass), ""); // "/js/MyClass.js"| 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> |
- Development Guide - Detailed development and build instructions
- Supported TypeScript Syntax - Complete syntax support list
- DLL Path Resolution Mechanism - Advanced configuration options
- δΈζθ―΄ζ - Chinese version of this README
Issues and Pull Requests are welcome!
MIT License