diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index fd2a1a21..338e721d 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -23,6 +23,7 @@ jobs:
run: |
cd docs
npm install
+ npm run docs:api
npm run docs:build
- uses: actions/configure-pages@v3
- uses: actions/upload-pages-artifact@v2
diff --git a/README.md b/README.md
index 920851d2..d7814cc5 100644
--- a/README.md
+++ b/README.md
@@ -12,10 +12,24 @@
-# Bootsharp
+# Use C# in web apps with comfort
-Compile C# solution into single-file ES module with auto-generated JavaScript bindings and type definitions.
+Author domain in C#, while taking full advantage of the modern JavaScript frontend ecosystem.
-![](https://raw.githubusercontent.com/elringus/bootsharp/main/docs/public/img/banner.png)
+β¨ Generates JavaScript bindings and type declarations for your C# APIs facilitating seamless interop between the domain and UI.
-Documentation will be added later. Please refer to [samples](https://github.com/elringus/bootsharp/tree/main/samples) for the time being.
+π¦ Choose between embedding all the C# binaries into single-file ES module for portability or sideload for best performance and build size.
+
+πΊοΈ Node, Deno, Bun, web browsers and even constrained environments, such as VS Code extensions β your app will work everywhere.
+
+β‘ Manually author interop APIs via static C# methods or simply feed Bootsharp your domain-specific interfaces β it'll figure the rest.
+
+π·οΈ When an interface value is specified in interop API, instance binding is generated allowing to interoperate on stateful objects.
+
+π οΈ Configure namespaces for emitted bindings, function and event names, C# -> TypeScript type mappings and more.
+
+π₯ Supports latest .NET features: WASM multi-threading, AOT compilation, assembly trimming, streaming module instantiation.
+
+### π¬ Get Started
+
+https://bootsharp.com/guide
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 00000000..e70b8a5f
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,2 @@
+cache
+api
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index 7bc5aa4d..1ca6dc96 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -1,4 +1,5 @@
import { defineConfig } from "vitepress";
+import proc from "node:child_process";
import imgit from "imgit/vite";
import md from "./md";
@@ -15,6 +16,7 @@ export default defineConfig({
["link", { rel: "icon", href: "/favicon.svg" }],
["link", { rel: "preload", href: "/fonts/inter.woff2", as: "font", type: "font/woff2", crossorigin: "" }],
["link", { rel: "preload", href: "/fonts/jb.woff2", as: "font", type: "font/woff2", crossorigin: "" }],
+ ["meta", { name: "og:image", content: "/img/og.jpg" }],
["meta", { name: "twitter:card", content: "summary_large_image" }]
],
themeConfig: {
@@ -30,8 +32,9 @@ export default defineConfig({
docFooter: { prev: "Previous page", next: "Next page" },
nav: [
{ text: "Guide", link: "/guide/", activeMatch: "/guide/" },
+ { text: "Reference", link: "/api/", activeMatch: "/api/" },
{
- text: "v0.1.0", items: [
+ text: proc.execSync("git describe --abbrev=0 --tags").toString(), items: [
{ text: "Changes", link: "https://github.com/elringus/bootsharp/releases/latest" },
{ text: "Contribute", link: "https://github.com/elringus/bootsharp/labels/help%20wanted" }
]
@@ -46,10 +49,28 @@ export default defineConfig({
{
text: "Guide",
items: [
- { text: "Introduction", link: "/guide/" }
+ { text: "Introduction", link: "/guide/" },
+ { text: "Getting Started", link: "/guide/getting-started" },
+ { text: "Type Declarations", link: "/guide/declarations" },
+ { text: "Namespaces", link: "/guide/namespaces" },
+ { text: "Events", link: "/guide/events" },
+ { text: "Serialization", link: "/guide/serialization" },
+ { text: "Interop Interfaces", link: "/guide/interop-interfaces" },
+ { text: "Interop Instances", link: "/guide/interop-instances" },
+ { text: "Emit Preferences", link: "/guide/emit-prefs" },
+ { text: "Build Configuration", link: "/guide/build-config" },
+ { text: "Sideloading Binaries", link: "/guide/sideloading" }
+ ]
+ },
+ {
+ text: "Extensions",
+ items: [
+ { text: "Dependency Injection", link: "/guide/extensions/dependency-injection" },
+ { text: "File System β¨", link: "/guide/extensions/file-system" }
]
}
- ]
+ ],
+ "/api/": [{ text: "Reference", items: (await import("./../api/typedoc-sidebar.json")).default }]
}
},
sitemap: { hostname: "https://bootsharp.com" }
diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css
index baef97a6..96dfaf1a 100644
--- a/docs/.vitepress/theme/style.css
+++ b/docs/.vitepress/theme/style.css
@@ -24,16 +24,20 @@
font-optical-sizing: auto;
/* Main highlight/link color (light mode). */
- /*--vp-c-brand-1: #d22c40;*/
+ /*--vp-c-brand-1: #9c00e7;*/
/* Link hover color (light mode). */
- /*--vp-c-brand-2: #be2033;*/
+ /*--vp-c-brand-2: #b519ff;*/
+ /* Danger block override for sponsors. (light mode) */
+ --vp-custom-block-danger-bg: #fee3f5;
}
.dark {
/* Main highlight/link color (dark mode). */
- /*--vp-c-brand-1: #ee3248;*/
+ /*--vp-c-brand-1: #b583ff;*/
/* Link hover color (dark mode). */
- /*--vp-c-brand-2: #f35d44;*/
+ --vp-c-brand-2: #c7cdff;
+ /* Danger block override for sponsors. (dark mode) */
+ --vp-custom-block-danger-bg: #55223c;
}
.dark .dark-only,
@@ -140,6 +144,12 @@ table > tfoot:not(:last-child) {
background: none !important;
}
+/* remove opacity gradient under navbar */
+.curtain,
+.aside-curtain {
+ display: none !important;
+}
+
/* a hack for logo title to keep sidebar bg color */
@media (min-width: 960px) {
.VPNavBar.has-sidebar div.title {
@@ -148,24 +158,16 @@ table > tfoot:not(:last-child) {
}
/* a bit of tint to make navbar not as transparent */
-.VPNavBar {
- background: rgba(255, 255, 255, 0.75) !important;
-}
-
-.dark .VPNavBar {
- background: rgba(30, 30, 32, 0.5) !important;
-}
-
-.VPLocalNav {
+.VPNavBar:not(.top) {
background: rgba(255, 255, 255, 0.75) !important;
}
-.dark .VPLocalNav {
+.dark .VPNavBar:not(.top) {
background: rgba(30, 30, 32, 0.5) !important;
}
/* the blur effect */
-.VPNav::after {
+.VPNavBar:not(.top)::after {
content: "";
position: absolute;
top: 0;
@@ -176,13 +178,14 @@ table > tfoot:not(:last-child) {
z-index: -1;
}
-.VPLocalNav::after {
- content: "";
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- backdrop-filter: saturate(180%) blur(5px);
- z-index: -1;
+.VPNavBar.top::after {
+ opacity: 0;
+}
+
+.VPNavBar.top {
+ background-color: transparent;
+}
+
+.VPNavBar {
+ transition: border-bottom-color 0.25s, background-color 0.25s, opacity 0.25s;
}
diff --git a/docs/guide/build-config.md b/docs/guide/build-config.md
new file mode 100644
index 00000000..1cd3965c
--- /dev/null
+++ b/docs/guide/build-config.md
@@ -0,0 +1,36 @@
+ο»Ώ# Build Configuration
+
+Build and publish related options are configured in `.csproj` file via MSBuild properties.
+
+| Property | Default | Description |
+|-----------------------------|------------|--------------------------------------------------------------|
+| BootsharpName | bootsharp | Name of the generated JavaScript module. |
+| BootsharpEmbedBinaries | true | Whether to embed binaries to the JavaScript module file. |
+| BootsharpAggressiveTrimming | false | Whether to disable some .NET features to reduce binary size. |
+| BootsharpBundleCommand | npx rollup | The command to bundle generated JavaScrip solution. |
+| BootsharpPublishDirectory | /bin | Directory to publish generated JavaScript module. |
+| BootsharpTypesDirectory | /types | Directory to publish type declarations. |
+| BootsharpBinariesDirectory | /bin | Directory to publish binaries when `EmbedBinaries` disabled. |
+| BootsharpPackageDirectory | / | Directory to publish `package.json` file. |
+
+Below is an example configuration, which will make Bootsharp name compiled module "backend" (instead of the default "bootsharp"), publish the module under solution directory root (instead of "/bin"), disable binaries embedding and instead publish them under "public/bin" directory one level above the solution root and enable aggressive assembly trimming to reduce build size:
+
+```xml
+
+
+
+ net8.0
+ browser-wasm
+ backend
+ $(SolutionDir)
+ false
+ $(SolutionDir)../public/bin
+ true
+
+
+
+
+
+
+
+```
diff --git a/docs/guide/declarations.md b/docs/guide/declarations.md
new file mode 100644
index 00000000..a7063891
--- /dev/null
+++ b/docs/guide/declarations.md
@@ -0,0 +1,121 @@
+ο»Ώ# Type Declarations
+
+Bootsharp will automatically generate [type declarations](https://www.typescriptlang.org/docs/handbook/2/type-declarations) for interop APIs when building the solution. The files are emitted under "types" directory of the compiled module package.
+
+## Function Declarations
+
+For the interop methods, function declarations are emitted.
+
+Exported `[JSInvokable]` methods will have associated function assigned under the declaring type space:
+
+```csharp
+public class Foo
+{
+ [JSInvokable]
+ public static void Bar() { }
+}
+```
+
+β will make following emitted in the declaration file:
+
+```ts
+export namespace Foo {
+ export function bar(): void;
+}
+```
+
+β which allows consuming the API in JavaScript as follows:
+
+```ts
+import { Foo } from "bootsharp";
+
+Foo.bar();
+```
+
+Imported `[JSFunction]` methods will be emitted as properties, which have to be assigned before booting the runtime:
+
+::: code-group
+
+```csharp [Foo.cs]
+public partial class Foo
+{
+ [JSFunction]
+ public static partial void Bar();
+}
+```
+
+```ts [bindings.d.ts]
+export namespace Foo {
+ export let bar: () => void;
+}
+```
+
+```ts [main.ts]
+import { Foo } from "bootsharp";
+
+Foo.bar = () => {};
+```
+
+:::
+
+## Event Declarations
+
+`[JSEvent]` methods will be emitted as objects with `subscribe` and `unsubscribe` methods:
+
+::: code-group
+
+```csharp [Foo.cs]
+public class Foo
+{
+ [JSEvent]
+ public static partial void OnBar (string payload);
+}
+```
+
+```ts [bindings.d.ts]
+export namespace Foo {
+ export const onBar: Event<[string]>;
+}
+```
+
+```ts [main.ts]
+import { Foo } from "bootsharp";
+
+Foo.onBar.subscribe(pyaload => {});
+```
+
+:::
+
+## Type Crawling
+
+Bootsharp will crawl types from the interop signatures and mirror them in the emitted declarations. For example, if you have a custom record with property of another custom record implementing a custom interface, both records and the interface will be emitted:
+
+::: code-group
+
+```csharp [Foo.cs]
+public interface IFoo { };
+public record Foo : IFoo;
+public record Bar (Foo foo);
+
+public partial class Foo
+{
+ [JSFunction]
+ public static partial Bar GetBar();
+}
+```
+
+```ts [bindings.d.ts]
+export interface IFoo {}
+export interface Foo implements IFoo {}
+export interface Bar {foo: Foo;}
+
+export namespace Foo {
+ export function getBar(): Bar;
+}
+```
+
+:::
+
+## Configuring Type Mappings
+
+You can override which type declaration are generated for associated C# types via `Type` patterns of [emit preferences](/guide/emit-prefs).
diff --git a/docs/guide/emit-prefs.md b/docs/guide/emit-prefs.md
new file mode 100644
index 00000000..bb0c82fa
--- /dev/null
+++ b/docs/guide/emit-prefs.md
@@ -0,0 +1,33 @@
+ο»Ώ# Emit Preferences
+
+Use `[JSPreferences]` 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.
+
+## Space
+
+By default, all the generated JavaScript binding objects and TypeScript declarations are grouped under corresponding C# namespaces; refer to [namespaces](/guide/namespaces) docs for more info.
+
+To customize emitted spaces, use `Space` parameter. For example, to make all bindings declared under "Foo.Bar" C# namespace have "Baz" namespace in JavaScript:
+
+```cs
+[assembly: JSPreferences(
+ Space = ["^Foo\.Bar\.(\S+)", "Baz.$1"]
+)]
+```
+
+The patterns are matched against full type name of declaring C# type when generating JavaScript objects for interop methods and against namespace when generating TypeScript syntax for C# types. Matched type names have the following modifications:
+
+- interfaces have first character removed
+- generics have parameter spec removed
+- nested type names have `+` replaced with `.`
+
+## Type
+
+Allows customizing generated TypeScript type syntax. The patterns are matched against full C# type names of interop method arguments, return values and object properties.
+
+## Event
+
+Used to customize which C# methods should be transformed into JavaScript events, as well as generated event names. The patterns are matched against C# method names declared under `[JSImport]` interfaces. By default, methods starting with "Notify..." are matched and renamed to "On...".
+
+## Function
+
+Customizes generated JavaScript function names. The patterns are matched against C# interop method names.
diff --git a/docs/guide/events.md b/docs/guide/events.md
new file mode 100644
index 00000000..a4ad79ce
--- /dev/null
+++ b/docs/guide/events.md
@@ -0,0 +1,68 @@
+ο»Ώ# Events
+
+To make a C# method act as event broadcaster for JavaScript consumers, annotate it with `[JSEvent]` attribute:
+
+```csharp
+[JSEvent]
+public static partial void OnSomethingChanged (string payload);
+```
+
+β and consume it from JavaScript as follows:
+
+```ts
+Program.onSomethingChanged.subscribe(handleSomething);
+Program.onSomethingChanged.unsubscribe(handleSomething);
+
+function handleSomething(payload: string) {
+
+}
+```
+
+When the method in invoked in C#, subscribed JavaScript handlers will be notified. In TypeScript the event will have typed generic declaration corresponding to the event arguments.
+
+## Events in Interop Interfaces
+
+To make a method in an [interop interface](/guide/interop-interfaces) act as event broadcaster, make its name start with "Notify". Such methods will be detected by Bootsharp and exposed to JavaScript as events with "Notify" changed to "On". For example, `NotifyUserUpdated` C# method will be exposed as `OnUserUpdated` JavaScript event.
+
+Which interface methods are considered events and the way they are named in JavaScript can be customized with [emit preferences](/guide/emit-prefs).
+
+## React Event Hooks
+
+Below are sample React utility hooks, which you may find useful:
+
+```ts
+export function useEvent(
+ event: EventSubscriber, handler: (...args: [...T]) => void,
+ deps?: DependencyList | undefined, destructor?: () => void) {
+ useEffect(() => {
+ event.subscribe(handler);
+ return () => {
+ event.unsubscribe(handler);
+ destructor?.();
+ };
+ }, [event, handler, destructor, ...(deps ?? [])]);
+}
+
+export function useEventState(
+ event: EventSubscriber,
+ defaultState?: T[0]): T[0] | undefined {
+ const initial = event.last === undefined ?
+ defaultState : getFirstArg(event.last);
+ const [state, setState] = useState(initial);
+ useEvent(event, (...args) => setState(getFirstArg(args)), []);
+ return state;
+
+ function getFirstArg(args: T): T[0] | undefined {
+ return args[0] === null ? undefined : args[0];
+ }
+}
+```
+
+The `useEventState` hook will take care of both subscribing and unsubscribing from the dotnet event when component unmounts and using last event args as the default state to catch up in case the component missed a broadcast before being mounted.
+
+```tsx
+const SomeComponent = () => {
+ const payload = useEventState(Program.onSomethingChanged);
+ return <>{payload}>;
+};
+```
diff --git a/docs/guide/extensions/dependency-injection.md b/docs/guide/extensions/dependency-injection.md
new file mode 100644
index 00000000..36558101
--- /dev/null
+++ b/docs/guide/extensions/dependency-injection.md
@@ -0,0 +1,66 @@
+# 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.
+
+Reference `Bootsharp.Inject` extension in the project configuration:
+
+```xml
+
+
+
+ net8.0
+ browser-wasm
+
+
+
+
+
+
+
+
+
+```
+
+β and use `AddBootsharp` extension method to inject the generated import implementations; `RunBootsharp` will initialize generated export implementation by requiring the handlers, which should be added to the services collection before.
+
+```csharp
+using Bootsharp;
+using Bootsharp.Inject;
+using Microsoft.Extensions.DependencyInjection;
+
+[assembly: JSExport(
+ typeof(IExported),
+ // other APIs to export to JavaScript
+)]
+
+[assembly: JSImport(
+ typeof(IImported),
+ // other APIs to import from JavaScript
+)]
+
+new ServiceCollection()
+ // Inject generated implementation of IImported.
+ .AddBootsharp()
+ // Inject other services, which may require IImported.
+ .AddSingleton()
+ // Provide handler for the exported interface.
+ .AddSingleton()
+ // Build the collection.
+ .BuildServiceProvider()
+ // Initialize the exported implementations.
+ .RunBootsharp();
+```
+
+`IImported` can now be requested via .NET's DI, while `IExported` APIs are available in JavaScript:
+
+```csharp
+public class SomeService (IImported imported) { }
+```
+
+```ts
+import { Exported } from "bootsharp";
+```
+
+::: tip EXAMPLE
+Find example on using the DI extension in the [React sample](https://github.com/elringus/bootsharp/blob/main/samples/react).
+:::
diff --git a/docs/guide/extensions/file-system.md b/docs/guide/extensions/file-system.md
new file mode 100644
index 00000000..847c0ec5
--- /dev/null
+++ b/docs/guide/extensions/file-system.md
@@ -0,0 +1,89 @@
+# File System
+
+::: danger SPONSORS
+This extension is exclusive for sponsors: https://github.com/sponsors/elringus.
+:::
+
+With the new [File System Access](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API) APIs it's possible to access local file system directly from web browser. Bootsharp.FileSystem extension provides C# bindings and JavaScript package to use the APIs directly from C#.
+
+Install the NuGet package to C# project:
+
+```xml
+
+
+
+
+ net8.0
+ browser-wasm
+
+
+
+
+
+
+
+
+```
+
+And the NPM package to JavaScript project:
+
+```json
+{
+ "dependencies": {
+ "backend": "file:backed",
+ "@rewaffle/bootsharp-file-system": "latest"
+ }
+}
+```
+
+Before booting C# solution in JavaScript, initialize the file system extension:
+
+```ts
+import bootsharp, { Bootsharp } from "backend";
+import * as fs from "@rewaffle/bootsharp-file-system";
+
+fs.init(Bootsharp.FileSystem.FileMounter);
+await bootsharp.boot();
+```
+
+Then proceed to C# where `IFileMounter` interface will be automatically injected by the extension importing following APIs from JavaScript:
+
+```csharp
+interface IFileMounter
+{
+ Task PickRoot (PickOptions? options = null);
+ Task Mount (string root, IFileWatcher watcher);
+ Task Unmount (string root);
+}
+```
+
+Invoking `PickRoot` method will prompt user to select root directory to mount. It will return unique root directory identifier to be used with `Mount` and `Unmount` methods or `null` in case user cancelled pick dialogue. Optional `PickOptions` argument allows specifying which directory pick dialogue should start in, whether write access should be requested, etc.
+
+After user picked a directory and you get the root ID, invoke `Mount`, which will return `IFileSystem` instance, providing common IO interface over the contents of the mapped directory:
+
+```csharp
+interface IFileSystem
+{
+ Task CreateDirectory (string uri);
+ Task RemoveDirectory (string uri);
+ Task WriteFile (string uri, byte[] content);
+ Task DeleteFile (string uri);
+ Task ReadFile (string uri);
+ Task GetFileInfo (string uri);
+}
+```
+
+File watcher instance specified when invoking `Mount` allows handling file changes under the mapped directory:
+
+```csharp
+interface IFileWatcher
+{
+ Task HandleFileChanges (FileChange[] changes);
+}
+```
+
+β until the directory is un-mounted, the watcher will be notified when an entry (directory or file) is added, removed or modified.
+
+::: tip EXAMPLE
+Find sample application built with `Bootsharp.FileSystem` in the [sponsors repository](https://github.com/rewaffle/extra).
+:::
diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md
new file mode 100644
index 00000000..8d1f1b2a
--- /dev/null
+++ b/docs/guide/getting-started.md
@@ -0,0 +1,139 @@
+ο»Ώ# Getting Started
+
+## Configure C# Project
+
+In `.csproj` file, set wasm runtime identifier and reference Bootsharp package:
+
+```xml
+
+
+
+
+ net8.0
+ browser-wasm
+
+
+
+
+
+
+
+```
+
+## Author Interop APIs
+
+Specify interop surface in the C# project.
+
+```cs
+using System;
+using Bootsharp;
+
+public static partial class Program
+{
+ public static void Main ()
+ {
+ OnMainInvoked($"Hello {GetFrontendName()}, .NET here!");
+ }
+
+ [JSEvent] // Used in JS as Program.onMainInvoked.subscribe(..)
+ public static partial void OnMainInvoked (string message);
+
+ [JSFunction] // Set in JS as Program.getFrontendName = () => ..
+ public static partial string GetFrontendName ();
+
+ [JSInvokable] // Invoked from JS as Program.GetBackendName()
+ public static string GetBackendName () => Environment.Version;
+}
+```
+
+## Compile ES Module
+
+Run following command under the solution root:
+
+```sh
+dotnet publish
+```
+
+β which will produce `bin/bootsharp` directory with the following content:
+
+| Name | Type | Description |
+|--------------|--------|-----------------------------------------------------------|
+| types | folder | Contains type declarations for the authored interop APIs. |
+| index.mjs | file | The compiled ES module with embedded binaries. |
+| package.json | file | NPM package manifest for convenient importing. |
+
+## Consume C# APIs in JavaScript
+
+Import the compiled ES module, assign imported functions, boot the runtime and use exported methods:
+
+::: code-group
+
+```js [JavaScript Runtime (Node, Deno, Bun)]
+// Importing compiled ES module.
+import bootsharp, { Program } from "./bin/bootsharp/index.mjs";
+
+// Binding 'Program.GetFrontendName' import invoked in C#.
+Program.getFrontendName = () => process.version;
+
+// Subscribing to 'Program.OnMainInvoked' C# event.
+Program.onMainInvoked.subscribe(console.log);
+
+// Initializing dotnet runtime and invoking entry point.
+await bootsharp.boot();
+
+// Invoking 'Program.GetBackendName' C# method.
+console.log(`Hello ${Program.getBackendName()}!`);
+```
+
+```html [Web Browser]
+
+
+
+```
+
+:::
+
+## Run the App
+
+Assuming the above code is in `main.mjs` file for JavaScript runtimes or in `index.html` file for browser, run the following to test the app:
+
+::: code-group
+
+```sh [Node]
+node main.mjs
+```
+
+```sh [Deno]
+deno run main.mjs
+```
+
+```sh [Bun]
+bun main.mjs
+```
+
+```sh [Browser]
+npx serve
+```
+
+:::
+
+::: tip EXAMPLE
+Find full sources of the minimal sample on GitHub: https://github.com/elringus/bootsharp/tree/main/samples/minimal.
+:::
diff --git a/docs/guide/index.md b/docs/guide/index.md
index 554dda96..e80761e9 100644
--- a/docs/guide/index.md
+++ b/docs/guide/index.md
@@ -1,3 +1,52 @@
# Introduction
-WIP
+## What?
+
+Bootsharp is a solution for building web applications, where domain is authored in .NET C# and is consumed by a standalone JavaScript or TypeScript project.
+
+## Why?
+
+C# is a popular language for building maintainable software with complex domain logic, such as enterprise and financial applications. However, its frontend capabilities are lacking, especially compared to the web ecosystem.
+
+Web platform is the industry-standard for building modern user interfaces. It has best in class tooling and frameworks, such as [React](https://react.dev) and [Svelte](https://svelte.dev) β allowing to build better frontends faster, compared to any other language/platform ecosystem.
+
+In contrast to solutions like [Blazor](https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor), which attempt to bring the entire web platform inside .NET (effectively reversing natural workflow), Bootsharp facilitates high-level interoperation between C# and TypeScript, allowing to build domain and UI layers under their natural ecosystems.
+
+## How?
+
+Bootsharp installs as a [NuGet package](https://www.nuget.org/packages/Bootsharp) to the C# project dedicated for building the solution for web. It's specifically designed to not "leak" the dependency outside entry assembly of the web target, which is essential to keep the domain clean from any platform-specific details.
+
+While it's possible to author both export (C# -> JS) and import (C# <- JS) bindings via static methods, complex solutions will benefit from interface-based interop: simply feed Bootsharp C# interfaces describing export and import API surfaces, and it will automatically generate associated bindings and type declarations.
+
+![](/img/banner.png)
+
+Bootsharp will automatically build and bundle JavaScript package when publishing C# solution, as well as generate `package.json`, so that you can reference the whole C# solution as any other ES module in your web project.
+
+::: code-group
+```jsonc [package.json]
+"scripts": {
+ // Compile C# solution into ES module.
+ "compile": "dotnet publish backend"
+},
+"dependencies": {
+ // Reference C# solution module.
+ "backend": "file:backend"
+}
+```
+:::
+
+::: code-group
+```ts [main.ts]
+// Import C# solution module.
+import bootsharp, { Backend, Frontend } from "backend";
+
+// Boot C# WASM module.
+await boosharp.boot();
+
+// Subscribe to C# event.
+Frontend.onUserChanged.subscribe(updateUserUI);
+
+// Invoke C# method.
+Backend.addUser({ name: "Carl" });
+```
+:::
diff --git a/docs/guide/interop-instances.md b/docs/guide/interop-instances.md
new file mode 100644
index 00000000..252c7764
--- /dev/null
+++ b/docs/guide/interop-instances.md
@@ -0,0 +1,40 @@
+ο»Ώ# Interop Instances
+
+When an interface is supplied as argument or return type of an interop method, instead of serializing it as value, Bootsharp will instead generate an instance binding, eg:
+
+```csharp
+public interface IExported { string GetFromCSharp (); }
+public interface IImported { string GetFromJavaScript (); }
+
+public class Exported : IExported
+{
+ public string GetFromCSharp () => "cs";
+}
+
+public static partial class Factory
+{
+ [JSInvokable] public static IExported GetExported () => new Exported();
+ [JSFunction] public static partial IImported GetImported ();
+}
+
+var imported = Factory.GetImported();
+imported.GetFromJavaScript(); //returns "js"
+```
+
+```ts
+import { Factory, IImported } from "bootsharp";
+
+class Imported implements IImported {
+ getFromJavaScript() { return "js"; }
+}
+
+Factory.getImported = () => new Imported();
+
+const exported = Factory.getExported();
+exported.getFromCSharp(); // returns "cs"
+```
+
+Interop instances are subject to the following limitations:
+- Can't be args or return values of other interop instance method
+- Can't be args of events
+- Interfaces from "System" namespace are not qualified
diff --git a/docs/guide/interop-interfaces.md b/docs/guide/interop-interfaces.md
new file mode 100644
index 00000000..f4433335
--- /dev/null
+++ b/docs/guide/interop-interfaces.md
@@ -0,0 +1,91 @@
+ο»Ώ# Interop Interfaces
+
+Instead of manually authoring a binding for each method, make Bootsharp generate them automatically with `[JSImport]` and `[JSExport]` assembly attributes.
+
+For example, say we have a JavaScript UI (frontend), which needs to be notified when a data is mutated on the C# domain layer (backend), so it can render the updated state; additionally, our frontend may have a setting (eg, stored in browser cache) to temporary mute notifications, which needs to be retrieved by the backend. Create the following interface in C# to describe the expected frontend APIs:
+
+```csharp
+interface IFrontend
+{
+ void NotifyDataChanged (Data data);
+ bool IsMuted ();
+}
+```
+
+Now add the interface type to the JS import list:
+
+```csharp
+[assembly: JSImport([
+ typeof(IFrontend)
+])]
+```
+
+Bootsharp will generate following C# implementation:
+
+```csharp
+public static partial class JSFrontend : IFrontend
+{
+ [JSFunction] public static partial void NotifyDataChanged (Data data);
+ [JSFunction] public static partial bool IsMuted ();
+
+ void IFrontend.NotifyDataChanged (Data data) => NotifyDataChanged(data);
+ bool IFrontend.IsMuted () => IsMuted();
+}
+```
+
+β which you can use in C# to interop with the frontend and following TypeScript spec to be implemented on the frontend:
+
+```ts
+export namespace Frontend {
+ export const onDataChanged: Event<[Data]>;
+ export let isMuted: () => boolean;
+}
+```
+
+Now say we want to provide an API for frontend to request mutation of the data:
+
+```csharp
+interface IBackend
+{
+ void AddData (Data data);
+}
+```
+
+Export the interface to JavaScript:
+
+```csharp
+[assembly: JSExport([
+ typeof(IBackend)
+])]
+```
+
+Get the following implementation auto-generated:
+
+```csharp
+public class JSBackend
+{
+ private static IBackend handler = null!;
+
+ public JSBackend (IBackend handler)
+ {
+ JSBackend.handler = handler;
+ }
+
+ [JSInvokable]
+ public static void AddData (Data data) => handler.AddData(data);
+}
+```
+
+β which will produce following spec to be consumed on JavaScript side:
+
+```ts
+export namespace Backend {
+ export function addData(data: Data): void;
+}
+```
+
+To make Bootsharp automatically inject and inititliaize generate interop implementations, use [dependency injection](/guide/extensions/dependency-injection) extension.
+
+::: tip Example
+Find example on using interop interfaces in the [React sample](https://github.com/elringus/bootsharp/tree/main/samples/react).
+:::
diff --git a/docs/guide/namespaces.md b/docs/guide/namespaces.md
new file mode 100644
index 00000000..5bf304cf
--- /dev/null
+++ b/docs/guide/namespaces.md
@@ -0,0 +1,91 @@
+ο»Ώ# 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.
+
+## Static Methods
+
+Full type name (including namespace) of the declaring type of the static interop method is mapped into JavaScript object name:
+
+```csharp
+class Class { [JSInvokable] static void Method() {} }
+namespace Foo { class Class { [JSInvokable] static void Method() {} } }
+namespace Foo.Bar { class Class { [JSInvokable] static void Method() {} } }
+```
+
+```ts
+import { Class, Foo } from "bootsharp";
+
+Class.method();
+Foo.Class.method();
+Foo.Bar.Class.method();
+```
+
+Methods inside nested classes are treated as if they were declared under namespace:
+
+```csharp
+namespace Foo;
+
+public class Class
+{
+ public class Nested { [JSInvokable] public static void Method() {} }
+}
+```
+
+```ts
+import { Foo } from "bootsharp";
+
+Foo.Class.Nested.method();
+```
+
+## Interop Interfaces
+
+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.
+
+```csharp
+[JSExport([
+ typeof(IExported),
+ typeof(Foo.IExported),
+ typeof(Foo.Bar.IExported),
+])]
+
+interface IExported { void Method(); }
+namespace Foo { interface IExported { void Method(); } }
+namespace Foo.Bar { interface IExported { void Method(); } }
+```
+
+```ts
+import { Exported, Foo } from "bootsharp";
+
+Exported.method();
+Foo.Exported.method();
+Foo.Bar.Exported.method();
+```
+
+## Types
+
+Custom types referenced in API signatures (records, classes, interfaces, etc) are declared under their respective namespace when they have one, or under root otherwise.
+
+```csharp
+public record Record;
+namespace Foo { public record Record; }
+
+partial class Class
+{
+ [JSFunction]
+ public static partial Record Method(Foo.Record r);
+}
+```
+
+```ts
+import { Class, Record, Foo } from "bootsharp";
+
+Class.method = methodImpl;
+
+function methodImpl(r: Record): Foo.Record {
+
+}
+```
+
+## Configuring Namespaces
+
+You can control how namespaces are generated via `Space` patterns of [emit preferences](/guide/emit-prefs).
diff --git a/docs/guide/serialization.md b/docs/guide/serialization.md
new file mode 100644
index 00000000..6c789263
--- /dev/null
+++ b/docs/guide/serialization.md
@@ -0,0 +1,118 @@
+ο»Ώ# Serialization
+
+Most simple types, such as numbers, booleans, strings, arrays (lists) and promises (tasks) of them are marshalled in-memory when crossing the C# <-> JavaScript boundary. Below are some of the natively-supported types (refer to .NET docs for the [full list](https://learn.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/import-export-interop)):
+
+| C# | JavaScript | Task of | Array of |
+|----------|------------|:-------:|:--------:|
+| bool | boolean | βοΈ | β |
+| byte | number | βοΈ | βοΈ |
+| char | string | βοΈ | β |
+| string | string | βοΈ | βοΈ |
+| int | number | βοΈ | βοΈ |
+| long | BigInt | βοΈ | β |
+| float | Number | βοΈ | β |
+| DateTime | Date | βοΈ | β |
+
+When a value of non-natively supported type is specified in an interop API, Bootsharp will attempt to de-/serialize it with [System.Text.JSON](https://learn.microsoft.com/en-us/dotnet/api/system.text.json?view=net-8.0) using fast source-generation mode. The whole process is encapsulated under the hood on both the C# and JavaScript sides, so you don't have to manually author generator hints or specify `[MarshallAs]` attributes for each value:
+
+```csharp
+public record User (long Id, string Name, DateTime Registered);
+
+[JSInvokable]
+public static void AddUser (User user) { }
+
+[JSEvent]
+public static partial void OnUserModified (User user);
+```
+
+β Bootsharp will automatically emit C# and JavaScript code required to de-/serialize `User` record on both ends, so that you can consume the APIs as if they were initially authored in JavaScript:
+
+```ts
+import { Program } from "bootsharp";
+
+Program.addUser({ id: 17, name: "Carl", registered: Date.now() });
+
+Program.onUserModified.subscribe(handleUserModified);
+
+function handleUserModified(user: Program.User) { }
+```
+
+## Enums Serialization
+
+Enums are marshalled as numbers for better performance, while additional name <-> index mappings are emitted on the JavaScript side for convenience.
+
+```csharp
+public enum Options { Foo, Bar }
+
+[JSInvokable]
+public static Options GetOption () => Options.Bar;
+```
+
+β while "GetOptions" return value will be passed to JavaScript as an integer index, Bootsharp will map enum indexes to string values (and vice-versa) in the emitted code, so that following will work as expected:
+
+```ts
+import { Program } from "bootsharp";
+
+const option = Program.getOption();
+console.log(option === Program.Options.Foo); // false
+console.log(option === Program.Options.Bar); // true
+console.log(Program.Options[Program.Options.Foo]); // "Foo"
+console.log(Program.Options[1]); // "Bar"
+```
+
+## Dictionary Serialization
+
+ES6 [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) doesn't natively support JSON serialization, hence Bootsharp will use plain objects when serializing C# dictionaries:
+
+```csharp
+[JSInvokable]
+public static Dictionary GetMap () =>
+ new () { ["foo"] = true, ["bar"] = false };
+```
+
+β the dictionary can be accessed via keys as usual JavaScript object:
+
+```ts
+import { Program } from "bootsharp";
+
+const map = Program.getMap();
+console.log(map.foo); // true
+console.log(map["bar"]); // false
+```
+
+## Collection Interfaces
+
+It's common to use various collection interfaces, such as `IReadOnlyList` or `IReadOnlyDictionary` when authoring C# APIs. Bootsharp will accept any kind of array or dictionary compatible interface in the interop APIs and marshal them as plain arrays and maps by default:
+
+```csharp
+[JSInvokable]
+public static IReadOnlyDictionary Map (
+ IReadOnlyList a, IReadOnlyCollection b) { }
+```
+
+```ts
+import { Program } from "bootsharp";
+
+const map = Program.map(["foo", "bar"], [0, 7]);
+console.log(map.bar); // 7
+```
+
+## Configuring Serialization Behaviour
+
+To override default JSON serializer options used for marshalling the interop data, use `Bootsharp.Serializer.Options` property before the program entry point is invoked:
+
+```csharp
+static class Program
+{
+ static Program () // Static constructor is invoked before 'Main'
+ {
+ // Make enums serialize as strings.
+ var converter = new JsonStringEnumConverter();
+ Bootsharp.Serializer.Options.Converters.Add(converter);
+ }
+
+ public static void Main () { }
+}
+```
+
+Refere to .NET docs for the available serialization options: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/overview.
diff --git a/docs/guide/sideloading.md b/docs/guide/sideloading.md
new file mode 100644
index 00000000..377994d4
--- /dev/null
+++ b/docs/guide/sideloading.md
@@ -0,0 +1,36 @@
+ο»Ώ# Sideloading Binaries
+
+By default, Bootsharp build task will embed project's DLLs and .NET WASM runtime to the generated JavaScript module. While convenient and even required in some cases (eg, for VS Code web extensions), this also adds about 30% of extra size due to binary -> base64 conversion of the embedded files.
+
+To disable the embedding, set `BootsharpEmbedBinaries` build property to false:
+
+```xml
+
+
+ false
+
+```
+
+The `dotnet.wasm` and solution's assemblies will be emitted in the build output directory. You will then have to provide them when booting:
+
+```ts
+const resources = {
+ wasm: Uint8Array,
+ assemblies: [{ name: "Foo.wasm", content: Uint8Array }],
+ entryAssemblyName: "Foo.dll"
+};
+await dotnet.boot({ resources });
+```
+
+β this way the binary files can be streamed directly from server to optimize traffic and initial load time.
+
+Alternatively, set `root` property of the boot options and Bootsharp will automatically fetch the resources form the specified URL:
+
+```ts
+// Assuming the resources are stored in "bin" directory under website root.
+await backend.boot({ root: "/bin" });
+```
+
+::: tip EXAMPLE
+Find sideloading example in the [React sample](https://github.com/elringus/bootsharp/blob/main/samples/react).
+:::
diff --git a/docs/index.md b/docs/index.md
index bd9f3ef4..7c8d67fe 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -7,7 +7,7 @@ titleTemplate: Bootsharp β’ :title
hero:
name: Bootsharp
text: Use C# in web apps with comfort
- tagline: Single-file ES module, auto-generated JavaScript bindings and type definitions.
+ tagline: Author domain in C#, while taking full advantage of the modern JavaScript frontend ecosystem.
actions:
- theme: brand
text: Get Started
@@ -20,35 +20,151 @@ hero:
alt: Bootsharp
---
+
+
+
+
+
+
+
+
+
β¨
+
High-level Interoperability
+
+
Generates JavaScript bindings and type declarations for your C# APIs facilitating seamless interop between the domain and UI.
+
+
+
+
+
+
+
π¦
+
Embed or Sideload
+
+
Choose between embedding all the C# binaries into single-file ES module for portability or sideload for best performance and build size.
+
+
+
+
+
+
+
πΊοΈ
+
Runs Everywhere
+
+
Node, Deno, Bun, web browsers and even constrained environments, such as VS Code extensions β your app will work everywhere.
+
+
+
+
+
+
+
+
+
β‘
+
Interop Interfaces
+
+
Manually author interop APIs via static C# methods or simply feed Bootsharp your domain-specific interfaces β it'll figure the rest.
+
+
+
+
+
+
+
π·οΈ
+
Instance Bindings
+
+
When an interface value is specified in interop API, instance binding is generated allowing to interoperate on stateful objects.
+
+
+
+
+
+
+
π οΈ
+
Customizable
+
+
Configure namespaces for emitted bindings, function and event names, C# -> TypeScript type mappings and more.