Skip to content
Merged
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ obj
*.user
*.nupkg
package-lock.json
last-failed-test-dump.txt
11 changes: 1 addition & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,7 @@ To check JS coverage, run `npm run cover` under `src/js`.

# Inspecting Generated Output

C# tests under `Bootsharp.Publish.Test` generate files inside a temporary `MockProject` root, which is deleted when the test is disposed. When you need to inspect the generated content, write it to a scratch file outside the mock project, for example:

```csharp
AddAssembly(With("// fixture source code"));
Execute();
File.WriteAllText(Path.Combine(Path.GetTempPath(), "scratch.txt"), GeneratedDeclarations);
Contains("// asserted generated content");
```

Then run the focused test, read the scratch file and remove the probe before finalizing. Do not commit debug dumps or temporary file writes.
C# tests under `Bootsharp.Publish.Test` generate files inside a temporary `MockProject` root, which is deleted when the test is disposed. When you need to inspect the generated content of the last failed test, read the `src/cs/Bootsharp.Publish.Test/last-failed-test-dump.txt` file.

# Running Shell Scripts

Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@ Facilitating high-level interoperation between C# and TypeScript, Bootsharp lets

✨ High-level C# <-> TypeScript interop

📦 Embeds binaries to single-file ES module
📦 Produces modern ES package with modules and types

🗺️ Works in browsers and JS runtimes (Node, Deno, Bun)

Generates bindings and types over C# interfaces
🧩 Generates bindings over abstract C# API surfaces

🏷️ Supports interop over object instances
🧬 Intelligently handles any type on the interop surface

🛠️ Allows customizing emitted bindings
🛠️ Allows customizing emitted interop API patterns

🔥 Supports multi-threading, NativeAOT-LLVM, trimming
⚡ Compiles optimized WASM with NativeAOT-LLVM and Binaryen

## 🎬 Get Started

Expand Down
4 changes: 2 additions & 2 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ export default defineConfig({
{ text: "Namespaces", link: "/guide/namespaces" },
{ text: "Events", link: "/guide/events" },
{ text: "Serialization", link: "/guide/serialization" },
{ text: "Interop Interfaces", link: "/guide/interop-interfaces" },
{ text: "Interop Modules", link: "/guide/interop-modules" },
{ text: "Interop Instances", link: "/guide/interop-instances" },
{ text: "Emit Preferences", link: "/guide/emit-prefs" },
{ text: "Preferences", link: "/guide/preferences" },
{ text: "Build Configuration", link: "/guide/build-config" },
{ text: "Sideloading Binaries", link: "/guide/sideloading" },
{ text: "NativeAOT-LLVM", link: "/guide/llvm" }
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/declarations.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,4 @@ export namespace Foo {

## Configuring Type Mappings

You can override which type declaration are generated for associated C# types via `Type` patterns of [emit preferences](/guide/emit-prefs).
You can override which type declaration are generated for associated C# types via `Type` patterns of [emit preferences](/guide/preferences).
2 changes: 1 addition & 1 deletion docs/guide/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Program.onSomethingChanged.broadcast("updated");

Bootsharp supports all common event types: `Action`, `EventHandler`, and any custom delegate types without a return type.

Events on the [interop interfaces](/guide/interop-interfaces) are picked up automatically, so you don't have to annotate them.
Events on [modules](/guide/interop-modules) and [instances](/guide/interop-instances) are picked up automatically, so you don't have to annotate them.

## React Event Hooks

Expand Down
2 changes: 1 addition & 1 deletion docs/guide/extensions/dependency-injection.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Dependency Injection

When using [interop interfaces](/guide/interop-interfaces), it's convenient to use a dependency injection mechanism to automatically route generated interop implementations for the services that needs them.
When using [modules](/guide/interop-modules), it's convenient to use a dependency injection mechanism to automatically route generated module implementations for the services that needs them.

Reference `Bootsharp.Inject` extension in the project configuration:

Expand Down
2 changes: 1 addition & 1 deletion docs/guide/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public static partial class Program
```

::: info NOTE
Authoring interop via static methods is impractical for large API surfaces—it's shown here only as a simple way to get started. For real projects, consider using [interop interfaces](/guide/interop-interfaces) instead.
Authoring interop via static methods is impractical for large API surfaces—it's shown here only as a simple way to get started. For real projects, consider using [modules](/guide/interop-modules) instead.
:::

## Compile ES Module
Expand Down
33 changes: 23 additions & 10 deletions docs/guide/interop-interfaces.md → docs/guide/interop-modules.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Interop Interfaces
# Interop Modules

Instead of manually authoring a binding for each member, let Bootsharp generate them automatically using the `[Import]` and `[Export]` assembly attributes.
Instead of manually authoring a binding for each member, let Bootsharp generate them automatically using the `[Import]` and `[Export]` assembly attributes. The type listed under each attribute defines an *interop module*.

For example, say we have a JavaScript UI (frontend) with a setting stored on the JS side, and a C# domain layer (backend) that wants to expose state changes back to JavaScript. You can describe the imported frontend APIs like this:
For example, say we have a JavaScript UI (frontend) with a setting stored on the JS side, and a C# domain layer (backend) that wants to expose state changes back to JavaScript. You can describe the imported frontend module like this:

```csharp
interface IFrontend
Expand All @@ -11,7 +11,7 @@ interface IFrontend
}
```

Now, add the interface type to the JS import list:
Now, add the module type to the JS import list:

```csharp
[assembly: Import(typeof(IFrontend))]
Expand All @@ -25,24 +25,37 @@ export namespace Frontend {
}
```

Now, export the backend contract to JavaScript:
Imported modules must be interfaces, since Bootsharp generates the C# implementation that calls into JavaScript.

Now, define the backend contract to expose to JavaScript. An exported module can be either an interface or a non-static class — pick whichever fits your backend best:

```csharp
interface IBackend
public interface IBackend
{
event Action<Data> OnDataChanged;
Data? Current { get; set; }
void AddData (Data data);
}
```

Export the interface to JavaScript:
```csharp
public class Backend
{
public event Action<Data>? OnDataChanged;
public Data? Current { get; set; }
public void AddData (Data data) { /* ... */ }
}
```

Export the module to JavaScript:

```csharp
[assembly: Export(typeof(IBackend))]
// or
[assembly: Export(typeof(Backend))]
```

This will produce the following spec to be consumed on the JavaScript side:
Either form produces the following spec to be consumed on the JavaScript side:

```ts
export namespace Backend {
Expand All @@ -52,10 +65,10 @@ export namespace Backend {
}
```

Imported interface events work the other way around: declare a real C# event on the interface, and Bootsharp will generate a JavaScript `EventBroadcaster` plus a regular subscribable event on the generated C# implementation.
Imported module events work the other way around: declare a real C# event on the interface, and Bootsharp will generate a JavaScript `EventBroadcaster` plus a regular subscribable event on the generated C# implementation.

To make Bootsharp automatically inject and initialize the generated interop implementations, use the [dependency injection](/guide/extensions/dependency-injection) extension.

::: tip Example
Find an example of using interop interfaces in the [React sample](https://github.com/elringus/bootsharp/tree/main/samples/react).
Find an example of using modules in the [React sample](https://github.com/elringus/bootsharp/tree/main/samples/react).
:::
16 changes: 8 additions & 8 deletions docs/guide/namespaces.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Namespaces

Bootsharp maps generated binding APIs based on the name of the associated C# types. The rules are a bit different for static interop methods, interop interfaces and types.
Bootsharp maps binding APIs based on the fully qualified name of the C# types.

## Static Methods
## Static Members

Full type name (including namespace) of the declaring type of the static interop method is mapped into JavaScript object name:
Full type name (including namespace) of the declaring type of the static member is mapped into JavaScript object name:

```csharp
class Class { [Export] static void Method() {} }
Expand Down Expand Up @@ -37,20 +37,20 @@ import { Foo } from "bootsharp";
Foo.Class.Nested.method();
```

## Interop Interfaces
## Interop Modules

When generating bindings for [interop interfaces](/guide/interop-interfaces), it's assumed the interface name has "I" prefix, so the associated implementation name will have first character removed. In case interface is declared under namespace, it'll be mirrored in JavaScript.
When generating bindings for [modules](/guide/interop-modules), an interface name is assumed to have an "I" prefix, so the associated JavaScript name will have the first character removed. Class modules keep their name as-is. In either case, if the type is declared under a namespace, it'll be mirrored in JavaScript.

```csharp
[Export(
typeof(IExported),
typeof(Foo.IExported),
typeof(Foo.Bar.IExported)
typeof(Foo.Bar.Exported)
)]

interface IExported { void Method(); }
namespace Foo { interface IExported { void Method(); } }
namespace Foo.Bar { interface IExported { void Method(); } }
namespace Foo.Bar { class Exported { public void Method() {} } }
```

```ts
Expand Down Expand Up @@ -88,4 +88,4 @@ function methodImpl(r: Record): Foo.Record {

## Configuring Namespaces

You can control how namespaces are generated via `Space` patterns of [emit preferences](/guide/emit-prefs).
You can control how namespaces are generated with `Space` option in [preferences](/guide/preferences).
2 changes: 1 addition & 1 deletion docs/guide/emit-prefs.md → docs/guide/preferences.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Emit Preferences
# Preferences

Use `[Preferences]` assembly attribute to customize Bootsharp behaviour at build time when the interop code is emitted. It has several properties that takes array of `(pattern, replacement)` strings, which are feed to [Regex.Replace](https://docs.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.replace?view=net-6.0#system-text-regularexpressions-regex-replace(system-string-system-string-system-string)) when emitted associated code. Each consequent pair is tested in order; on first match the result replaces the default.

Expand Down
26 changes: 13 additions & 13 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ titleTemplate: Bootsharp • :title
hero:
name: Bootsharp
text: Use C# in web apps with comfort
tagline: Author the domain in C#, while fully leveraging the modern JavaScript frontend ecosystem.
tagline: Author the domain in C#, while fully leveraging the modern TypeScript frontend ecosystem.
actions:
- theme: brand
text: Get Started
Expand All @@ -31,17 +31,17 @@ hero:
<div class="icon">✨</div>
<h2 class="title">High-level Interoperation</h2>
</div>
<p class="details">Generates JavaScript bindings and type declarations for your C# APIs, enabling seamless interop between domain and UI.</p></article>
<p class="details">Generates JavaScript bindings and TypeScript declarations for your C# APIs, enabling seamless interop between domain and UI.</p></article>
</div>
</div>
<div class="grid-3 item">
<div class="VPLink no-icon VPFeature">
<article class="box">
<div class="box-title">
<div class="icon">📦</div>
<h2 class="title">Embed or Sideload</h2>
<h2 class="title">Modern ES Package</h2>
</div>
<p class="details">Choose between embedding all C# binaries into a single-file ES module for portability or sideloading for performance and size.</p></article>
<p class="details">Just run "dotnet publish" and get a full-fledged ES package with "package.json" included—directly importable into your web project.</p></article>
</div>
</div>
<div class="grid-3 item">
Expand All @@ -60,20 +60,20 @@ hero:
<div class="VPLink no-icon VPFeature">
<article class="box">
<div class="box-title">
<div class="icon"></div>
<h2 class="title">Interop Interfaces</h2>
<div class="icon">🧩</div>
<h2 class="title">Interop Modules</h2>
</div>
<p class="details">Manually author interop APIs via static C# methods or feed Bootsharp your domain-specific interfaces—it'll handle the rest.</p></article>
<p class="details">Author fine-grained bindings for C# members, or feed Bootsharp entire API surfaces—it'll handle the rest.</p></article>
</div>
</div>
<div class="grid-4 item">
<div class="VPLink no-icon VPFeature">
<article class="box">
<div class="box-title">
<div class="icon">🏷️</div>
<h2 class="title">Instance Bindings</h2>
<div class="icon">🧬</div>
<h2 class="title">Type Polyglot</h2>
</div>
<p class="details">When an interface value is used in interop, instance binding is generated to interoperate with stateful objects.</p></article>
<p class="details">Intelligently supports any type: immutables are copied with a fast binary serializer, others passed by reference—fully automated.</p></article>
</div>
</div>
<div class="grid-4 item">
Expand All @@ -90,10 +90,10 @@ hero:
<div class="VPLink no-icon VPFeature">
<article class="box">
<div class="box-title">
<div class="icon">🔥</div>
<h2 class="title">Modern .NET</h2>
<div class="icon"></div>
<h2 class="title">Fast and Tiny</h2>
</div>
<p class="details">Supports latest runtime features: WASM multi-threading, assembly trimming, NativeAOT-LLVM, streaming instantiation.</p></article>
<p class="details">Compiles WASM with NativeAOT-LLVM and further optimizes with Binaryen for optimal performance and minimal bundle size.</p></article>
</div>
</div>
</div>
Expand Down
20 changes: 0 additions & 20 deletions src/cs/Bootsharp.Common.Test/InterfacesTest.cs

This file was deleted.

28 changes: 28 additions & 0 deletions src/cs/Bootsharp.Common.Test/ModulesTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace Bootsharp.Common.Test;

public class ModulesTest
{
[Fact]
public void RegistersInterfaceExport ()
{
var export = new ExportModule(typeof(IBackend), null);
Modules.Register(typeof(Backend), export);
Assert.Equal(typeof(IBackend), Modules.Exports[typeof(Backend)].Handler);
}

[Fact]
public void RegistersClassExport ()
{
var export = new ExportModule(typeof(Backend), null);
Modules.Register(typeof(Backend), export);
Assert.Equal(typeof(Backend), Modules.Exports[typeof(Backend)].Handler);
}

[Fact]
public void RegistersImport ()
{
var import = new ImportModule(new Frontend());
Modules.Register(typeof(IFrontend), import);
Assert.IsType<Frontend>(Modules.Imports[typeof(IFrontend)].Instance);
}
}
12 changes: 6 additions & 6 deletions src/cs/Bootsharp.Common/Attributes/ExportAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ namespace Bootsharp;
/// <summary>
/// When applied to a static method makes it invokable in JavaScript.
/// When applied to a static <see cref="Action"/> event allows JavaScript consumers subscribe to it.
/// When applied to WASM entry point assembly, specified interfaces will
/// When applied to WASM entry point assembly, specified module class or interfaces types will
/// be automatically exported for consumption on the JavaScript side.
/// </summary>
/// <remarks>
Expand All @@ -22,11 +22,11 @@ namespace Bootsharp;
/// [Export]
/// public static event Action OnSomething;
/// </code>
/// Expose "IHandlerA" and "IHandlerB" C# APIs to JavaScript and wrap invocations in "Utils.Try()":
/// Expose "IService" and "Handler" C# API surfaces to JavaScript and wrap invocations in "Utils.Try()":
/// <code>
/// [assembly: Export(
/// typeof(IHandlerA),
/// typeof(IHandlerB),
/// typeof(IService),
/// typeof(Handler),
/// invokePattern = "(.+)",
/// invokeReplacement = "Utils.Try(() => $1)"
/// )]
Expand All @@ -36,10 +36,10 @@ namespace Bootsharp;
public sealed class ExportAttribute : Attribute
{
/// <summary>
/// When applied to assembly, lists the interface types to generated export bindings for.
/// When applied to assembly, lists the module (class or interface) types to generated export bindings for.
/// </summary>
public Type[] Types { get; }

/// <param name="types">The interface types to generate export bindings for (when applied to assembly).</param>
/// <param name="types">The module types to generate export bindings for (when applied to assembly).</param>
public ExportAttribute (params Type[] types) => Types = types;
}
8 changes: 4 additions & 4 deletions src/cs/Bootsharp.Common/Attributes/ImportAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ namespace Bootsharp;
/// <summary>
/// When applied to a static partial method binds it with a JavaScript function.
/// When applied to a static <see cref="Action"/> event allows JavaScript consumers broadcast it.
/// When applied to WASM entry point assembly, JavaScript bindings for the specified interfaces
/// will be automatically generated for consumption on the C# side.
/// When applied to WASM entry point assembly, JavaScript bindings for the specified module
/// interfaces will be automatically generated for consumption on the C# side.
/// </summary>
/// <remarks>
/// When used on the assembly level, generated bindings have to be implemented on the JavaScript side.
Expand Down Expand Up @@ -34,10 +34,10 @@ namespace Bootsharp;
public sealed class ImportAttribute : Attribute
{
/// <summary>
/// When applied to assembly, lists the interface types to generated import bindings for.
/// When applied to assembly, lists the module interface types to generate import bindings for.
/// </summary>
public Type[] Types { get; }

/// <param name="types">The interface types to generate import bindings for (when applied to assembly).</param>
/// <param name="types">The module interface types to generate import bindings for (when applied to assembly).</param>
public ImportAttribute (params Type[] types) => Types = types;
}
Loading
Loading